1

I have an optimisation issue with gcc running on Eclipse. I have a program which runs well with -O0 optimization level, but I need to turn ON other levels of optimization because my software uses too much flash memory space. But when I use -O1 optimization level (for instance) my code doesn't work anymore and I need to do strange modifications as described below with the UART peripheral managing functions.

Code running well with -O0

void function(...)
{
   // Send char one by one on UART peripheral
   UART_put_char(...)

   // Wait end of transmission on UART
   while(end_of_transmission_flag == flase);
}

void UART_TxCompleteCallback()
{
   // Change flag state
   end_of_transmission_flag = true;
}

Code with modification for -O1

void function(...)
{
   // Send char one by one on UART peripheral
   UART_put_char(...)

   // Wait end of transmission on UART
   while(end_of_transmission_flag == flase)
   {
      Dummy = Dummy == 0 ? 1 : 0;
   }
}


void UART_TxCompleteCallback()
{
   // Change flag state
   end_of_transmission_flag = true;
}

I would like to know why gcc is forcing me to do this kind of modifications, do I need to change my "way of think" about the design of my code. Or is there an explanation with this behavior ?

Thanks for your advices.

LOSnel
  • 161
  • 1
  • 1
  • 8
  • 7
    Is `end_of_transmission_flag` declared as `volatile`? – user694733 Jun 05 '17 at 06:57
  • 3
    ^ it should also be declared atomic (or, prior to C11, you'd have to rely on compiler extension to guarantee that it is read and written in an atomic manner) – M.M Jun 05 '17 at 07:02
  • @M.M Atomic doesn't matter unless there's threads involved here. Volatile is enough for a hardware driver. – Zan Lynx Jun 05 '17 at 07:55
  • @ZanLynx no it's not, at least not generally. On an AVR, for example, any 16-bit assignment is non-atomic and would need to be protected from "modification halfway into the assignment" by an ISR by disabling interrupts for the time of the assignment. – tofro Jun 05 '17 at 12:25
  • http://www.embedded.com/electronics-blogs/beginner-s-corner/4023801/Introduction-to-the-Volatile-Keyword – Clifford Jun 05 '17 at 16:06
  • @tofro: That is all stuff specific to hardware platforms. I would never trust that to the C compiler's libraries. To write drivers, you need to know those details and implement them yourself by writing functions that disable the interrupts. Using things like atomic libraries you *might* get the interrupt disable correct. But you'll end up with stupid things like "disable, write one, enable, disable, write one, enable" repeated over and over when you actually want a single critical section with two writes. – Zan Lynx Jun 05 '17 at 19:10

2 Answers2

3

As said in the comments, you should declare end_of_transmission_flag as volatile to make sure the compiler does not remove the loop during its optimization. By declaring the variable volatile, the compiler knows the variable will be updated somewhere else.

But to debug your issue you should not change all the files with '-O1' in once, but you should try to move some file in -O1 and keep some file in -O0.

You can also disable GCC optimization locally:

#pragma GCC push_options
#pragma GCC optimize ("O0")

// your code

#pragma GCC pop_options

Be aware in embedded you must respect the order of the operation when you access hardware register. Compilation optimization sometimes/often change the order. Using volatile with the address of the registers ensures the order of the operation.

OlivierM
  • 2,820
  • 24
  • 41
  • _"Using volatile with the address of the registers ensures the order of the operation."_ I wouldn't go that far. It only guarantees that the memory will be explicitly read rather then reusing some previously read value. https://stackoverflow.com/questions/1787450/how-do-i-understand-read-memory-barriers-and-volatile – Clifford Jun 05 '17 at 16:13
  • You are right. As mentioned by your link memory/instruction barriers are the answers to the ordering requirements (in case of ARM architecture) – OlivierM Jun 05 '17 at 20:24
  • And what about -Os optimization ? It seems even a volatile isn't enougth to keep the variable. – LOSnel Jun 08 '17 at 15:18
  • Why do you think `volatile` will disappear with `-Os`? – OlivierM Jun 08 '17 at 16:18
-1

With optimization turned on, the compiler tries to save as much as it can on redundant code. This line:

while(end_of_transmission_flag == false);

continuously checks a variable which is never modified (from the point of view of the compiler). Moreover, the line does nothing (again from the point of view of the compiler). In reality the line does something: it waits until the flag changes state. But the compiler is not smart enough to understand this. So you have to help it by declaring the flag variable as volatile. Doing this, you warn the compiler that the variable can change state behind the scene (i.e., in interrupt). The compiler now knows that it can not rely on the fact that the variable seems to be never modified - it can not "cache" its value to produce faster code.

Please note that, even so, the whole line still does nothing (there is no code to execute inside the loop); but normally, in this situation, the compiler understands that the loop must be executed anyway.

Your trick "Dummy = Dummy == 0 ? 1 : 0;" seems to have some effect on the compiler, and I can not explain it to myself. But, as you already thought, it seems really strange. The trick addresses the problem of a statement which does nothing, forcing the compiler to generate code inside the loop. But this is only half of the problem and, anyway, a very smart compiler could think "why should I compile code to assign a value to a variable which is never used? When such things happen, the compiler often warns you with something like "Warning: value assigned to xxxxx is never used".

To conclude, declare end_of_transmission_flag as volatile.

Just a thought aside: many programming languages are not born to cope well with hardware (port I/O, interrupts, DMA and so on), and C is no exception: it forces the programmer to strange declarations, pragmas, compilation flags and so on. On the other hand, a line like "while(end_of_transmission_flag == false);" is kind of ambiguous; the real line should read something like "wait_until_true(end_of_transmission_flag);". Things like this could be offered by the language, or by the environment (via macros, libraries, hardware support or whatever).

  • 2
    `while (X == false);` means "wait until true(X)" – M.M Jun 05 '17 at 08:16
  • Cool. I think any C programmer would indeed read it as such without skipping a beat though, I don't really follow your complaint that the language should offer some alternative syntax for the same effect – M.M Jun 05 '17 at 08:26
  • @M.M Well, I would not write `while (X == false);` in a multiprocess environment: there are semaphores, messages, signals and so on. In many situations the language seems simply a syntactical glue to put together system calls. This is not good. If I want to wait for a variable to change state, I would have language support for it! In fact, there are modern languages which have it. The question in this page is a good example of the problems arising when a language/compiler is not tailored well for a problem domain. – linuxfan says Reinstate Monica Jun 05 '17 at 09:09
  • There is language support for synchronization in the latest C Standard (2011) – M.M Jun 05 '17 at 09:16
  • @M.M By definition, optimization should not change the behavior of a program. Instead often it does, and this happens because the language is too poor to express certain things. But this is a long and complicated matter. – linuxfan says Reinstate Monica Jun 05 '17 at 09:16
  • 2
    Yes it is long and complicated. C11 is sufficiently expressive IMO, if you write code that's well defined according to the language spec then the behaviour is not unpredictable – M.M Jun 05 '17 at 09:27