Перейти к содержанию
    

SII

Свой
  • Постов

    898
  • Зарегистрирован

  • Посещение

Сообщения, опубликованные SII


  1. В 08.06.2023 в 15:54, xvr сказал:

    Знак проверяется правильно, а вот сдвиг частичного остатка (п3 в вашем алгоритме) делается не правильно.

    Ну и собственно реализация в целом неправильная - у вас описан алгоритм, а не схема. HDL язык - это не язык програмирования! Это язык описания аппаратуры.

    Посмотрите какие нибудь примеры, как на нём пишут.

    А ещё лучше -- разобраться, как это делается в реальном железе, на логических элементах, триггерах и прочая. Если подходить к HDL как к языку программирования, хорошего результата не будет (даже если что-то и будет работать).

  2. А ещё при использовании DMA возможны проблемы из-за того, что у Cortex-M7 есть кэш -- соответственно, перед запуском передачи по DMA надо убедиться, что данные физически лежат в памяти (а не только в кэше), ну а после завершения приёма -- очистить кэш, чтоб добраться до данных в памяти.

  3. 24 минуты назад, maksimdag0 сказал:

    Но возможность выбора ядра появится только тогда, когда ты скачаешь пакет для своего МК

    Или собственно ARMовский пакет, где только ядра и указаны. Но обычно лучше пакет под свой МК -- хотя б из-за возможности простого просмотра регистров периферии в процессе отладки.

  4. Ну, судя по вопросу, нужно указать компилятору, в каких секциях что размещать -- он и сгенерит соответствующий код и управляющую информацию. Затем можно, например, все объектники собрать компоновщиком в один объектник (кейловский компоновщик такое умеет -- если память не изменяет, partial linking это обзывается), который скармливать при сборке полного приложения у заказчика.

    Как секции указывать, зависит от компилятора, так как стандарт это не регламентирует. В ARM Clang кейловском можно, в GCC можно (насколько помню), в мелкомягком компиляторе можно -- так что и в ИАРовском наверняка сие поддерживается.

  5. Проверил 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 он уже не умничал.

  6. В 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 (одно поле -- указатель на сам заголовок, другое -- на первый элемент очереди). На досуге проверю.

  7. 1 час назад, Arlleex сказал:

    В ARM CLang тоже поддерживаются

    О, спасибо. Похоже, то, что нужно. Жаль, конечно, что это не стандартное средство языка, а расширение компилятора, но жрём, что дают 🙂

    ADD. Правда, ссылка -- не для ARM Clang, а для старого компилятора 5-й версии, но в ARM Clang тоже есть.

    ADD2. Увы, не помогло. Похоже, эти вещи срабатывают лишь для операций, которые, с точки зрения компилятора, имеют побочные эффекты, и не препятствуют ему производить предзагрузку из ячейки, явного обращения на запись к которой нет. Так что только volatile.

  8. 1 час назад, amaora сказал:

    квалификатор restrict в аргументах функции

    Он чисто сишный, вроде как, а в це++ не поддерживается.

    1 час назад, amaora сказал:

    Есть и на уровне компилятора барьеры, любой вызов extern функции

    А в данном случае она inline -- для эффективности.

  9. 1 минуту назад, jcxz сказал:

    В IAR есть атрибут __root:

    __root int x;

    Он указывает компилятору не выбрасывать сам объект (переменную, константу, ...) из программы даже если к нему нет обращений. Но именно только: не выбрасывать сам объект. __root совсем не даёт гарантии от удаления операций работы с переменной, если оптимизатор решил, что скажем x нигде не используется.

    Ну, во-первых, он не решает данную проблему, как понимаете. А во-вторых, он специфичен для конкретного компилятора, что не шибко хорошо.

    1 минуту назад, jcxz сказал:

    Так укажите - внесите их внутрь функции. Кто вам мешает? Либо можно функцию обернуть в макрос, в котором перед и после вызова функции поставить барьеры.

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

  10. 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 сказал:

    Может, Вы о них просто не в курсе?

    Запросто. Це++ необъятен.

  11. 2 часа назад, Arlleex сказал:

    Не понятно, что SII не нравится в создании локальной неквалифицированной версии идентификатора, ведь это:

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

    2 часа назад, Arlleex сказал:

    Там тоже без чтения в локальную копию не обойтись.

    Да, бывают случаи, когда действительно не обойтись. Однако мне не нравится, что нужно постоянно применять костыли просто из-за того, что в языке отсутствуют средства, которые позволили бы объяснить компилятору, что можно делать, а что нельзя. Как человек выше указал, существуют же барьеры памяти -- так почему нельзя указать, что некая функция является барьером для оптимизации?

  12. 6 минут назад, Сергей Борщ сказал:

    Возможно. Заведите временную не-volatile переменную, скопируйте в нее значение указателя и обращайтесь к полям через нее.

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

  13. 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. 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.

  15. Вообще, эта проблема оказалась не единственной. В другом месте у меня простейшее присваивание (внутри функции, являющейся методом класса):

    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 (т.е. волатильными являются не только описатели конечных точек, но и хранящий их адрес регистр, что не соответствует действительности).

    В общем, очень странное поведение...

  16. 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, где всё было корректно (я код выше приводил), и в другом месте -- печатал её значение...

  17. Попробовал на всякий случай переопределить не обычную операцию new (которая, по стандарту, кидает исключение, если не может выделить память), а new(Size, std::nothrow) -- не помогло. Но зато заметил, что при включённой оптимизации компилятор сжирает if  в такой конструкции:

    P = new type;
    
    if ( P == nullptr )...

    А вот в такой -- не сжирает:

    P = new (std::nothrow) type;
    
    if ( P == nullptr )...

    Но здесь к нему претензий нет: по стандарту, первый вариант никогда nullptr не возвращает, а вот второй -- именно его и возвращает, если выделить не может.

  18. Использую 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 попросту не было, и просто возвращает полученный указатель!

    Есть у кого-нибудь какие-нибудь мысли по этому поводу?

    Пы.Сы. Вот потому я и люблю ассемблер: что ты написал, то и будет 🙂

  19. 2 часа назад, tonyk_av сказал:

    То, что на сайте TI фаза жизненного цикла у этих микросхем обозначена как "Active" совершенно не означает, что они есть на складах продавцов и их можно купить. Не так давно понадобилось сделать схему на ТТЛ, так что пришлось изучить доступность микросхем на складах как буржуйских, так и отечественных.

    Ну, Active означает, что ТИ (именно ТИ) готова их поставлять. Понятно, что по 10 штук она не продаёт, но если партия в ннадцать тысяч -- то пожалуйста.

  20. В 21.12.2022 в 09:31, Plain сказал:

    Студент, очевидно, ведь данные ИС сняли с производства лет 30 назад.

    У нас сняты, а вот TI, насколько помню, продолжает выпускать половину, если не 2/3, классических ТТЛ/ТТЛШ микросхем, разработанных ею ещё в 60-70-х годах... Ну и, кроме того, если не понимать, как всё это работает на уровне логики, и учиться ваять сразу на HDL, результаты будут... так себе.

  21. В 10.09.2022 в 23:11, bvn123 сказал:

    Спасибо, почитаю подробнее о volatile.

    Сие ключевое слово указывает компилятору, что значение переменной может измениться в любой момент неочевидным для компилятора образом. Например:

    int  a;
    ...
    if ( a == 0 )
    {
        ...
    }
    ...
    if ( a != 0 )
    {
        ...
    }

    Если переменная a объявлена, как написано, т.е. без volatile, компилятор может загрузить её значение в регистр, а затем использовать для обоих проверок. Если же указать volatile, для каждой из проверок значение будет в обязательном порядке загружаться из памяти. Соответственно, если после первого, но перед вторым if значение изменилось (обработчиком прерывания или другим потоком), то без volatile это изменение может быть замечено, а может остаться незамеченным (в зависимости от того, какой код будет сгенерирован -- а это зависит в т.ч. от уровня оптимизации), а с volatile будет гарантированно замечено.

  22. 16 часов назад, Сергей Борщ сказал:

    Максимальный уровень оптимизации как бы предполагает, что клиент готов отдать все за любой прирост :biggrin:

    Сравнить же всё равно интересно: насколько велик прирост скорости у разных компиляторов и как много места им для этого понадобилось. Да и не всегда "готов отдать всё": скажем, прошивка перестанет влезать во флэш определённого размера, нужно искать что-нибудь побольше -- или несколько пожертвовать скоростью.

×
×
  • Создать...