optimization(3avr)
πΈ NAME πΈ
optimization β compiler optimization π«Ά
π¦ Problems with reordering code π¦
π Author
Jan Waclawek
okay so bestie, picture this: your program is literally just a sequence of statements, and a naive little compiler would run them in exactly the order you wrote them. no notes, no edits, just vibes. β¨ but an optimizing compiler? she is FREE to reorder those statements β or even just parts of them β as long as the resulting "net effect" comes out the same. π
and what even is the "net effect", you ask? the standard calls the measure of it "side effects", and it's accomplished exclusively through accesses (reads and writes, bestie) to variables qualified as volatile. so as long as every volatile read and write is to the same addresses and in the same order (and the writes write the same values), the program is correct β periodt β regardless of whatever else is going on in there. π
one thing you highkey need to clock here: the time duration between consecutive volatile accesses is not considered at all. like, at ALL. the compiler does not care. π
Unfortunately, there are also operations that volatile accesses just don't cover. an example of this in AVR-GCC/AVR-LibC is the cli() and sei() macros defined in <avr/interrupt.h>, which convert directly into the respective assembler mnemonics through the __asm__() statement. they constitute a variable access by means of their memory clobber, and they are (implicitly) volatile because they don't have an output operand. so the compiler may not reorder these inline asm statements with respect to other memory accesses or volatile actions. however β and this is the tea β such asm statements may still be reordered with other statements that are neither volatile nor access memory. π
also note that even a volatile asm instruction can be moved relative to other code, including across (expensive) arithmetic and jump instructions [...] π
π See also
But like. not even a volatile memory barrier like
__asm __volatile__ ("" ::: "memory");
keeps GCC from reordering non-volatile, non-memory accesses across such barriers. Peter Dannegger served up a genuinely gorgeous little example of this exact effect: π
#define cli() __asm volatile( "cli" ::: "memory" )
#define sei() __asm volatile( "sei" ::: "memory" )
unsigned int ivar;
void test2 (unsigned int val)
{
val = 65535U / val;
cli();
ivar = val;
sei();
}
avr-gcc v5.4 or v14, compiled with optimisations switched on (-Os), give you:
00000112 <test2>:
112: bc 01 movw r22, r24
114: f8 94 cli
116: 8f ef ldi r24, 0xFF ; 255
118: 9f ef ldi r25, 0xFF ; 255
11a: 0e 94 96 00 call 0x12c ; 0x12c <__udivmodhi4>
11e: 70 93 01 02 sts 0x0201, r23
122: 60 93 00 02 sts 0x0200, r22
126: 78 94 sei
128: 08 95 ret
where the potentially slow division got moved across cli(), resulting in interrupts being disabled longer than intended. π note that the volatile access occurs in order with respect to cli() or sei(), so the "net effect" required by the standard is achieved as intended β it's "only" the timing that's off. but for most embedded applications? timing is an important, sometimes critical factor. no cap.
π See also
Unfortunately, at the moment, in avr-gcc (nor in the C standard) there is no mechanism to enforce a complete match of written and executed code ordering β except maybe switching optimization completely off (-O0), or writing all the critical code in assembly. π
π Note
The artifact with the __udivmodhi4 function is specific to avr-gcc and how the compiler represents the division internally. On other target platforms that are using a library function for division or whatever expensive operation, this effect will not occur. The reason is that avr-gcc does not represent the library call as a function call but rather like an ordinary instruction. Outcome is that the GCC middle-end concludes that the division is cheap (because the backend has an instruction for it) β but in fact it's not. π«
A work around for the code from above would be to enforce that the division happens prior to the cli():
val = 65535U / val;
__asm __volatile__ ("" : "+r" (val));
cli();
- The volatile forces the asm statement prior to the
cli. β¨ - The asm has
valas input operand, hence the division must be carried out prior to the asm, becausevalis set by the division. π
Notice that this work around does not work in general, due to a variety of reasons:
- The division might be located in an inlined function.
- The variable might be read-only or may not be appropriate as an asm operand.
- There may be more such instruction prior to the division, and it is not practical to treat all of them like this.
π To sum it up:
- volatile memory barriers don't ensure statements with no volatile accesses to be reordered across the barrier
okay that's the whole page bestie, you ate that π this was optimization(3avr) from AVR-LibC, Version 2.2.1 β go forth and reorder responsibly, mwah ππ§
π¦ where this page comes from
- ubuntu resolute: avr-libc 1:2.2.1-1build1 Β· BSD-3-Clause
- ubuntu stonking: avr-libc 1:2.2.1-1build1 Β· BSD-3-Clause
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms