Why did my project stop working after updating the compiler ?

Q: Why doesn't my project work with the new version of the compiler, while the same sources on the same board worked fine with the previous version of the compiler? Is this a bug in the compiler?

A:  Normally this is due to a coding mistake in the project source or configuration that had no impact with an older version of the compiler, but that has some impact with the new version.

This situation is very rarely due to a bug in the compiler. GCC for ARM compiler (and linker etc.) is intensively tested before each version is released, so compiler bugs are very rare, and even when this happens, there are so many users that the bug is usually detected and corrected very quickly. The chances of you finding one is very small. In over 5 years we have seen it once, but only once, and very few people were impacted.

However, changing the compiler is never completely transparent for a developper, and there are situations when it will require some work. Most compiler updates include new optimizations.
As a consequence, it is quite frequent that coding mistakes that go unnoticed with one version of the compiler make the project fail with another version.
Compiler changes can make your project reach a limit in code size, stack usage (in speed opti mode, mostly) and timing constraints (in size opti mode, mostly).

Below is a non-exhaustive list of things to keep in mind, and ways to detect and solve related problems...
The most frequent (in our experience) sources of problems are listed first.

Optimization of access to non-volatile variables.

This is by far the most frequent source of problems when upgrading the compiler.

Older versions of the GCC for ARM compiler did not really optimize accesses to non-volatile variables, but new versions of the compiler do.
A lot of old source code uses variables as if they were volatile, but did not put the "volatile" keyword in the declaration of the variable.
This was OK with old compilers because they didn't optimize these accesses, or very little, so the code "worked".
But the same code on the same board with the new compiler will not behave the same way as it did with the old compiler.

These problems can be solved by declaring the concerned variables as volatile.

First make sure you know precisely what volatile means. wink
(See the C standard definitions)

Here are typical examples of such problems...

*** Counting delay

Consider this function code:

void delay(unsigned long count)

With an older compiler that does not optimize accesses to non-volatile variables, the execution time of this function will vary depending on the value of parameter count.
This is a "classic" way to perform a delay, but it is completely wrong: The C standard allows the compiler to optimize it so that it will only take a few cycles to execute, if any, regardless of the value of the parameter.
And nowadays most ARM compilers do it, and GCC does it.

A correct way (well, a "less bad" way) to write it would be this:

void delay(unsigned long count)
volatile unsigned long mycount=count;

Here, the "volatile" keyword forbids the compiler to optimize accesses to mycount variable, ensuring that the execution time of the function is indeed roughly proportional to the value of the parameter.

Note: In fact this code is wrong too, but we'll see why later. wink But it is not as bad as the first version. smile

*** Shared variables

Consider this code:

//declare a variable for communicating data between an ISR and the main program flow
unsigned char mysharedvar=0;
int main ()
//start timer which will trigger a call to my_ISR() after some time
//wait for timer to change value of variable
//turn LED ON
//the interrupt mechanism will call this function, asynchronously from the main program flow,
// some time after the timer has been configured by Configure_Timer()
void my_ISR()

With a compiler that does not optimize accesses to non-volatile variables, the execution of this code will turn the LED ON after some time.

But this code is wrong because the C standard allows the compiler to optimize the "while(mysharedvar==0)" so that it will become an "if", if it is kept at all.
And again the old GCC for ARM did not perform this optimization, while the new one does.

Declaring mysharedvar as volatile is the normal and best way to solve this:

volatile unsigned char mysharedvar=0;
//the rest does not need to change ;)

Note: Keep in mind that most RTOSes switch between tasks asynchronously, using interrupts. So when working with an RTOS, the volatile keyword must be applied to all variables (and tables and such) that are potentially accessed by several OS tasks. In fact these items should be protected using mutexes, semaphores or other mechanisms, and then in some of these cases the volatile keyword might be omitted, but not in all cases. So unless you are really sure, always use "volatile" on shared data.

Other considerations... to be developed later: Execution time, Code Size, Stack usage, Undead code, Inlining, Corrections, ...