Compilers have a certain degree of code optimisation they perform on the intermediate representation (IR). They aim to correctly improve the performance of the code, reduce the number of instructions required in the executable, and better target the architecture compiled to. Optimisation is the most challenging part of modern compiler design, compared to designing front-ends and code generators, which rely on well-understood techniques.

For programmers, we can specify the degree of optimisation with the -O flag (supported by GCC and LLVM). -O1 and -O2 optimise based on conservative optimisations. -O3 directs the compiler to be aggressive about optimisation, which may affect the proper functioning of the program.1

Types of optimisations

Optimisations include a wide variety of changes:

  • Low-hanging fruit
    • Eliminating dead code and unreachable branches.
    • Pre-computing simple arithmetic.
  • Function inlining
  • More difficult changes, like instruction re-ordering.

Structure

Optimisers have two main analysis functions:

  • Control flow analysis
  • Data flow analysis

And it then takes multiple optimising passes, many of which do separate small optimisations. For example, GCC has about 100 such passes. Pass flow is not necessarily linear — sometimes it may rely on passes already done.

Footnotes

  1. From IBM’s documentation of AIX.