STM32 MCU G CC Поведение при компиляции - PullRequest
0 голосов
/ 17 июня 2020

У меня есть некоторое недопонимание относительно поведения компиляции MCU G CC в отношении функции, возвращающей другие вещи, имеющие 32-битное значение.

MCU: STM32 L0 Series (STM32L083)
GCC : gcc version 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] (GNU Tools for Arm Embedded Processors 7-2018-q2-update)

Мой код оптимизирован для размера (с опцией -Os). Насколько я понимаю, это позволит g cc использовать неявный -fshort-enums для упаковки перечислений.

У меня есть два enum var шириной 1 байт:

enum eRadioMode         radio_mode // (@ 0x20003200)
enum eRadioFunction     radio_func // (@ 0x20003201)

И функция:

enum eRadioMode radio_get_mode(enum eRadioFunction _radio_func);

Когда я вызываю эту кучу кода:

radio_mode = radio_get_mode(radio_func);

Она генерирует эту кучу ASM во время компиляции:

; At this point :
;   r4 value is 0x20003201 (Address of radio_func)

7820        ldrb    r0, [r4, #0]            ; GCC treat correctly r4 as a pointer to 1 byte wide var, no problem here
f7ff ffcd   bl  80098a8 <radio_get_mode>    ; Call to radio_get_mode()
4d1e        ldr r5, [pc, #120]              ; r5 is loaded with 0x20003200 (Address of radio_mode)
6028        str r0, [r5, #0]                ; Why GCC use 'str' and not 'strb' at this point ?

Последняя строка здесь - проблема: значение r0, возвращаемое значение radio_get_mode(), сохраняется по адресу, указанному r5, как 32-битное значение. Поскольку radio_func находится на 1 байт после radio_mode, его значение перезаписывается вторым байтом r0 (это всегда 0x00, поскольку enum имеет ширину всего 1 байт).

В качестве моей функции radio_get_mode объявлен как возвращающий 1 байт, почему G CC не использует инструкцию strb, чтобы сохранить этот единственный байт по адресу, указанному r5?

Я пробовал:

  • radio_get_mode() при возврате uint8_t: uint8_t radio_get_mode(enum eRadioFunction _radio_func);
  • Принудительное приведение к uint8_t: radio_mode = (uint8_t)radio_get_mode(radio_func);
  • Передача третьей переменной (но G CC отменить этот бесполезный ход при компиляции - не так уж и глупо):
uint32_t r = radio_get_mode(radio_func);
radio_mode = (uint8_t) r;

Но ни одно из этих решений не работает.

Поскольку сначала требуется оптимизация размера (-Os) зрение, чтобы уменьшить использование ROM (а не ram - в это время моего проекта -) Я обнаружил, что обходной путь g cc option -fno-short-enums позволит компилятору использовать 4 байта по перечислению, отбрасывая, кстати, любую перекрывающуюся память в это случай.

Но, на мой взгляд, это грязный способ скрыть здесь настоящую проблему:

  • Может ли G CC правильно обрабатывать другой размер возврата, кроме 32-битного?
  • Есть правильный способ сделать это?

Заранее спасибо.

EDIT:

  • Я НЕ использовал -f-short-enums в любой момент.
  • Я уверен, что это перечисление не имеет значения больше чем 0xFF
  • Я пытался объявить radio_mode и radio_func как uint8_t (он же unsigned char): проблема та же.
  • При компиляции с -Os, Output.map выглядит следующим образом:

Common symbol       size              file
...
radio_mode          0x1               src/radio/radio.o
radio_func          0x1               src/radio/radio.o
...
...
...
Section         address               label
                0x2000319c                radio_state
                0x20003200                radio_mode
                0x20003201                radio_func
                0x20003202                radio_protocol
...

Вывод файла карты ясно показывает, что radio_mode и radio_func имеют ширину 1 байт и по следующему адресу.

  • При компиляции без -Os, Output.map ясно показывает, что перечисления становятся шириной 4 байта (с дополнением адреса до 4).
  • При компиляции с -Os и -fno-short-enums выполните те же действия, что и без -Os для всех перечислений (вот почему я думаю, что -Os подразумевает неявное -f-short-enums)
  • Я постараюсь предоставить минимально воспроизводимый пример
  • Мой анализ th Проблема в том, что я почти уверен, что это ошибка компилятора. Для меня это явно наложение воспоминаний. Мой вопрос больше о том, что лучше всего сделать, чтобы этого избежать - в соответствии с "передовой практикой".

РЕДАКТИРОВАТЬ 2

Это мой плохо, у меня есть повторный тестер, меняющий всю подпись на uint8_t (он же unsigned char), и он работает хорошо.

@ Питер Кордес, похоже, нашел здесь проблему: при его использовании -Os частично включение -fshort-enums, получение некоторых частей G CC для обработки его как размера 1 и других частей для обработки его как размера 4.

Код ASM с использованием только uint8_t:

    ; Same position than before
    7820        ldrb    r0, [r4, #0]
    f7ff ffcd   bl  80098a8 <radio_get_mode>
    4d1e        ldr r5, [pc, #120]  
    7028        strb    r0, [r5, #0]   ; Yes ! GCC use 'strb' and not 'str' like before !

Чтобы уточнить:

  • Кажется, есть ошибка компилятора при использовании -Os и перечислений. Плохая удача, что два перечисления находятся в последовательных адресах, которые перекрываются.
  • Использование -fno-short-enums в сочетании с -Os кажется хорошим обходным путем IMO, поскольку проблема касается только перечисления, а не всех 1 byte var вообще.

Еще раз спасибо.

1 Ответ

0 голосов
/ 18 июня 2020

ARM-порт abi определяет перечисления none-aebi как тип переменного размера, linux -eabi - как стандартные фиксированные.

Это причина поведения, которое вы наблюдаете. Это не связано с оптимизацией.

В этом примере вы можете увидеть, как это работает. https://godbolt.org/z/-mY_WY

...