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 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 ?
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-enumsat any moment. - I'm sure that these enum has no value greater than 0xFF
- I have tried to declare
radio_modeandradio_funcasuint8_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.o
radio_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
-Osand-fno-short-enums, do the same things that without-Osfor all enums (This is why I guess-Osimplies 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
-Osand enums. This is bad luck that two enum is at consecutive adresses that overlap. - Using
-fno-short-enumsin conjonction with-Osappear to be a good workaround IMO, since the problem is concerning only enum, and not all 1 byte var at all.
Thanks again.
-Osdoes not imply-fshort-enumswill be used. It has to be explicitly given. Is that what you've tried ? - FrankH.enum eRadioFunctiondoesn't have any values > 255 that would stop that enum type from being narrow? Everything you show is consistent withsizeof(enum eRadioMode) == 4,sizeof(enum eRadioFunction) == 1, except for the addresses. Are you really seeing the store toradio_modeoverlap withradio_func? If so, that sounds like a compiler bug. Can you provide a minimal reproducible example of this, for example on godbolt.org? - Peter Cordes-fshort-enumschanges the ABI, it is not part of any optimisation preset and should be used with care. - fuzradio_modethe typeunsigned char? - fuz