简体   繁体   中英

STM32 MCU GCC Compilation behavior

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 returning uint8_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 and radio_func as uint8_t (aka unsigned 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 -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.

ARM port abi defines none-aebi enums to be a variable sized type, linux-eabi to be standards fixed one.

That is the reason the behaviour you observe. It is not related to the optimisation.

In this example you can see how it works. https://godbolt.org/z/-mY_WY

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM