Jump to content
    

SII

Свой
  • Posts

    895
  • Joined

  • Last visited

Reputation

0 Обычный

2 Followers

About SII

  • Rank
    Знающий
    Знающий
  • Birthday 12/22/1972

Информация

  • Город
    Array

Recent Profile Visitors

5,616 profile views
  1. Пакеты в keil

    Или собственно ARMовский пакет, где только ядра и указаны. Но обычно лучше пакет под свой МК -- хотя б из-за возможности простого просмотра регистров периферии в процессе отладки.
  2. Ну, судя по вопросу, нужно указать компилятору, в каких секциях что размещать -- он и сгенерит соответствующий код и управляющую информацию. Затем можно, например, все объектники собрать компоновщиком в один объектник (кейловский компоновщик такое умеет -- если память не изменяет, partial linking это обзывается), который скармливать при сборке полного приложения у заказчика. Как секции указывать, зависит от компилятора, так как стандарт это не регламентирует. В ARM Clang кейловском можно, в GCC можно (насколько помню), в мелкомягком компиляторе можно -- так что и в ИАРовском наверняка сие поддерживается.
  3. Ошибка в ArmClang?

    А почему тогда в delete не выбрасывает? Действия-то принципиально одинаковые выполняются...
  4. Ошибка в ArmClang?

    Проверил union. Не работает. Заодно проверил, достаточно ли объявить как volatile только First, но не Last. Тоже недостаточно: ER_IROM1:0000202A STRH R7, [R0,#8] ER_IROM1:0000202C STR R5, [R0,#0xC] ER_IROM1:0000202E STRB R2, [R0,#0x10] ER_IROM1:00002030 STRD.W R2, R6, [R0] ER_IROM1:00002034 LDR R1, [R4,#4] ER_IROM1:00002036 LDR R3, [R4,#8] ER_IROM1:00002038 CMP R1, R0 ER_IROM1:0000203A STR R0, [R3] ER_IROM1:0000203C ADD.W R3, R8, #1 ER_IROM1:00002040 STR R0, [R4,#8] ER_IROM1:00002042 STRB.W R3, [R4,#0x40] ER_IROM1:00002046 BNE loc_20A2 Загрузка First и Last выполняется двумя отдельными командами (2034 и 2036), однако выборка First опять произошла до записи по Last. Фактически -- да. First всегда указывает на первый элемент очереди (FIFO_LNK) или содержит нуль, если очередь пуста. А вот Last указывает либо на последний элемент очереди (FIFO_LNK), либо на поле First самого заголовка (или, если угодно, на сам заголовок -- FIFO_HDR). Эм... Почему? Выборка из Last идёт по адресу 2080 (самая первая LDR), запись по Last -- в 208E, а выборка First -- в 2090. В 2092 идёт запись в Last. Похоже на то. В общем, не глюк компилятора, а оптимизация. Правда, остаётся непонятным, почему именно он выкидывал кусок кода в моём самом первом сообщении этой темы (в операции new): вроде б не должен столь своевольно обращаться с кодом, тем более, что в delete он уже не умничал.
  5. Ошибка в ArmClang?

    Эта опция в данном случае помогла. С ней такой код нагенерило: ER_IROM1:00002080 LDR R2, [R4,#8] ER_IROM1:00002082 MOVS R1, #0 ER_IROM1:00002084 STRH R7, [R0,#8] ER_IROM1:00002086 STR R6, [R0,#0xC] ER_IROM1:00002088 STRB R1, [R0,#0x10] ER_IROM1:0000208A STR R1, [R0] ER_IROM1:0000208C STR R5, [R0,#4] ER_IROM1:0000208E STR R0, [R2] ER_IROM1:00002090 LDR R2, [R4,#4] ER_IROM1:00002092 STR R0, [R4,#8] ER_IROM1:00002094 CMP R2, R0 Первая LDR -- это загрузка указателя Last, а последняя LDR -- это загрузка First; запись по адресу в Last, как видим, происходит непосредственно перед загрузкой First, поэтому всё будет корректно. Ну а без этой опции код такой: ER_IROM1:00002006 LDRD.W R3, R1, [R4,#4] ER_IROM1:0000200A MOVS R2, #0 ER_IROM1:0000200C STRB R2, [R0,#0x10] ER_IROM1:0000200E STRD.W R2, R8, [R0] ER_IROM1:00002012 STR R0, [R1] ER_IROM1:00002014 ADDS R1, R6, #1 ER_IROM1:00002016 CMP R3, R0 ER_IROM1:00002018 STRH R5, [R0,#8] ER_IROM1:0000201A STR R7, [R0,#0xC] ER_IROM1:0000201C STR R0, [R4,#8] ER_IROM1:0000201E STRB.W R1, [R4,#0x40] ER_IROM1:00002022 BNE loc_207C Переупорядочило, как видим, куда агрессивней -- и некорректно для этого случая. Ну а с volatile (и без опции) код такой: ER_IROM1:00002022 MOVS R2, #0 ER_IROM1:00002024 STR R7, [R0,#4] ER_IROM1:00002026 STRH R6, [R0,#8] ER_IROM1:00002028 STR.W R8, [R0,#0xC] ER_IROM1:0000202C STRB R2, [R0,#0x10] ER_IROM1:0000202E STR R2, [R0] ER_IROM1:00002030 LDR R1, [R4,#8] ER_IROM1:00002032 STR R0, [R1] ER_IROM1:00002034 ADDS R1, R5, #1 ER_IROM1:00002036 STR R0, [R4,#8] ER_IROM1:00002038 STRB.W R1, [R4,#0x40] ER_IROM1:0000203C LDR R1, [R4,#4] ER_IROM1:0000203E CMP R1, R0 ER_IROM1:00002040 BNE loc_209C А вот эта статейка навела меня на мысль, как сие исправить и без -fno-strict-aliasing, и без volatile: использовать объединение для поля First (одно поле -- указатель на сам заголовок, другое -- на первый элемент очереди). На досуге проверю.
  6. Ошибка в ArmClang?

    О, спасибо. Похоже, то, что нужно. Жаль, конечно, что это не стандартное средство языка, а расширение компилятора, но жрём, что дают 🙂 ADD. Правда, ссылка -- не для ARM Clang, а для старого компилятора 5-й версии, но в ARM Clang тоже есть. ADD2. Увы, не помогло. Похоже, эти вещи срабатывают лишь для операций, которые, с точки зрения компилятора, имеют побочные эффекты, и не препятствуют ему производить предзагрузку из ячейки, явного обращения на запись к которой нет. Так что только volatile.
  7. Ошибка в ArmClang?

    Он чисто сишный, вроде как, а в це++ не поддерживается. А в данном случае она inline -- для эффективности.
  8. Ошибка в ArmClang?

    Да решил я уже проблему, решил. И не жалуюсь я, а просто констатирую, что без костылей и/или потери эффективности в современной версии языка, похоже, не обойтись.
  9. Ошибка в ArmClang?

    Ну, во-первых, он не решает данную проблему, как понимаете. А во-вторых, он специфичен для конкретного компилятора, что не шибко хорошо. Ну, в моём представлении, барьер памяти влияет на работу процессора, а не компилятора (применительно к ARM -- команды DMB, DSB, ISB). А мне нужно именно на компилятор повлиять, чтоб не переносил код, куда ему вздумается. Или речь о чём-то другом?
  10. Ошибка в ArmClang?

    Стандартный? Или конкретного компилятора? В числе стандартных я не находил. Ну, выше я указывал проблему с моей функцией Append, добавляющей новый элемент в хвост очереди: inline void FIFO_HDR::Append(FIFO_LNK *Entry) { Entry->Next = nullptr; Last->Next = Entry; Last = Entry; } Когда очередь пуста, поле First заголовка очереди содержит нуль, а поле Last -- адрес поля First. Поле Next всегда является самым первым полем элемента очереди, поэтому такой "трюк" благополучно работает. Собственно, это очень распространённый способ организации очереди, который я встречал миллион раз (в частности, в Винде, но далеко не только в ней). Проблема возникла из-за того, что компилятор не способен увидеть, что вызов Append может поменять поле First заголовка очереди (что происходит, когда перед вызовом очередь пуста), он видит изменение только Last. В моём случае он выполнил предвыборку полей First и Last одной командой LDRD, затем сгенерировал вызов Append, а потом воспользовался предвыбранным, а не обновлённым значением поля First -- что дало неверный результат. Если бы была возможность атрибутировать эту функцию как имеющую побочные эффекты, запрещающие перемещение кода мимо неё, проблема бы не возникала. Конечно, это в какой-то мере ограничило бы оптимизатор, но не слишком сильно, думается. Запросто. Це++ необъятен.
  11. Ошибка в ArmClang?

    А ещё это -- загромождение текста лишними объявлениями. Мне не нравится то, что, кроме volatile, нет других атрибутов для переменных, которые могли бы указать, в частности, что данную переменную нельзя выкинуть из программы по желанию компилятора. Если бы был такой атрибут, его пришлось бы указать ровно один раз -- в объявлении самой переменной. Нет и атрибутов для функций, чтобы можно было, скажем, указать, что функция может вызывать побочные эффекты, поэтому нельзя перемещать код до и после вызова этой функции. Ну и т.д. и т.п. Да, бывают случаи, когда действительно не обойтись. Однако мне не нравится, что нужно постоянно применять костыли просто из-за того, что в языке отсутствуют средства, которые позволили бы объяснить компилятору, что можно делать, а что нельзя. Как человек выше указал, существуют же барьеры памяти -- так почему нельзя указать, что некая функция является барьером для оптимизации?
  12. Ошибка в ArmClang?

    Это уже костыль. Прямым решением был бы атрибут, объясняющий компилятору правила обращения с переменной.
  13. Ошибка в ArmClang?

    То, в чём была проблема изначально, я выложил, подсказки получил -- мне этого достаточно. Дальше в какой-то мере действительно монолог: описываю дальнейшие проблемы и указываю, как их решил. Я прекрасно знаю, что такое volatile, и использую его, когда надо. А вот что компилятор очень вольно обращается с порядком операций -- это таки да, было для меня новостью. Эффективность volatile действительно в ряде случаев убивает. Чтоб не было проблем с телепатией, слегка разжую. struct FIFO_HDR { // Адрес поля связи первого элемента в очереди или нуль. FIFO_LNK *First; // Адрес поля связи последнего элемента в очереди. Если очередь пуста, это // поле содержит адрес поля First. FIFO_LNK *Last; } Таким было моё первоначальное определение заголовка очереди, с которым компилятор позволил себе предзагрузку значения поля First, что в дальнейшем привело к неправильной работе (он сравнивал старое значение поля First, которое к тому времени изменилось). struct FIFO_HDR { FIFO_LNK* volatile First; FIFO_LNK* volatile Last; } Таким стало новое определение. Теперь компилятор никакими предзагрузками не занимается, и код работает правильно. Однако, если в программе будет нечто вроде такого: Queue.First->A = .... Queue.First->B = .... (обращения к нескольким полям структуры, на которую указывает Queue.First), компилятор уже не сможет соптимизировать этот код и загрузить указатель из First только один раз, он будет вынужден загружать First для каждого обращения к тому или иному полю, что эффективность и убивает. Так что, извините, Вы не правы, утверждая, что лишних команд не будет. Можно возразить: мол, ты можешь загрузить значение из First во временную переменную и использовать его оттуда. Могу, конечно. Но это -- раздувание текста программы и лишняя работа для программиста на, по сути, пустом месте. Правильным было бы дать программисту средства запретить компилятору слишком агрессивную оптимизацию. Например, если бы я мог снабдить мою функцию Append (которая может модифицировать поле First неочевидным для компилятора способом) неким атрибутом, запрещающим компилятору перемещать обращения к памяти до и после её вызова, проблемы бы не возникало и при этом компилятор мог бы свободно оптимизировать код до и после вызова функции. Если говорить про влияние на локальные объекты -- в общем, согласен с Вами (за исключением того, что надо бы программисту дать в руки стандартные средства явно указывать компилятору пределы возможной оптимизации, а не заставлять обходить её с помощью временных переменных и т.п. вещей, усложняющих восприятие программы). А вот если говорить про глобальные объекты -- нет, не согласен. Если идёт присваивание глобальной переменной, я считаю правильным, чтобы компилятор всегда его выполнял, даже если, как ему кажется, она нигде не используется -- на то она и глобальная. (Ну а не злоупотреблять глобальными переменными -- это уже забота программиста, да). volatile, как я уже показал выше, не является полноценным выходом, так как препятствует любой оптимизации. Вот был бы атрибут, говорящий компилятору, что эта переменная используется, и он не должен выкидывать обращения к ней, но в остальном может оптимизировать обращения к ней обычным образом, а не трактовать как volatile... В данном случае не соглашусь. Данный регистр контроллера никогда не меняется аппаратурой, т.е. его значение абсолютно устойчиво; соответственно, если программа пишет в него что-то, а затем хочет использовать записанное значение, технически совершенно корректным и наиболее эффективным было бы использовать значение, уже находящееся в одном из регистров процессора, а не загружать его каждый раз из регистра контроллера -- а вот в случае с volatile загрузка действительно обязательна. В данном конкретном случае не "любое обращение" должно бы компилироваться в обращение к регистру, а только запись в него; чтение можно было бы опустить, если уже есть копия значения, -- но объяснить это компилятору невозможно.
  14. Ошибка в ArmClang?

    Судя по всему, компилятор решил, что, раз видимого использования поля нет, то запись в него можно вообще выкинуть -- причём для этого ему нужно было проанализировать код всей программы, а не одного только файла (блоки регистров -- глобальные переменные; соответственно, обращения к их полям могут быть где угодно). Я так и не понял, разрешено ли такое стандартом. volatile исправляет ситуацию, но потенциально ведёт к потере эффективности (в данном конкретном случае -- нет, не ведёт). Вообще, похоже, разработчики стандарта разрешили крайне агрессивную оптимизацию, но не предложили никаких средств для того, чтоб её ограничивать (кроме древнего volatile, нередко убивающего эффективность, и std::atomic, которые тоже вполне могут быть абсолютно неэффективными, да и предназначены, вообще говоря, для другого). Например: IORP *P = new (std::nothrow) IORP; if ( P == nullptr ) { return false; } P->Buffer = const_cast<pvoid>(Buffer); P->Size = Size; P->CB = CB; P->Ignore_Errors = false; Tx_Queue.Append(&P->Link); ++Tx_Req_Count; if ( Tx_Queue.First == &P->Link ) { Init_Transmit(); } При включённой оптимизации компилятор одной командой LDRD грузил оба поля структуры Tx_Queue (First и Last; если очередь пуста, Last указывает на First -- типичное использование подобных очередей, например, в недрах Винды) сразу после выделения памяти, а не в Tx_Queue.Append, где осуществляется первое обращение к Tx_Queue. Позднее, в последнем if, он сравнивал старое (предварительно загруженное) значение поля First, а оно к тому времени может быть изменено в Append (только не прямо, а через указатель -- если очередь была пуста; компилятор, понятно, не видит этой возможности изменения). Никаких стандартных средств сказать компилятору, что Append должна служить "водоразделом", нет, так что приходится First и Last объявлять volatile, хотя в реальности они не меняются произвольным образом и зачастую действительно можно загрузить значение и использовать его несколько раз, что будет запрещено при использовании volatile.
  15. Ошибка в ArmClang?

    Вообще, эта проблема оказалась не единственной. В другом месте у меня простейшее присваивание (внутри функции, являющейся методом класса): HW::USB.DEV.DESCADD = EP_Desc; EP_Desc -- поле этого класса, содержит указатель на структуру типа HW::USBD_BUF_DESC (а точней, на массив таких структур -- это описатели конечных точек контроллера USB). Память под сей массив была выделена ранее и указатель на неё помещён в это поле, ну а в данном методе я переписываю указатель из поля класса в регистр контроллера USB в процессе включения последнего (HW::USB -- "переменная", на самом деле являющаяся блоком регистров контроллера USB). Так вот, это присваивание компилятором на любой оптимизации, начиная с -O1, просто выбрасывается, хотя весь остальной код функции генерируется правильно. Чтоб оно было, требуется описать поле HW::USB.DEV.DESCADD не как volatile USBD_BUF_DESC *, а как volatile USBD_BUF_DESC * volatile (т.е. волатильными являются не только описатели конечных точек, но и хранящий их адрес регистр, что не соответствует действительности). В общем, очень странное поведение...
×
×
  • Create New...