I have some misunderstanding about MCU GCC compilation behavior regarding function that return other things that 32bits value.
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)
My code is optimized for size (with option -Os
). In my understanding, this will allow the gcc to use implicit -fshort-enums
in order to pack enums.
I have two enum var, 1-byte wide :
enum eRadioMode radio_mode // (@ 0x20003200)enum eRadioFunction radio_func // (@ 0x20003201)
And a function :
enum eRadioMode radio_get_mode(enum eRadioFunction _radio_func);
When i call this bunch of code :
radio_mode = radio_get_mode(radio_func);
It will produce this bunch of ASM at compile time:
; 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 heref7ff 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 ?
The last line here is the problem : The value of r0
, return value of radio_get_mode()
, is stored into address pointed by r5
, as a 32bit value.Since radio_func
is 1 byte after radio_mode
, its value is overwritten by the second byte of r0
(that is always 0x00 since enum is only 1 byte wide).
As my function radio_get_mode
is declared as returning 1 single byte, why GCC doesn't use instruction strb
in order to save this single byte into the address pointed by r5
?
I have tried :
radio_get_mode()
as returninguint8_t
:uint8_t radio_get_mode(enum eRadioFunction _radio_func);
- Forcing cast to
uint8_t
:radio_mode = (uint8_t)radio_get_mode(radio_func);
- Passing by a third var (but GCC cancel that useless move at compile - not so dumb) :
uint32_t r = radio_get_mode(radio_func);radio_mode = (uint8_t) r;
But none of these solutions work.
Since the size optimization (-Os) is needed in first sight to reduce rom usage (and not ram - at this time of my project -) I found that the workaround gcc option -fno-short-enums
will let the compiler to use 4 bytes by enum, discarding by the way any overlapping memory in this case.
But, in my opinion, this is a dirty way to hide a real problem here :
- Is GCC not able to correctly handle other return size than 32bit ?
- There is a correct way to do that ?
Thanks in advance.
EDIT :
- I did NOT use
-f-short-enums
at any moment. - I'm sure that these enum has no value greater than 0xFF
- I have tried to declare
radio_mode
andradio_func
asuint8_t
(akaunsigned char
) : The problem is the same. - When compiled with
-Os
, Output.map is as follow :
Common symbol size file...radio_mode 0x1 src/radio/radio.oradio_func 0x1 src/radio/radio.o.........Section address label 0x2000319c radio_state 0x20003200 radio_mode 0x20003201 radio_func 0x20003202 radio_protocol...
The output of the mapfile show clearly that radio_mode
and radio_func
is 1 byte wide and at following address.
- When compiled without
-Os
, Output.map show clearly that enums become 4 byte wide (with address padded to 4). - When compiled with
-Os
and-fno-short-enums
, do the same things that without-Os
for all enums (This is why I guess-Os
implies implicit-f-short-enums
) - I will try to provide minimal reproducible example
- My analysis of the problem is that I'm pretty sure it is a compiler bug. For me, this is clearly a memory overlapping. My question is more about the best things to do in order to avoid this - in the "best practice" way.
EDIT 2
It is my bad, I have re-tester changing all signature to uint8_t
(aka unsigned char
) and it work well.
@Peter Cordes seems to found the problem here : When using it, -Os
is partly enabling -fshort-enums
, getting some parts of GCC to treat it as size 1 and other parts to treat it as size 4.
ASM code using only uint8_t
is :
; 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 !
To clarify :
- It seems to have compiler bug when using
-Os
and enums. This is bad luck that two enum is at consecutive adresses that overlap. - Using
-fno-short-enums
in conjonction with-Os
appear to be a good workaround IMO, since the problem is concerning only enum, and not all 1 byte var at all.
Thanks again.