GCC Compiler Background

The GCC Embedded Arm Compiler is a common compiler used for the 32-bit Arm Cortex-A, Cortex-R and Cortex-M processors.

Choosing the right GCC compiler flags for your scenario is hard, especially when working with constrained device with limited flash availability. It is important to choose the right settings that give you the best optimization for size, without sacrificing performance. Secondly, you want to activate appropriate warnings to surface common coding issues at compile time.

Below are some of the common GCC compiler flags I use in my production environments for optimizing the outputs. Many of these can and should be used for both debug and release targets.

Dead code elimination

One of the most important components is to remove dead code from the final binary. Dead code comes from additional files that are linked in but will never be executed. In most projects, it's often not feasible to track every line of code compiled into your final binary, so this is the most important compiler and link flags.

One way to do this is to remove and files from your projects, or to remove that at the pre-processor stage through the use of #ifdef directives, however this can be time consuming, hard to maintain and prone to error.

The easier way is to tell the compiler to separate each function and data item into their own sections, which then allows the linker to discard sections that aren't used in the final program.

Compiler flags: -ffunction-sections -fdata-sections

Linker flags: -Wl,--gc-sections

Nano specs

Newlib is the underlying C library designed for embedded systems. Is it a collection of different libraries that perform a number of functions including string manipulation and outputting to the console.

The default newlib implementation, specified as nosys.specs, has historically not been that usable for microcontrollers and a new variant called newlib-nano was developed to provide a reduced code size and can be activated with the following compiler flag.

If you decide to use the nano version of newlib, you will be required to add additional low-level functions. An example of this implementation can be found here.

Compiler flags: --specs=nano.specs

Double promotion

Floats and doubles are different. A float is 32-bit and considered “single precision” and is natively supported on a 32-bit processor such as the Cortex-M series. A double, however, is emulated on a 32-bit processor and the resulting code is expensive in both in time and space.

By enabling the double promotion compiler flag, the compiler will generate a warning whenever a float is implicitly promoted to a double and letting you fix the problem.

One of the common times a double is unintentionally used is when defining a floating-point number:

bool float_compare(float value)
{
    return value > 10.0;
}

Compiler flags: -Wdouble-promotion

Size optimization

For embedded targets, size is often the constraining factor. To optimize for size, use the following flag. This will enable all -O2 optimizations except those that results in an increased code size.

Note

This optimization is not recommended for debug builds, as code will be optimized in a way that makes it difficult to step through.

Compiler flags: -Os

Link time optimization is a somewhat controversial feature that optimized across all compiled units at link time.

In the past, this feature has been responsible for large increases in the required stack space due to inlining, as well as impacting compilation time and debuggability.

Note

This optimization is not recommended for debug builds, as code will be optimized in a way that makes it difficult to step through.

Compiler flags: -flto

Linker flags: -ftlo

Summary

Below is a summary of the compiler and linker flags I would typically use in one of my embedded projects. Hopefully the descriptions above provide you with enough context so that you can customize the flags to your own specific project.

Compiler flags: -Os -ffunction-sections -fdata-sections -flto --specs=nano.specs -Wdouble-promotion

Linker flags: -Wl,--gc-section -flto

Next Steps

See the recommended compiler flags above integrated into a CMake toolchain.