5.13 Optimizations
The optimizations in the MPLAB XC8 compiler can be broadly grouped into C-level optimizations performed on the source code before conversion into assembly and assembly-level optimizations performed on the assembly code generated by the compiler.
The C-level optimizations are performed early during the code generation phase and so have flow-on benefits: performing one optimization might mean that another can then be applied. As these optimizations are applied before the debug information has been produced, they have less impact on source-level debugging of programs.
Some of these optimizations are integral to the code generation process and so cannot be disabled via an option. Suggestions as to how specific optimizations can be defeated are given in the sections below.
If your compiler is unlicensed, some of the optimization levels are disabled (see 4.6.6 Options for Controlling Optimization). Even if they are enabled, optimizations can only be applied if very specific conditions are met. As a result, you might see that some lines of code are optimized, but other similar lines are not.
The optimization level determines the available optimizations, which are listed in the Table 5-13 table.
Level | Optimization sets available |
---|---|
O0 |
|
O1 |
|
O2 |
|
O3
(Licensed only) |
|
Os
(Licensed only) |
|
Assembly-level optimizations are described in 6.2 Assembly-Level Optimizations.
The minimal code generator optimizations consist of the following.
- Whole-program analysis for object allocation into data banks without having to use non-standard keywords or compiler directives.
- Simplification and folding of constant expressions to simplify expressions.
- Expression tree optimizations to ensure efficient assembly generation.
- Propagation of constants is performed where the numerical contents of
a variable can be determined. Variables which are not
volatile
and whose value can be exactly determined are replaced with the numerical value. Uninitialized global variables are assumed to contain zero prior to any assignment to them. - Unreachable code is removed. C Statements that cannot be reached are removed before they generate assembly code. This allows subsequent optimizations to be applied at the C level.
The following is a list of more advanced code generation (C-level) optimizations, which simplify C expressions or code produced from C expressions. These are applied across the entire program, not just on a module-by-module basis.
- Tracking of the current data bank is performed by the compiler as it generates assembly code. This allows the compiler to reduce the number of bank-selection instructions generated.
- Strength reductions and expression transformations are applied to all expression trees before code is generated. This involves replacing expressions with equivalent, but less costly operations.
- Unused variables in a program are removed. This applies to all
variables. Variables removed will not have memory reserved for them, will not appear in
any list or map file, and will not be present in debug information (will not be
observable in the debugger). A warning is produced if an unused variable is encountered.
Global objects qualified
volatile
will never be removed (see 5.3.8.2 Volatile Type Qualifier). Taking the address of a variable or referencing its assembly-domain symbol in hand-written assembly code also constitutes use of the variable. - Redundant assignments to variables not subsequently used are removed,
unless the variable is
volatile
. The assignment statement is completely removed, as if it was never present in the original source code. No code will be produced for it and you will not be able to set a breakpoint on that line in the debugger. - Unused functions in a program are removed. A function is considered
unused if it is not called, directly or indirectly, nor has had its address taken. The
entire function is removed, as if it was never present in the original source code. No
code will be produced for it and you will not be able to set a breakpoint on any line in
the function in the debugger. Referencing a function’s assembly-domain symbol in a
separate hand-written assembly module will prevent it being removed. The assembly code
need only use the symbol in the
GLOBAL
directive. - Unused return expressions in a function are removed. The return value
is considered unused if the result of all calls to that function discard the return
value. The code associated with calculation of the return value will be removed and the
function will be encoded as if its return type was
void
. - Variables assigned a value before being read are not cleared or
initialized by the runtime startup code. Only non-auto variables are considered and if
they are assigned a value before other code can read their value, they are treated as
being
__persistent
(see 5.3.9.5 Persistent Type Qualifier. All__persistent
objects are not cleared by the runtime startup code, so this optimization will speed execution of the program startup. - Pointer sizes are optimized to suit the target objects they can access. The compiler tracks all assignments to pointer variables and keeps a list of targets each pointer can access. As the memory space of each target is known, the size and dereference method used can be customized for each pointer.
- Dereferencing pointers with only target can be replaced with direct access of the target object. This applies to data and function pointers.
- Objects qualified
const
are considered for placement into data memory if any such memory is otherwise unused by the program.
MPLAB X IDE or other IDEs can indicate incorrect values when watching variables if optimizations hold a variable in a register. Try to use the ELF/DWARF debug file format to minimize such occurrences. Check the assembly list file to see if registers are used in the routine that is being debugged.