19. The optimizer

The compiler applies various optimizations to reduce code size and increase performance. This chapter describes how to control optimizations and highlights useful aspects, rather than detailing every optimization applied.

19.1. Overview

By default, the compiler performs basic rewrites, optimizes code selection for efficient instruction set utilization, and makes good use of the CPU’s internal registers.

The resulting application is generally well-suited for debugging, as many optimization passes are disabled by default.

General settings

Optimizations can be broadly enabled using the following command-line options.

-O1

Enable some optimization passes.

--O2

Enable all provided optimization passes.

--speed

Allow application size to grow in order to make the application run faster.

--space

Tune optimizations more towards making the application small. This is the default.

19.2. Function inlining

Function inlining expands a function’s body at its call site, typically increasing performance but potentially increasing code size.

Function inlining may be beneficial on code size for small functions as the inserted function body becomes tailored to the particular code surrounding it. Normally when making a function call there is a calling convention that has to be followed which puts restrictions on how registers are used.

Inlining is enabled by specifying at least -O1.

The inline keyword

The inline keyword in C is sometimes misunderstood. Its purpose is to expose a function body to many compilation units and hint that the function may be inline expanded instead of making an ordinary call to it. There is no guarantee that a function marked as inline is actually inlined, this decision is taken by the compiler. Futhermore, the compiler may also choose to inline functions that are not marked as inline.

Using the inline keyword

A function marked as inline is normally placed in a header file to allow it to be used by multiple compilation units:

inline max (int a, int b) {
  if (a > b) {
    return a;
  } else {
    return b;
  }
}

An inline function can also be marked as static inline in a similar way:

static inline max (int a, int b) {
  if (a > b) {
    return a;
  } else {
    return b;
  }
}

You can use functions marked as inline or static inline in the same way as any other function. They can be called and in that case they may be subject to inline expansion and you can store it as a function pointer and make calls to it using that function pointer.

There are two cases when a function marked as inline or static inline requires a separate copy of the function. First, the compiler may choose to make an ordinary function call to it. Second, a function marked as inline may be stored in a function pointer.

Variants of inline functions

As hinted in the previous section, there are two kinds of inline functions, plain inline and static inline. As long as the compiler chose to do inline expansion these two variants work identically. They differ when the compiler choose to not inline expand it and make an ordinary function call instead.

inline

A function is marked as inline does not cause the compiler to generate a separate definition of the function. If such separate function is needed, it is assumed that one compilation unit provides the definition using the extern keyword:

extern inline max (int a, int b);

This should be placed in an ordinary C source file, not a header file.

In most cases you will need to tell the compiler to actually generate such separate function using an extern declaration somewhere. If you do not do this and the function is required by the application, a linker error will result that tells you that the function is not defined.

The main benefit of using inline rather than static inline is that you are ensured that there will be at most one separate definition of the function.

Note

One subtle benefit which inline has over static inline is that if you really must expand the function inline, then you can omit the extern declaration of the function. The linker will then let you know that the required inline expansion did not happen. In this case you most likely want to specify the command-line option --always-inline to ensure that the inliner is always used.

static inline

The compiler will generate a static standalone version of a static inline function whenever there is a need for it. Being static it is local to the compilation unit with no external linkage.

This means that if you make use of the same static inline function in several compilation units, there may be multiple copies of the same function.

Note

If you take the address of the same static inline function in different translation units then the result will not compare equal.

Note

While it is slightly easier to use a static inline function as you do not need to provide a single extern declaration, it comes with the cost of potentially having duplicate code in the final application.

Controlling inline expansion

The compiler will apply it own decisions on what to inline and when to use normal function calls. Small simple functions and static functions with only a single use are prime candidates for inline expansion.

There are a couple of command-line options that allow for tuning which functions are considered for inlining:

--always-inline

Always enable the inliner, no matter which optimization level is used.

This is useful if you have functions that you always want to be inlined.

--no-inline

Do not perform any inlining at all.

--only-marked-as-inline

Only consider functions that have the inline keyword. This prevents functions not marked as inline from being considered for inlining.

--strong-inline

Try to obey the inline keyword. This relaxes some of the requirements, such as that the function must be small enough. This is a strong hint that we really want functions marked for inlining to be inlined. The compiler may still refuse to inline a function.

Note

There are a number of reasons why the compiler may refuse to inline a function, e.g. used recursively, there is no prototype, it uses a variable argument list or it uses variable length arrays.

--inline-on-matching-custom-text-section

Functions being placed in a custom section (using #pragma clang section) are only allowed to be inlined into a function that belongs to the same custom section.

This is useful in situations when the memory system is being manipulated and functions are placed in a custom section to control placement to obey the memory setup. Especially if you have several such configurations you may want to prevent functions from being inlined cross custom sections as they may make assumptions on which section (memory placement) they are being executed from.

19.3. Cross call

This optimization pass can sometimes result in quite significant savings on the application size. It examines the almost final program looking for instruction sequences that are identical. Such sequences are lifted out to separate subroutines which are called instead. This takes in account the size of the code sequences as well as how many times they appear.

Controlling cross call

Cross call is enabled by specifying at least -O2. Even though cross call is focusing on the application size, it is enabled also when the --speed command-line option is specified, as the savings on code space can sometimes be quite significant.

--no-cross-call

This option disables the cross call optimization. This can be used with -O2 when you want to avoid the subroutine call overhead that cross call will introduce.

--no-interprocedural-cross-jump

This option disables the cross jump between functions optimization. This can be used if you are manually mapping in functions by some bank mechanism in a way so that not all functions in the same compilation are visible at the same time.