Fundamentals 21 min read

Mastering LTO in Keil5: Shrink Code Size and Boost Performance

This in‑depth guide explains how to enable and fine‑tune Link‑Time Optimization (LTO) in Keil MDK5 using ArmClang, showing real‑world examples, benchmark data and step‑by‑step instructions that dramatically reduce flash footprint and improve execution speed for embedded systems.

IoT Full-Stack Technology
IoT Full-Stack Technology
IoT Full-Stack Technology
Mastering LTO in Keil5: Shrink Code Size and Boost Performance

Why LTO matters for modern embedded projects

Smart‑home devices are becoming increasingly complex, and maintaining stable wireless connections on the MT7697 chip requires more than just good hardware. The hidden performance booster is the compiler’s Link‑Time Optimization (LTO) feature, which can dramatically reduce code size and improve execution efficiency.

From local to global: how LTO reshapes optimization

In a traditional build each source file is compiled to a .o file, limiting optimizations to the file scope. For example, an inline function defined in file1.c cannot be inlined into file2.c because the linker sees no intermediate representation (IR) to connect them:

// file1.c
static inline int compute(int a) { return a * 2 + 1; }

// file2.c
int api_call(int x) { return compute(x); } // expected inline fails

LTO preserves the LLVM IR (usually in a .llvmbc section) inside each object file. During the link stage the linker loads all IR, analyses the whole program and can perform cross‑file inlining, dead‑code elimination and other global optimisations.

Real‑world impact on flash and speed

Measurements on a Cortex‑M4 project show that enabling LTO reduces the .text section by 15‑30 % on average and up to 48 % in extreme cases. The smaller code also improves instruction‑cache hit rates, yielding faster overall execution.

Step‑by‑step guide to activate LTO in Keil MDK5

1. Use the correct compiler : ARM Compiler 5 (ARMCC) does not support LTO. Switch to ARM Compiler 6 (ArmClang) via

Project → Options for Target → Target → Compiler Version 6

. Verify with armclang --version (e.g., clang version 6.18).

2. Choose the optimisation level : In the C/C++ tab set “Compile Time” to “Optimize for Size” or “Optimize for Time” and select Optimization Level 3 ( -O3).

3. Enable the LTO switch : Tick “Enable Link‑Time Optimization”. Keil adds -flto -fno-use-linker-plugin to the compile command.

4. Configure the linker : In the Linker tab enable “Use Memory Layout from Target Dialog” and add --lto to the Misc Controls field. Also enable “Generate Cross Reference List” and “Create Object Browser Information” for better analysis.

The resulting compile command looks like:

armclang --target=arm-arm-none-eabi -mcpu=cortex-m4 -O3 -ffunction-sections -fdata-sections -flto -fno-use-linker-plugin …

The full build flow is:

[Source Files]
   ↓
[armclang -flto -O3]
   ↓
[.o with .llvmbc]
   ↓
[armlink --lto]
   ↓
[Final .axf with Global Optimisation]

Common pitfalls and how to fix them

Using ARMCC instead of ArmClang – switch compiler version.

Stale object files – delete the Objects/ and Listings/ folders and rebuild.

Missing LTO flag – ensure -flto appears in the build log.

Typical linker error

Error: L6242E: Cannot link object file xxx.o as it contains debug information in an unsupported format.

indicates that a library was built with ARMCC and lacks Bitcode. Solutions:

Re‑compile the library with ArmClang -flto (ideal but costly).

Link the non‑LTO library separately using --allow-shrink-warnings --ignore-lto-warnings.

Exclude the problematic file from LTO with -fno-lto.

Detecting LTO support in object files

Run fromelf -c your_object.o | grep llvmbc. If there is output, the object contains Bitcode and can participate in LTO.

Empirical data: flash savings

Section   | Without LTO (Bytes) | With LTO (Bytes) | Reduction | Compression
--------------------------------------------------------------------------
.text     | 183488               | 94208            | -89280    | 48.7%
.constdata| 12544                | 6144             | -6400     | 51.0%
.data     | 4096                 | 4096             | 0         | 0%
.bss      | 16384                | 15872            | -512      | 3.1%
--------------------------------------------------------------------------
Total     | 216512               | 120320           | -96192    | 44.4%

The .data section cannot be optimised because it holds initialized globals, while .bss shrinks slightly as unused static buffers are removed.

C++ template deduplication

When multiple instantiations of the same template generate identical code, LTO merges them. Example:

template<int N> class VectorProcessor {
public:
  float process(const float* input) {
    float sum = 0.0f;
    for (int i = 0; i < N; ++i) sum += input[i] * 0.5f;
    return sum / N;
  }
};

VectorProcessor<3> vp3;
VectorProcessor<4> vp4;
VectorProcessor<3> another_vp3; // same specialization

Without LTO each VectorProcessor<3>::process is emitted twice. With LTO the linker keeps only one copy, saving about 76 bytes.

Performance boost beyond size reduction

Using the Cortex‑M DWT cycle counter, a low‑pass filter benchmark shows a drop from 142 cycles per call (no LTO) to 98 cycles (LTO), a 30.9 % speed‑up. The gains come from constant folding of the filter coefficient, reordered floating‑point operations, register‑resident state variables and loop unrolling.

Interrupt latency also improves: a four‑level ISR chain that costs ~36 cycles without LTO shrinks to ~18 cycles after full inlining, a 50 % reduction. Real measurements on a 168 MHz core confirm average latency dropping from 2.34 µs to 1.52 µs (35 % faster).

Trade‑offs: build time and debugging

Full LTO can increase compile‑link time dramatically (e.g., from 12.4 s to 47.8 s, a 285 % rise). Mitigation strategies include enabling LTO only for Release builds, using distributed build caches (IceCC), or pre‑building stable modules as LTO libraries.

Debugging becomes harder because many functions disappear or are renamed. Recommended practices:

Develop with -O2 and no LTO; enable LTO only for final releases.

Keep --debug and --split_sections in Release builds.

Mark critical debug functions with __attribute__((used)) to prevent removal.

Stack‑size analysis also suffers because LTO changes call graphs. Use runtime stack‑checking (MPU guard pages), insert explicit __current_sp() checks, or rely on profiling tools to capture actual stack usage.

Advanced LTO policies for large projects

Module Type          | Example Components          | LTO Recommendation
--------------------------------------------------------------------------
Real‑time control    | PID, PWM ISR                | -O3 + LTO + aggressive inlining
Peripheral drivers   | UART, ADC drivers           | -O2, disable cross‑file inlining
Protocol stacks      | Modbus, JSON parser         | Full LTO, prioritize size reduction
Third‑party libs     | FreeRTOS, CMSIS‑DSP         | Build as non‑LTO objects

Critical functions can be forced to stay inline with __attribute__((always_inline)), while safety‑critical code (e.g., flash write) can be compiled with a pragma that forces -O2 and disables LTO.

Future outlook: AI‑assisted LTO

ArmClang already supports ThinLTO and FullLTO. The next step may be AI‑driven optimisation hints that analyse call graphs, memory patterns and historic performance data to automatically suggest which modules should enable LTO, turning the optimisation process into a continuous, data‑driven workflow.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PerformanceLTOCode Size ReductionARM CompilerEmbedded OptimizationKeil5
IoT Full-Stack Technology
Written by

IoT Full-Stack Technology

Dedicated to sharing IoT cloud services, embedded systems, and mobile client technology, with no spam ads.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.