SII
-
Постов
898 -
Зарегистрирован
-
Посещение
Сообщения, опубликованные SII
-
-
В 08.06.2023 в 15:54, xvr сказал:
Знак проверяется правильно, а вот сдвиг частичного остатка (п3 в вашем алгоритме) делается не правильно.
Ну и собственно реализация в целом неправильная - у вас описан алгоритм, а не схема. HDL язык - это не язык програмирования! Это язык описания аппаратуры.
Посмотрите какие нибудь примеры, как на нём пишут.
А ещё лучше -- разобраться, как это делается в реальном железе, на логических элементах, триггерах и прочая. Если подходить к HDL как к языку программирования, хорошего результата не будет (даже если что-то и будет работать).
-
А ещё при использовании DMA возможны проблемы из-за того, что у Cortex-M7 есть кэш -- соответственно, перед запуском передачи по DMA надо убедиться, что данные физически лежат в памяти (а не только в кэше), ну а после завершения приёма -- очистить кэш, чтоб добраться до данных в памяти.
-
24 минуты назад, maksimdag0 сказал:
Но возможность выбора ядра появится только тогда, когда ты скачаешь пакет для своего МК
Или собственно ARMовский пакет, где только ядра и указаны. Но обычно лучше пакет под свой МК -- хотя б из-за возможности простого просмотра регистров периферии в процессе отладки.
-
Ну, судя по вопросу, нужно указать компилятору, в каких секциях что размещать -- он и сгенерит соответствующий код и управляющую информацию. Затем можно, например, все объектники собрать компоновщиком в один объектник (кейловский компоновщик такое умеет -- если память не изменяет, partial linking это обзывается), который скармливать при сборке полного приложения у заказчика.
Как секции указывать, зависит от компилятора, так как стандарт это не регламентирует. В ARM Clang кейловском можно, в GCC можно (насколько помню), в мелкомягком компиляторе можно -- так что и в ИАРовском наверняка сие поддерживается.
-
1 час назад, Arlleex сказал:
Все вполне может быть объяснено тем же самым механизмом строгого alias
А почему тогда в delete не выбрасывает? Действия-то принципиально одинаковые выполняются...
-
Проверил 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.
1 час назад, amaora сказал:Так значит типы разные все таки бывают при работе с First и Last?
Фактически -- да. First всегда указывает на первый элемент очереди (FIFO_LNK) или содержит нуль, если очередь пуста. А вот Last указывает либо на последний элемент очереди (FIFO_LNK), либо на поле First самого заголовка (или, если угодно, на сам заголовок -- FIFO_HDR).
1 час назад, jcxz сказал:Странно, а мы видим иное: запись по адресу в Last, как видим, происходит непосредственно после загрузки First.
Эм... Почему? Выборка из Last идёт по адресу 2080 (самая первая LDR), запись по Last -- в 208E, а выборка First -- в 2090. В 2092 идёт запись в Last.
1 час назад, Arlleex сказал:Я, так понял, здесь классическая "грабля" с оптимизацией при strict aliasing, которую каждая тематическая статья приводит в пример
Похоже на то. В общем, не глюк компилятора, а оптимизация. Правда, остаётся непонятным, почему именно он выкидывал кусок кода в моём самом первом сообщении этой темы (в операции new): вроде б не должен столь своевольно обращаться с кодом, тем более, что в delete он уже не умничал.
-
В 07.02.2023 в 12:19, Arlleex сказал:
А что насчет -fno-strict-aliasing в опциях компилятора? На уровнях O1 и выше отключится строгий алиасинг и оптимизирующего упреждающего чтения First вместе с Last быть не должно.
Хотя проблема в том, что указатели у Вас одинакового типа - это, скорее всего, не сработает.
Эта опция в данном случае помогла. С ней такой код нагенерило:
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 (одно поле -- указатель на сам заголовок, другое -- на первый элемент очереди). На досуге проверю.
-
1 час назад, Arlleex сказал:
В ARM CLang тоже поддерживаются
О, спасибо. Похоже, то, что нужно. Жаль, конечно, что это не стандартное средство языка, а расширение компилятора, но жрём, что дают 🙂
ADD. Правда, ссылка -- не для ARM Clang, а для старого компилятора 5-й версии, но в ARM Clang тоже есть.
ADD2. Увы, не помогло. Похоже, эти вещи срабатывают лишь для операций, которые, с точки зрения компилятора, имеют побочные эффекты, и не препятствуют ему производить предзагрузку из ячейки, явного обращения на запись к которой нет. Так что только volatile.
-
1 час назад, amaora сказал:
квалификатор restrict в аргументах функции
Он чисто сишный, вроде как, а в це++ не поддерживается.
1 час назад, amaora сказал:Есть и на уровне компилятора барьеры, любой вызов extern функции
А в данном случае она inline -- для эффективности.
-
Да решил я уже проблему, решил. И не жалуюсь я, а просто констатирую, что без костылей и/или потери эффективности в современной версии языка, похоже, не обойтись.
-
1 минуту назад, jcxz сказал:
В IAR есть атрибут __root:
__root int x;
Он указывает компилятору не выбрасывать сам объект (переменную, константу, ...) из программы даже если к нему нет обращений. Но именно только: не выбрасывать сам объект. __root совсем не даёт гарантии от удаления операций работы с переменной, если оптимизатор решил, что скажем x нигде не используется.
Ну, во-первых, он не решает данную проблему, как понимаете. А во-вторых, он специфичен для конкретного компилятора, что не шибко хорошо.
1 минуту назад, jcxz сказал:Так укажите - внесите их внутрь функции. Кто вам мешает? Либо можно функцию обернуть в макрос, в котором перед и после вызова функции поставить барьеры.
Ну, в моём представлении, барьер памяти влияет на работу процессора, а не компилятора (применительно к ARM -- команды DMB, DSB, ISB). А мне нужно именно на компилятор повлиять, чтоб не переносил код, куда ему вздумается. Или речь о чём-то другом?
-
6 минут назад, Arlleex сказал:
А атрибут есть - __attribute__((used))
Стандартный? Или конкретного компилятора? В числе стандартных я не находил.
7 минут назад, Arlleex сказал:Это какие, например, побочные эффекты и для чего запрещать перемещение?
Ну, выше я указывал проблему с моей функцией 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 -- что дало неверный результат. Если бы была возможность атрибутировать эту функцию как имеющую побочные эффекты, запрещающие перемещение кода мимо неё, проблема бы не возникала. Конечно, это в какой-то мере ограничило бы оптимизатор, но не слишком сильно, думается.
14 минут назад, Arlleex сказал:Может, Вы о них просто не в курсе?
Запросто. Це++ необъятен.
-
2 часа назад, Arlleex сказал:
Не понятно, что SII не нравится в создании локальной неквалифицированной версии идентификатора, ведь это:
А ещё это -- загромождение текста лишними объявлениями. Мне не нравится то, что, кроме volatile, нет других атрибутов для переменных, которые могли бы указать, в частности, что данную переменную нельзя выкинуть из программы по желанию компилятора. Если бы был такой атрибут, его пришлось бы указать ровно один раз -- в объявлении самой переменной. Нет и атрибутов для функций, чтобы можно было, скажем, указать, что функция может вызывать побочные эффекты, поэтому нельзя перемещать код до и после вызова этой функции. Ну и т.д. и т.п.
2 часа назад, Arlleex сказал:Там тоже без чтения в локальную копию не обойтись.
Да, бывают случаи, когда действительно не обойтись. Однако мне не нравится, что нужно постоянно применять костыли просто из-за того, что в языке отсутствуют средства, которые позволили бы объяснить компилятору, что можно делать, а что нельзя. Как человек выше указал, существуют же барьеры памяти -- так почему нельзя указать, что некая функция является барьером для оптимизации?
-
6 минут назад, Сергей Борщ сказал:
Возможно. Заведите временную не-volatile переменную, скопируйте в нее значение указателя и обращайтесь к полям через нее.
Это уже костыль. Прямым решением был бы атрибут, объясняющий компилятору правила обращения с переменной.
-
1 час назад, jcxz сказал:
Ваш разговор напоминает монолог с самим собой - деклараций сущностей, о которых ведёте речь, вы не приводите, априори считая читателей телепатами. Что они могут вам посоветовать если даже не понимают о чём речь???
То, в чём была проблема изначально, я выложил, подсказки получил -- мне этого достаточно. Дальше в какой-то мере действительно монолог: описываю дальнейшие проблемы и указываю, как их решил.
1 час назад, jcxz сказал:volatile - вещь полезная и незаменимая при работе с периферией из си. Посмотрите хотя бы на определения регистров периферии для разных МК в заголовочных файлах компиляторов. "Эффективность убивает" она только в неумелых руках.
Я прекрасно знаю, что такое 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 неочевидным для компилятора способом) неким атрибутом, запрещающим компилятору перемещать обращения к памяти до и после её вызова, проблемы бы не возникало и при этом компилятор мог бы свободно оптимизировать код до и после вызова функции.
1 час назад, jcxz сказал:ЗЫ: И (имхо) - очень умный оптимизатор должен выкидывать код, который в результате работы прямо или опосредованно не влияет ни на одну из volatile-сущностей в программе (не считая влияния времени исполнения этого кода). Пусть даже окажется выкинутой половина всей программы с целым деревом вызовов функций.
Если говорить про влияние на локальные объекты -- в общем, согласен с Вами (за исключением того, что надо бы программисту дать в руки стандартные средства явно указывать компилятору пределы возможной оптимизации, а не заставлять обходить её с помощью временных переменных и т.п. вещей, усложняющих восприятие программы).
А вот если говорить про глобальные объекты -- нет, не согласен. Если идёт присваивание глобальной переменной, я считаю правильным, чтобы компилятор всегда его выполнял, даже если, как ему кажется, она нигде не используется -- на то она и глобальная. (Ну а не злоупотреблять глобальными переменными -- это уже забота программиста, да). volatile, как я уже показал выше, не является полноценным выходом, так как препятствует любой оптимизации. Вот был бы атрибут, говорящий компилятору, что эта переменная используется, и он не должен выкидывать обращения к ней, но в остальном может оптимизировать обращения к ней обычным образом, а не трактовать как volatile...
1 час назад, Сергей Борщ сказал:Именно, что соответствует - любое обращение к переменной должно компилироваться в обращение к регистру. Компилятор не может знать, как именно обращение к регистру влияет на состояние абстрактной машины, это забота программиста. Если программист точно знает, что в каком-то месте обращения можно соптимизировать - он может это сделать при помощи временной не-volatile переменной.
В данном случае не соглашусь. Данный регистр контроллера никогда не меняется аппаратурой, т.е. его значение абсолютно устойчиво; соответственно, если программа пишет в него что-то, а затем хочет использовать записанное значение, технически совершенно корректным и наиболее эффективным было бы использовать значение, уже находящееся в одном из регистров процессора, а не загружать его каждый раз из регистра контроллера -- а вот в случае с volatile загрузка действительно обязательна. В данном конкретном случае не "любое обращение" должно бы компилироваться в обращение к регистру, а только запись в него; чтение можно было бы опустить, если уже есть копия значения, -- но объяснить это компилятору невозможно.
-
14 часов назад, Arlleex сказал:
Пока что из описания видно как раз некорректное определение, а не глюк компилятора
Судя по всему, компилятор решил, что, раз видимого использования поля нет, то запись в него можно вообще выкинуть -- причём для этого ему нужно было проанализировать код всей программы, а не одного только файла (блоки регистров -- глобальные переменные; соответственно, обращения к их полям могут быть где угодно). Я так и не понял, разрешено ли такое стандартом. 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.
-
Вообще, эта проблема оказалась не единственной. В другом месте у меня простейшее присваивание (внутри функции, являющейся методом класса):
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 (т.е. волатильными являются не только описатели конечных точек, но и хранящий их адрес регистр, что не соответствует действительности).
В общем, очень странное поведение...
-
5 часов назад, xvr сказал:
Предположу что здесь UB - компилятор знает что такое malloc и считает Ptr-1 не инициализированной памятью. Он же не в курсе, что вы в потраха менеджера памяти полезли.
Сделайте Ptr volatile переменной, возможно поможет
Да, volatile помог, хотя сгенерированный код, конечно, стал куда менее эффективным: постоянные загрузки из памяти:
ER_IROM1:00005FA8 ; pvoid operator new(size_t Size, const std::nothrow_t *) ER_IROM1:00005FA8 EXPORT _ZnwjRKSt9nothrow_t ER_IROM1:00005FA8 _ZnwjRKSt9nothrow_t ; CODE XREF: UART_ATSAMx5::Receive(void *,ushort,void (*)(void const*,ushort,bool,bool,bool,bool,bool),bool)+2C↑p ER_IROM1:00005FA8 ; UART_ATSAMx5::Transmit(void const*,ushort,void (*)(void const*,ushort,bool,bool,bool,bool,bool))+2C↑p ... ER_IROM1:00005FA8 ER_IROM1:00005FA8 var_C = -0xC ER_IROM1:00005FA8 Ptr = 4 ER_IROM1:00005FA8 ER_IROM1:00005FA8 PUSH {R7,LR} ER_IROM1:00005FAA SUB SP, SP, #8 ER_IROM1:00005FAC BL malloc ER_IROM1:00005FB0 STR R0, [SP,#0x10+var_C] ER_IROM1:00005FB2 LDR R0, [SP,#0x10+var_C] ER_IROM1:00005FB4 CBZ R0, loc_5FCC ER_IROM1:00005FB6 LDR R0, [SP,#0x10+var_C] ER_IROM1:00005FB8 MOV R1, #0x20000060 ER_IROM1:00005FC0 LDR.W R0, [R0,#-4] ER_IROM1:00005FC4 LDR R2, [R1] ER_IROM1:00005FC6 ADD R0, R2 ER_IROM1:00005FC8 STR R0, [R1] ER_IROM1:00005FCA B loc_5FD2 ER_IROM1:00005FCC ; --------------------------------------------------------------------------- ER_IROM1:00005FCC ER_IROM1:00005FCC loc_5FCC ; CODE XREF: operator new(uint,std::nothrow_t const&)+C↑j ER_IROM1:00005FCC MOVS R0, #1 ; Status ER_IROM1:00005FCE BL _Z14Set_LED_Statusb ; Set_LED_Status(bool) ER_IROM1:00005FD2 ER_IROM1:00005FD2 loc_5FD2 ; CODE XREF: operator new(uint,std::nothrow_t const&)+22↑j ER_IROM1:00005FD2 LDR R0, [SP,#0x10+var_C] ER_IROM1:00005FD4 ADD SP, SP, #8 ER_IROM1:00005FD6 POP {R7,PC} ER_IROM1:00005FD6 ; End of function operator new(uint,std::nothrow_t const&)
3 часа назад, Alex11 сказал:Еще вариант: Вы не используете нигде значение счетчика выделенной памяти. При этом оптимизатор его убивает за полной ненадобностью.
Нет, она использовалась и далее в delete, где всё было корректно (я код выше приводил), и в другом месте -- печатал её значение...
-
Попробовал на всякий случай переопределить не обычную операцию new (которая, по стандарту, кидает исключение, если не может выделить память), а new(Size, std::nothrow) -- не помогло. Но зато заметил, что при включённой оптимизации компилятор сжирает if в такой конструкции:
P = new type; if ( P == nullptr )...
А вот в такой -- не сжирает:
P = new (std::nothrow) type; if ( P == nullptr )...
Но здесь к нему претензий нет: по стандарту, первый вариант никогда nullptr не возвращает, а вот второй -- именно его и возвращает, если выделить не может.
-
Использую MDK ARM 5.38.0.0, версия компилятора 6.19, пишу на C++20.
Для контроля за динамическим выделением/освобождением памяти переопределил операции new и delete:
uint32 Alloc_Ctr = 0; [[nodiscard]] pvoid operator new(std::size_t Size) { pvoid Ptr = std::malloc(Size); if ( Ptr != nullptr ) { Alloc_Ctr = Alloc_Ctr + *(reinterpret_cast<puint32>(Ptr) - 1); } else { Set_LED_Status(true); } return Ptr; } void operator delete(pvoid Ptr) noexcept { Alloc_Ctr -= *(reinterpret_cast<puint32>(Ptr) - 1); std::free(Ptr); }
Как можно видеть, они подсчитывают количество фактически выделенных или освобождённых байтов памяти, пользуясь тем, что размер выделенного блока помещается в слове, непосредственно предшествующем этому блоку. Кроме того, если new не может выделить память, он зажигает светодиод, вызывая функцию Set_LED_Status.
При отключенной оптимизации (-O0) всё работает, как и следовало ожидать, замечательно. А когда включаешь -O2, работать перестаёт 🙂
Я дизассемблировал прошивку и вот что получил. Сначала операция delete:
ER_IROM1:00005EA0 ; void __fastcall operator delete(pvoid Ptr) ER_IROM1:00005EA0 EXPORT _ZdlPv ER_IROM1:00005EA0 _ZdlPv ; CODE XREF: operator delete(void *,uint)↓j ER_IROM1:00005EA0 Ptr = R0 ; pvoid ER_IROM1:00005EA0 MOV R2, #0x20000060 ER_IROM1:00005EA8 LDR.W R1, [Ptr,#-4] ER_IROM1:00005EAC LDR R3, [R2] ER_IROM1:00005EAE SUBS R1, R3, R1 ER_IROM1:00005EB0 STR R1, [R2] ER_IROM1:00005EB2 B.W free ER_IROM1:00005EB2 ; End of function operator delete(void *)
Как видимо, всё хорошо: в R2 загружается адрес переменной Alloc_Ctr (20000060); из слова, предшествующего освобождаемому блоку (его адрес -- в R0, дизассемблер влепил ему имя Ptr по названию параметра функции), извлекается размер блока, он вычитается из счётчика, счётчик записывается на место, а потом вызывается обычная функция free, которая и освобождает память. В общем, поведение абсолютно соответствует тому, что написано на C++.
А теперь смотрим на new:
ER_IROM1:00005ED4 ; pvoid operator new(size_t Size) ER_IROM1:00005ED4 EXPORT _Znwj ER_IROM1:00005ED4 _Znwj ; CODE XREF: UART_ATSAMx5::Receive(void *,ushort,void (*)(void const*,ushort,bool,bool,bool,bool,bool),bool)+2A↑p ER_IROM1:00005ED4 ; UART_ATSAMx5::Transmit(void const*,ushort,void (*)(void const*,ushort,bool,bool,bool,bool,bool))+26↑p ... ER_IROM1:00005ED4 PUSH {R4,LR} ER_IROM1:00005ED6 BL malloc ER_IROM1:00005EDA MOV R4, R0 ER_IROM1:00005EDC CBZ R0, loc_5EE2 ER_IROM1:00005EDE MOV R0, R4 ER_IROM1:00005EE0 POP {R4,PC} ER_IROM1:00005EE2 ; --------------------------------------------------------------------------- ER_IROM1:00005EE2 ER_IROM1:00005EE2 loc_5EE2 ; CODE XREF: operator new(uint)+8↑j ER_IROM1:00005EE2 MOVS R0, #1 ; Status ER_IROM1:00005EE4 BL _Z14Set_LED_Statusb ; Set_LED_Status(bool) ER_IROM1:00005EE8 MOV R0, R4 ER_IROM1:00005EEA POP {R4,PC} ER_IROM1:00005EEA ; End of function operator new(uint)
Как видим, сначала вызывается malloc, затем производится условный переход по значению полученного указателя (CBZ -- переход, если нуль). Ветка для нулевого значения совершенно корректна: вызывается функция зажигания светодиода и возвращается нулевой указатель.
А вот ветка для ненулевого указателя некорректна: компилятор полностью выкинул увеличение счётчика выделенной памяти, как будто строки Alloc_Ctr = Alloc_Ctr + *(reinterpret_cast<puint32>(Ptr) - 1); внутри if попросту не было, и просто возвращает полученный указатель!
Есть у кого-нибудь какие-нибудь мысли по этому поводу?
Пы.Сы. Вот потому я и люблю ассемблер: что ты написал, то и будет 🙂
-
2 часа назад, tonyk_av сказал:
То, что на сайте TI фаза жизненного цикла у этих микросхем обозначена как "Active" совершенно не означает, что они есть на складах продавцов и их можно купить. Не так давно понадобилось сделать схему на ТТЛ, так что пришлось изучить доступность микросхем на складах как буржуйских, так и отечественных.
Ну, Active означает, что ТИ (именно ТИ) готова их поставлять. Понятно, что по 10 штук она не продаёт, но если партия в ннадцать тысяч -- то пожалуйста.
-
В 21.12.2022 в 09:31, Plain сказал:
Студент, очевидно, ведь данные ИС сняли с производства лет 30 назад.
У нас сняты, а вот TI, насколько помню, продолжает выпускать половину, если не 2/3, классических ТТЛ/ТТЛШ микросхем, разработанных ею ещё в 60-70-х годах... Ну и, кроме того, если не понимать, как всё это работает на уровне логики, и учиться ваять сразу на HDL, результаты будут... так себе.
-
В 10.09.2022 в 23:11, bvn123 сказал:
Спасибо, почитаю подробнее о volatile.
Сие ключевое слово указывает компилятору, что значение переменной может измениться в любой момент неочевидным для компилятора образом. Например:
int a; ... if ( a == 0 ) { ... } ... if ( a != 0 ) { ... }
Если переменная a объявлена, как написано, т.е. без volatile, компилятор может загрузить её значение в регистр, а затем использовать для обоих проверок. Если же указать volatile, для каждой из проверок значение будет в обязательном порядке загружаться из памяти. Соответственно, если после первого, но перед вторым if значение изменилось (обработчиком прерывания или другим потоком), то без volatile это изменение может быть замечено, а может остаться незамеченным (в зависимости от того, какой код будет сгенерирован -- а это зависит в т.ч. от уровня оптимизации), а с volatile будет гарантированно замечено.
-
16 часов назад, Сергей Борщ сказал:
Максимальный уровень оптимизации как бы предполагает, что клиент готов отдать все за любой прирост
Сравнить же всё равно интересно: насколько велик прирост скорости у разных компиляторов и как много места им для этого понадобилось. Да и не всегда "готов отдать всё": скажем, прошивка перестанет влезать во флэш определённого размера, нужно искать что-нибудь побольше -- или несколько пожертвовать скоростью.
STM32H743 не хочет работать на 480МГц
в STM
Опубликовано · Пожаловаться
Насколько помню, в STM32H745, чтоб работать на 480 МГц, надо должным образом подавать питание на МК, из-за чего на СТМовской плате (DISCO которая) требовалось перепаять пару перемычек.