mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
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>
51 lines
2.4 KiB
CMake
51 lines
2.4 KiB
CMake
# Helpers for link-time optimization (LTO) support.
|
|
#
|
|
# These helpers are shared between the legacy build system (tools/cmake) and the
|
|
# build system v2 (tools/cmakev2). They only rely on the component property API
|
|
# (idf_component_get_property) and the COMPONENT_DIR / LDFRAGMENTS component
|
|
# properties, which are available in both build systems.
|
|
|
|
include_guard(GLOBAL)
|
|
|
|
# __lto_collect_fragment_placed_components(<output_var> <component_name>...)
|
|
#
|
|
# Returns, in output_var, the subset of the given components whose object code is
|
|
# placed by an explicit "archive: libNAME.a" directive in some linker fragment
|
|
# (.lf) file - whether that fragment belongs to the component itself or to a
|
|
# different component.
|
|
#
|
|
# Such components rely on section placement that is matched by object file /
|
|
# archive name. LTO merges and renames the object files of a component, so the
|
|
# placement rules silently stop matching and the affected functions (for example
|
|
# the spi_flash / GDMA HAL routines that must execute from IRAM while the flash
|
|
# cache is disabled) end up in flash. Compiling these components without LTO keeps
|
|
# their placement intact.
|
|
#
|
|
# Wildcard directives ("archive: *") are intentionally ignored: they match by
|
|
# section name rather than by object file name and are therefore not affected by
|
|
# LTO.
|
|
function(__lto_collect_fragment_placed_components output_var)
|
|
set(placed "")
|
|
foreach(component_name ${ARGN})
|
|
idf_component_get_property(component_dir ${component_name} COMPONENT_DIR)
|
|
idf_component_get_property(ldfragments ${component_name} LDFRAGMENTS)
|
|
foreach(ldfragment ${ldfragments})
|
|
get_filename_component(ldfragment_abs "${ldfragment}" ABSOLUTE BASE_DIR "${component_dir}")
|
|
if(EXISTS "${ldfragment_abs}")
|
|
file(READ "${ldfragment_abs}" ldfragment_contents)
|
|
string(REGEX MATCHALL "archive:[ \t]*lib[A-Za-z0-9_-]+\\.a"
|
|
archive_matches "${ldfragment_contents}")
|
|
foreach(archive_match ${archive_matches})
|
|
string(REGEX REPLACE "archive:[ \t]*lib([A-Za-z0-9_-]+)\\.a" "\\1"
|
|
archive_name "${archive_match}")
|
|
list(APPEND placed ${archive_name})
|
|
endforeach()
|
|
endif()
|
|
endforeach()
|
|
endforeach()
|
|
if(placed)
|
|
list(REMOVE_DUPLICATES placed)
|
|
endif()
|
|
set(${output_var} "${placed}" PARENT_SCOPE)
|
|
endfunction()
|