mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
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:
co-authored by
Claude Opus 4.8
parent
8bf9c476cf
commit
d20a986999
+37
-1
@@ -291,7 +291,22 @@ if(CONFIG_ESP_SYSTEM_USE_FRAME_POINTER)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
list(APPEND link_options "-fno-lto")
|
if(CONFIG_COMPILER_LTO_LINKTIME AND NOT BOOTLOADER_BUILD AND NOT ESP_TEE_BUILD)
|
||||||
|
list(APPEND link_options "-flto=auto")
|
||||||
|
|
||||||
|
if(CONFIG_APP_REPRODUCIBLE_BUILD)
|
||||||
|
# LTO generates code at link time, where the path remapping applied to
|
||||||
|
# compile_options doesn't take effect, so pass it to the linker as well.
|
||||||
|
# -save-temps keeps LTRANS objects out of $TMPDIR, and a pinned random
|
||||||
|
# seed makes LTO bytecode byte-identical. See the commit message for
|
||||||
|
# details.
|
||||||
|
list(APPEND link_options ${prefix_map_compile_options})
|
||||||
|
list(APPEND link_options "-save-temps")
|
||||||
|
list(APPEND compile_options "-frandom-seed=1")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
list(APPEND compile_options "-fno-lto")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
|
if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
|
||||||
# Not all versions of the MacOS linker support the -warn_commons flag.
|
# Not all versions of the MacOS linker support the -warn_commons flag.
|
||||||
@@ -383,6 +398,27 @@ foreach(component_target ${build_component_targets})
|
|||||||
set(__idf_component_context 0)
|
set(__idf_component_context 0)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
if(CONFIG_COMPILER_LTO_COMPILETIME AND NOT BOOTLOADER_BUILD AND NOT ESP_TEE_BUILD)
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/tools/cmake/lto.cmake")
|
||||||
|
idf_build_get_property(build_components BUILD_COMPONENTS)
|
||||||
|
# Components whose object code is placed by a linker fragment (their own or
|
||||||
|
# another component's) must not be compiled with LTO, otherwise the placement
|
||||||
|
# stops matching. See tools/cmake/lto.cmake for details.
|
||||||
|
__lto_collect_fragment_placed_components(lto_placed_components ${build_components})
|
||||||
|
# For each component, enable LTO unless it has its own linker fragments, is
|
||||||
|
# placed by some fragment, has opted out via NO_LTO, or is not a static library.
|
||||||
|
foreach(component_name ${build_components})
|
||||||
|
idf_component_get_property(ldfragment ${component_name} LDFRAGMENTS)
|
||||||
|
idf_component_get_property(no_lto ${component_name} NO_LTO)
|
||||||
|
idf_component_get_property(component_lib ${component_name} COMPONENT_LIB)
|
||||||
|
get_target_property(type ${component_lib} TYPE)
|
||||||
|
if(NOT ldfragment AND NOT no_lto AND NOT component_name IN_LIST lto_placed_components
|
||||||
|
AND type STREQUAL "STATIC_LIBRARY")
|
||||||
|
target_compile_options(${component_lib} PRIVATE -flto=auto)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Run component validation checks after all components have been processed
|
# Run component validation checks after all components have been processed
|
||||||
# Only run validation for the main project, not subprojects like bootloader
|
# Only run validation for the main project, not subprojects like bootloader
|
||||||
idf_build_get_property(bootloader_build BOOTLOADER_BUILD)
|
idf_build_get_property(bootloader_build BOOTLOADER_BUILD)
|
||||||
|
|||||||
@@ -787,6 +787,47 @@ mainmenu "Espressif IoT Development Framework Configuration"
|
|||||||
default "gcc" if COMPILER_RT_LIB_GCCLIB
|
default "gcc" if COMPILER_RT_LIB_GCCLIB
|
||||||
default "" if COMPILER_RT_LIB_HOST
|
default "" if COMPILER_RT_LIB_HOST
|
||||||
|
|
||||||
|
config COMPILER_LTO_LINKTIME
|
||||||
|
bool "Enable LTO at link time"
|
||||||
|
default n
|
||||||
|
# LTO for host builds is probably not needed at all
|
||||||
|
# LTO with Clang needs LLD (IDF-8286)
|
||||||
|
# LTO works with APP_REPRODUCIBLE_BUILD (see top-level CMakeLists.txt)
|
||||||
|
depends on !IDF_TARGET_LINUX && !IDF_TOOLCHAIN_CLANG
|
||||||
|
help
|
||||||
|
If enabled, ESP-IDF build system will tell the linker to use LTO
|
||||||
|
(Link Time Optimization). This option may increase linking time but
|
||||||
|
will also produce smaller binaries.
|
||||||
|
Note that to see some code size reduction, you should also enable LTO
|
||||||
|
at compile time for some components.
|
||||||
|
|
||||||
|
You can do this either selectively for specific components, or use
|
||||||
|
the option CONFIG_COMPILER_LTO_COMPILETIME below to enable
|
||||||
|
it for most of the components by default.
|
||||||
|
|
||||||
|
Note that LTO can also increase task stack usage, because inlining
|
||||||
|
across translation units produces fewer but larger stack frames. After
|
||||||
|
enabling LTO, check the stack high water mark of your tasks and raise the
|
||||||
|
stack sizes of the affected tasks if necessary.
|
||||||
|
|
||||||
|
config COMPILER_LTO_COMPILETIME
|
||||||
|
bool "Enable LTO by default at compile time"
|
||||||
|
depends on COMPILER_LTO_LINKTIME
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
If enabled, ESP-IDF build system will compile most of the components with
|
||||||
|
LTO (link time optimizations) enabled.
|
||||||
|
|
||||||
|
Components which have link-time sections placement requirements will not
|
||||||
|
have LTO enabled. This includes components which specify linker fragment
|
||||||
|
files, as well as components whose object code is placed by a linker
|
||||||
|
fragment of another component.
|
||||||
|
|
||||||
|
Note that enabling LTO for all components does not always reduce the
|
||||||
|
binary size, and in some cases can increase it. For a more predictable
|
||||||
|
result, consider enabling LTO selectively for specific components by
|
||||||
|
adding the -flto=auto compile option to them.
|
||||||
|
|
||||||
choice COMPILER_ORPHAN_SECTIONS
|
choice COMPILER_ORPHAN_SECTIONS
|
||||||
prompt "Orphan sections handling"
|
prompt "Orphan sections handling"
|
||||||
default COMPILER_ORPHAN_SECTIONS_ERROR
|
default COMPILER_ORPHAN_SECTIONS_ERROR
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# 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()
|
||||||
@@ -43,6 +43,12 @@ else()
|
|||||||
set(CMAKE_C_COMPILER ${_CMAKE_TOOLCHAIN_PREFIX}gcc)
|
set(CMAKE_C_COMPILER ${_CMAKE_TOOLCHAIN_PREFIX}gcc)
|
||||||
set(CMAKE_CXX_COMPILER ${_CMAKE_TOOLCHAIN_PREFIX}g++)
|
set(CMAKE_CXX_COMPILER ${_CMAKE_TOOLCHAIN_PREFIX}g++)
|
||||||
set(CMAKE_ASM_COMPILER ${_CMAKE_TOOLCHAIN_PREFIX}gcc)
|
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()
|
endif()
|
||||||
|
|
||||||
# Handle different execution contexts for the toolchain file.
|
# Handle different execution contexts for the toolchain file.
|
||||||
|
|||||||
Reference in New Issue
Block a user