2

I am compiling some code that works without optimisation but breaks with optimisations enabled. I suspect certain critical sections of the code of being optimised out, resulting in the logic breaking.

I want to do something like:

code...

#disable opt

more code...

#enable opt

Even better if I can set the level of optimisation for that section (like O0, O1...)

For those suggesting it is the code:

The section of the code being deleted is (checked by disassembling the object file):

void wait(uint32_t time)
{
  while (time > 0) {
    time--;
  }
}

I seriously doubt there is something wrong with that code

Makogan
  • 8,208
  • 7
  • 44
  • 112
  • 3
    Is this for debugging purposes, or to "fix" the bug? I hope you're not trying to fix your program with this idea. – John Kugelman May 27 '17 at 03:23
  • Well, it's for an embedded system, so the likelihood of hardware opperations being considered pointless is high, and even the use of volatile is not helping, so I was gonna use it to fix it :p – Makogan May 27 '17 at 03:44
  • Note that modern compilers aggressively optimize code that invokes undefined behaviour — they're allowed to because the behaviour is undefined. If you do find things going wrong under optimization, take a very hard look at the code; check for undefined behaviour. – Jonathan Leffler May 27 '17 at 06:17
  • the code that is disappearing is: while(var > 0) var-- and it is not even an infinite loop – Makogan May 27 '17 at 06:20
  • Using a spin like your `while` loop is asking for trouble on an embedded system. You shouldn't trust the embedded hardware to give you cycle accurate delays. You should use standard library functions intended for small delays. I don't know what timing you need, but `usleep()` goes down to microsecond delays. If the delay is a long delay (like a millisecond), you should be using interrupts or your RTOS sleep functions. – Mark Lakata Jan 25 '21 at 19:05
  • If the intent of that code is to cause CPU cycles to be spent, then *the code is wrong indeed*: A standard-compliant compiler is allowed to optimize the function away as it has no observable side-effects according to the C-standard. In fact, the C virtual machine does not speak about CPU cycles or timing in general at all. Thus, there is *no standard-compliant way* to write C-code that causes CPU cycles to be spent deterministically. Using an OS-specific function is the only option, as suggested by @MarkLakata. – burnpanck Jul 18 '23 at 08:01

3 Answers3

7

If optimization causes your program to crash, then you have a bug and should fix it. Hiding the problem by not optimizing this portion of code is poor practice that will leave your code fragile, and its like leaving a landmine for the next developer who supports your code. Worse, by ignoring it, you will not learn how to debug these problems.

Some Possible Root Causes:

Hardware Accesses being optimized out: Use Volatile It is unlikely that critical code is being optimized out, although if you are touching hardware registers then you should add the volatile attribute to force the compiler to access those registers regardless of the optimization settings.

Race Condition: Use a Mutex or Semaphore to control access to shared data It is more likely that you have a race condition that is timing specific, and the optimization causes this timing condition to show itself. That is a good thing, because it means you can fix it. Do you have multiple threads or processes that access the same hardware or shared data? You might need to add a mutex or semaphore to control access to avoid timing problems.

Heisenbug: This is when the behavior of code changes based on whether or not debug statements are added, or whether the code is optimized or not. There is a nice example here where the optimized code does floating point comparisons in registers in high precision, but when printf's are added, then the values are stored as doubles and compared with less precision. This resulted in the code failing one way, but not the other. Perhaps that will give you some ideas.

Timing Loop gets Optimized Out: Creating a wait function that works by creating a timing loop that increments a local variable in order to add a delay is not good programming style. Such loops can be completely optimized out based on the compiler and optimization settings. In addition, the amount of delay will change if you move to a different processor. Delay functions should work based on CPU ticks or real-time, which will not get optimized out. Have the delay function use the CPU clock, or a real time clock, or call a standard function such as nanosleep() or use a select with a timeout. Note that if you are using a CPU tick, be sure to comment the function well and highlight that the implementation needs to be target specific.

Bottom line: As others have suggested, place the suspect code in a separate file and compile that single source file without optimization. Test it to ensure it works, then migrate half the code back into the original, and retest with half the code optimized and half not, to determine where your bug is. Once you know which half has the Heisenbug, use divide and conquer to repeat the process until you identify the smallest portion of code that fails when optimized.

If you can find the bug at that point, great. Otherwise post that fragment here so we can help debug it. Provide the compiler optmization flags used to cause it to fail when optimized.

ScottK
  • 1,526
  • 1
  • 16
  • 23
  • Nope critical code is defnetely being optimised out. I have a small wait funtion (a loop that only increments a variable and nothing else) that is disappearing on the binaries – Makogan May 27 '17 at 05:46
  • In other words I know exactly what the bug is, the compiler is ommiting an entire function – Makogan May 27 '17 at 05:47
  • 2
    @Makogan: Since the code doesn't use the incremented variable, it is perfectly permissible for the compiler to omit the code. You have to disguise what you're doing a lot better to avoid the compiler optimizing away a function that simply doesn't have any detectable side effects. For example, you might need to assign the incremented variable to a global variable — or otherwise prevent the compiler from eliminating the code. – Jonathan Leffler May 27 '17 at 06:22
  • I do know that compilers try their best at optimizing code, but in this case it's biting me in the ass, that's why I need to tell the compiler to leave my code alone – Makogan May 27 '17 at 06:25
  • 1
    @Makogan: Creating a wait function that works by incrementing a local variable in order to add a delay is not good programming style. As Jonathan points out, it can be completely optimized out, and the delay is also likely to change if you move to a different processor. Your delay function should work based on time, which will not get optimized out. Have it use the CPU clock, or a real time clock, or call a standard function such as nanosleep() or use a select with a timeout. See https://stackoverflow.com/a/30969653/6693299 – ScottK May 28 '17 at 14:15
  • 1
    @Makogan You misunderstand the definition of the language & the contract with the compiler, and you are ignoring/dismissing what you are being told and what is the answer to your problem. Your code asks for the function to do nothing. Period. You have some wrong notion that your program asks for certain steps to be performed under some kind of unjustified idiosyncratic execution model. There is an execution model used in the definition/contract, and it is "the C++ abstract machine", and that abstraction does not say that it takes any time to execute that loop. See also re 'the as-if rule'. – philipxy Dec 23 '17 at 02:52
3

You can poke around the GCC documentation. For example:

  • Function specific option pragmas

    #pragma GCC optimize ("string"...)

    This pragma allows you to set global optimization options for functions defined later in the source file. One or more strings can be specified. Each function that is defined after this point is as if attribute((optimize("STRING"))) was specified for that function. The parenthesis around the options is optional. See Function Attributes, for more information about the optimize attribute and the attribute syntax.

    See also the pragmas for push_options, pop_options and reset_options.

  • Common function attributes

    optimize The optimize attribute is used to specify that a function is to be compiled with different optimization options than specified on the command line. Arguments can either be numbers or strings. Numbers are assumed to be an optimization level. Strings that begin with O are assumed to be an optimization option, while other options are assumed to be used with a -f prefix. You can also use the '#pragma GCC optimize' pragma to set the optimization options that affect more than one function. See Function Specific Option Pragmas, for details about the '#pragma GCC optimize' pragma.

    This attribute should be used for debugging purposes only. It is not suitable in production code.

  • Optimize options

    This page lists a lot of optimization options that can be used with the -f option on the command line, and hence with the optimize attribute and/or pragma.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
2

The best thing you can do is to move the code you do not want optimized into a separate source file. Compile that without optimization, and link it against the rest of your code.

With GCC you can also declare a function with __attribute__((optimize("O0")) to inhibit optimization.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436