mirror of
https://github.com/espressif/esp-idf.git
synced 2026-08-18 06:35:35 +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>
116 lines
6.3 KiB
CMake
116 lines
6.3 KiB
CMake
include(${CMAKE_CURRENT_LIST_DIR}/toolchain_flags.cmake)
|
|
include($ENV{IDF_PATH}/tools/cmake/deduplicate_flags.cmake)
|
|
|
|
if(NOT CMAKE_PARENT_LIST_FILE)
|
|
message(FATAL_ERROR "toolchain.cmake cannot be used standalone (use chip-specific toolchain file instead)")
|
|
endif()
|
|
|
|
# Paths normalization
|
|
get_filename_component(_idf_toolchain_dir "$ENV{IDF_PATH}/tools/cmake" REALPATH)
|
|
file(TO_CMAKE_PATH "${_idf_toolchain_dir}" _idf_toolchain_dir)
|
|
|
|
get_filename_component(_toolchain_filename "${CMAKE_TOOLCHAIN_FILE}" NAME)
|
|
get_filename_component(_current_toolchain_dir "${CMAKE_TOOLCHAIN_FILE}" DIRECTORY REALPATH)
|
|
file(TO_CMAKE_PATH "${_current_toolchain_dir}" _current_toolchain_dir)
|
|
|
|
set(CMAKE_SYSTEM_NAME Generic)
|
|
|
|
# Windows CreateProcess enforces a short command line (~32k). IDF compile lines can be
|
|
# huge (includes, reproducible-build prefix maps, etc.). Tell the Ninja generator to
|
|
# pass compiler arguments via response files (CMake 3.15+). This is orthogonal to the
|
|
# early-stage @cflags/@cxxflags response files below, which only cover CMAKE_*_FLAGS.
|
|
if(CMAKE_HOST_WIN32 AND CMAKE_GENERATOR MATCHES "Ninja"
|
|
AND NOT DEFINED CACHE{CMAKE_NINJA_FORCE_RESPONSE_FILE})
|
|
set(CMAKE_NINJA_FORCE_RESPONSE_FILE ON CACHE BOOL
|
|
"Use Ninja response files on Windows hosts (avoid CreateProcess command-line limit).")
|
|
endif()
|
|
|
|
# Set compiler tools according to the toolchain type
|
|
string(FIND "${_toolchain_filename}" "clang" found_clang)
|
|
if(NOT found_clang EQUAL -1)
|
|
set(IDF_TOOLCHAIN "clang" CACHE STRING "IDF Build Toolchain Type" FORCE)
|
|
|
|
set(CMAKE_C_COMPILER clang)
|
|
set(CMAKE_CXX_COMPILER clang++)
|
|
set(CMAKE_ASM_COMPILER clang)
|
|
set(CMAKE_LINKER ${_CMAKE_TOOLCHAIN_PREFIX}clang-ld)
|
|
set(CMAKE_AR llvm-ar)
|
|
set(CMAKE_RANLIB llvm-ranlib)
|
|
set(CMAKE_OBJDUMP ${_CMAKE_TOOLCHAIN_PREFIX}clang-objdump)
|
|
else()
|
|
set(IDF_TOOLCHAIN "gcc" CACHE STRING "IDF Build Toolchain Type" FORCE)
|
|
|
|
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.
|
|
# CMake may execute this toolchain file in different contexts:
|
|
#
|
|
# 1. First execution (IDF project build):
|
|
# When CMAKE_TOOLCHAIN_FILE points to IDF sources, the toolchain file is
|
|
# executed for the main project. In this case, we create the response files
|
|
# directory in the build directory, copy the toolchain file there, initialize
|
|
# the response files, and update CMAKE_TOOLCHAIN_FILE to point to the copy.
|
|
#
|
|
# 2. Subsequent executions (External project builds):
|
|
# When CMAKE_TOOLCHAIN_FILE points to a copied toolchain file (from a
|
|
# previous root project build), the response files already exist in the same
|
|
# directory as the toolchain file. We simply set IDF_TOOLCHAIN_BUILD_DIR to
|
|
# point to that existing directory.
|
|
if(_idf_toolchain_dir STREQUAL _current_toolchain_dir)
|
|
set(IDF_TOOLCHAIN_BUILD_DIR "${CMAKE_BINARY_DIR}/toolchain"
|
|
CACHE PATH "Path to toolchain build directory containing response files and toolchain file copy" FORCE)
|
|
|
|
# Copy toolchain file into the build directory and update CMAKE_TOOLCHAIN_FILE
|
|
# to point to the copy. This approach allows us to avoid worrying about different
|
|
# CMAKE_BINARY_DIR values between base IDF-project builds and external projects.
|
|
# For external project builds, compiler response files are located in the same
|
|
# directory as CMAKE_TOOLCHAIN_FILE, making them easy to find.
|
|
file(MAKE_DIRECTORY "${IDF_TOOLCHAIN_BUILD_DIR}")
|
|
file(COPY "${CMAKE_TOOLCHAIN_FILE}" DESTINATION "${IDF_TOOLCHAIN_BUILD_DIR}")
|
|
set(CMAKE_TOOLCHAIN_FILE "${IDF_TOOLCHAIN_BUILD_DIR}/${_toolchain_filename}")
|
|
|
|
# Create response files before CMake performs compiler checks.
|
|
# These files are required for the compiler detection process to succeed.
|
|
# The files are created even when the flag variables are empty, ensuring
|
|
# they exist when referenced by CMAKE_*_FLAGS variables below.
|
|
idf_toolchain_add_flags(C_COMPILE_OPTIONS "${CMAKE_C_FLAGS}"
|
|
CXX_COMPILE_OPTIONS "${CMAKE_CXX_FLAGS}"
|
|
ASM_COMPILE_OPTIONS "${CMAKE_ASM_FLAGS}"
|
|
LINK_OPTIONS "${CMAKE_EXE_LINKER_FLAGS}")
|
|
# Clear CMAKE_*_FLAGS because all flags are written to response files.
|
|
set(CMAKE_C_FLAGS "")
|
|
set(CMAKE_CXX_FLAGS "")
|
|
set(CMAKE_ASM_FLAGS "")
|
|
set(CMAKE_EXE_LINKER_FLAGS "")
|
|
else()
|
|
set(IDF_TOOLCHAIN_BUILD_DIR "${_current_toolchain_dir}"
|
|
CACHE PATH "Path to toolchain build directory containing response files and toolchain file copy" FORCE)
|
|
endif()
|
|
|
|
# Merge the response file path with any existing CMAKE_*_FLAGS (e.g. from
|
|
# ExternalProject_Add), then remove duplicates. Deduplication is needed because
|
|
# CMake may execute this toolchain file multiple times during initialization.
|
|
remove_duplicated_flags("@\"${IDF_TOOLCHAIN_BUILD_DIR}/cflags\" ${CMAKE_C_FLAGS}" CMAKE_C_FLAGS)
|
|
remove_duplicated_flags("@\"${IDF_TOOLCHAIN_BUILD_DIR}/cxxflags\" ${CMAKE_CXX_FLAGS}" CMAKE_CXX_FLAGS)
|
|
remove_duplicated_flags("@\"${IDF_TOOLCHAIN_BUILD_DIR}/asmflags\" ${CMAKE_ASM_FLAGS}" CMAKE_ASM_FLAGS)
|
|
remove_duplicated_flags("@\"${IDF_TOOLCHAIN_BUILD_DIR}/ldflags\" ${CMAKE_EXE_LINKER_FLAGS}" CMAKE_EXE_LINKER_FLAGS)
|
|
|
|
# Configure CMake to use response files for compiler and linker flags.
|
|
# Some compilation options enabled by IDF configuration options are not yet
|
|
# defined at this very early CMake stage (toolchain.cmake execution). Response
|
|
# files allow these flags to be dynamically updated during the CMake configuration
|
|
# phase, after the options become available.
|
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "C Compiler Base Flags" FORCE)
|
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" CACHE STRING "C++ Compiler Base Flags" FORCE)
|
|
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS}" CACHE STRING "Asm Compiler Base Flags" FORCE)
|
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" CACHE STRING "Linker Base Flags" FORCE)
|