feat(build): add options to enable link-time optimization (LTO)

Link-time optimization (LTO) lets the compiler inline and optimize across
translation units. ESP-IDF relies heavily on linker-script placement rules
that match object files by name, which LTO does not preserve, so LTO cannot
be enabled for the whole framework. This change adds two opt-in options that
side-step that conflict:

- CONFIG_COMPILER_LTO_LINKTIME tells the linker to perform LTO on any object
  files that carry LTO information (compiled with -flto). On its own this is
  safe: users can add -flto to specific components (e.g. their own libraries)
  to shrink them, without affecting components that use linker fragments.

- CONFIG_COMPILER_LTO_COMPILETIME automatically compiles most
  components with -flto. A component is excluded when it has its own linker
  fragments, when it opts out via the NO_LTO component property, or when its
  object code is placed by *another* component's linker fragment (matched by
  archive name). The last case is handled by tools/cmake/lto.cmake, which
  scans linker fragments for explicit "archive: libNAME.a" placement and
  excludes those components. Without it, functions that must run from IRAM
  while the flash cache is disabled (e.g. the spi_flash / GDMA HAL routines,
  placed in IRAM by spi_flash/esp_driver_dma fragments) would be moved to
  flash by LTO and the device would panic with a cache error at run time.

Both options are disabled for the bootloader and ESP-TEE builds, which depend
on object-file-name based placement. LTO is also gated off for Clang
(needs LLD, IDF-8286) and host builds.

LTO works together with CONFIG_APP_REPRODUCIBLE_BUILD, but needs extra
flags: LTO defers code generation and most debug-info emission from compile
time to link time, where the reproducible-build path remapping (applied to
compile_options only) does not take effect. When both options are enabled,
three extra flags keep the .elf, .bin and .map byte-identical across build
directories (verified on esp32 / GCC 16.1):

- the -f*-prefix-map options are passed to the linker as well, so the LTO
  code generator remaps DW_AT_comp_dir (otherwise the build dir leaks into
  .debug_str, and cascades into esp_app_desc_t.app_elf_sha256 in the .bin);
- -save-temps makes lto-wrapper use stable LTRANS object names in the build
  dir instead of random $TMPDIR paths that leak into the .map;
- -frandom-seed=1 makes LTO GIMPLE bytecode objects byte-identical (a
  shared seed was verified not to collide, including for C++ file-local
  static variables and anonymous namespaces promoted by LTO).

The gcc-ar / gcc-ranlib wrappers are selected in the GCC toolchain file so
that the LTO plugin is loaded when creating and indexing static archives;
plain ar/ranlib do not record LTO symbols in the archive index.

Note: LTO, like other inlining, can also increase binary size. Enable it
together with CONFIG_COMPILER_OPTIMIZATION_SIZE to get a code-size benefit.

Related: IDF-71, IDF-8286

Closes https://github.com/espressif/esp-idf/issues/18741

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ivan Grokhotkov (bot)
2026-07-14 18:59:46 +02:00
co-authored by Claude Opus 4.8
parent 8bf9c476cf
commit d20a986999
4 changed files with 134 additions and 1 deletions
+6
View File
@@ -43,6 +43,12 @@ else()
set(CMAKE_C_COMPILER ${_CMAKE_TOOLCHAIN_PREFIX}gcc)
set(CMAKE_CXX_COMPILER ${_CMAKE_TOOLCHAIN_PREFIX}g++)
set(CMAKE_ASM_COMPILER ${_CMAKE_TOOLCHAIN_PREFIX}gcc)
# Use the gcc-ar/gcc-ranlib wrappers so that the LTO plugin is loaded when
# creating and indexing static archives. This is required for link-time
# optimization (CONFIG_COMPILER_LTO_LINKTIME) to work across static libraries;
# plain ar/ranlib do not record the LTO symbols in the archive index.
set(CMAKE_AR ${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar)
set(CMAKE_RANLIB ${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib)
endif()
# Handle different execution contexts for the toolchain file.