diff --git a/CMakeLists.txt b/CMakeLists.txt index 75042cfa8fc..2ece1b802ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,7 +291,22 @@ if(CONFIG_ESP_SYSTEM_USE_FRAME_POINTER) 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") # 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) 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 # Only run validation for the main project, not subprojects like bootloader idf_build_get_property(bootloader_build BOOTLOADER_BUILD) diff --git a/Kconfig b/Kconfig index 064e4091115..1a70c322166 100644 --- a/Kconfig +++ b/Kconfig @@ -787,6 +787,47 @@ mainmenu "Espressif IoT Development Framework Configuration" default "gcc" if COMPILER_RT_LIB_GCCLIB 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 prompt "Orphan sections handling" default COMPILER_ORPHAN_SECTIONS_ERROR diff --git a/components/bootloader/subproject/CMakeLists_v2.txt b/components/bootloader/subproject/CMakeLists_v2.txt index b684c8bddbd..f676d9331f6 100644 --- a/components/bootloader/subproject/CMakeLists_v2.txt +++ b/components/bootloader/subproject/CMakeLists_v2.txt @@ -92,6 +92,10 @@ idf_build_set_property(IDF_COMPONENT_OPTIONAL_REQUIRES_MODE DEFERRED) # (CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_*) set below, not the app-level # CONFIG_COMPILER_OPTIMIZATION_* defaults. idf_build_set_property(SET_COMPILER_OPTIMIZATION NO) +# The bootloader relies on linker-script section placement matched by object +# file name, which LTO does not preserve, so it must never be compiled or linked +# with LTO regardless of the application's CONFIG_COMPILER_LTO_* options. +idf_build_set_property(SET_COMPILER_LTO NO) # Perform internal IDF project initialisation idf_project_init() diff --git a/components/bootloader_support/CMakeLists.txt b/components/bootloader_support/CMakeLists.txt index eb274e46175..98bae2bae7e 100644 --- a/components/bootloader_support/CMakeLists.txt +++ b/components/bootloader_support/CMakeLists.txt @@ -239,3 +239,7 @@ if(BOOTLOADER_BUILD) # linker will ignore this structure as it has no other files depending on it. target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_bootloader_desc") endif() + +# Disable LTO for bootloader_support: it relies on linker script placements that +# depend on object file names, which LTO does not preserve. +idf_component_set_property(${COMPONENT_NAME} NO_LTO 1) diff --git a/components/esp_common/include/esp_attr.h b/components/esp_common/include/esp_attr.h index c8c6c4d5f0c..8d78cfa9be6 100644 --- a/components/esp_common/include/esp_attr.h +++ b/components/esp_common/include/esp_attr.h @@ -90,7 +90,17 @@ extern "C" { // Forces a string into DRAM instead of flash // Use as esp_rom_printf(DRAM_STR("Hello world!\n")); -#define DRAM_STR(str) (__extension__({static const DRAM_ATTR char __c[] = (str); (const char *)&__c;})) +// The string is placed into a uniquely-named variable in a uniquely-named +// subsection of .dram1. Both names are derived from a single __COUNTER__ value. +// This avoids "section type conflict" errors under LTO, where the local static +// variables generated by DRAM_STR in different translation units would otherwise +// share the same name and the same DRAM_ATTR section name after the units are merged. +#define DRAM_STR(str) _DRAM_STR_IMPL(str, __COUNTER__) +#define _DRAM_STR_COMBINE(x, y) x ## y +#define _DRAM_STR_IMPL(str, counter) \ + _DRAM_STR_IMPL2(str, _DRAM_STR_COMBINE(__c, counter), _SECTION_ATTR_IMPL(".dram1.str", counter)) +#define _DRAM_STR_IMPL2(str, varname, attr) \ + (__extension__({static const attr char varname[] = (str); (const char *)&varname;})) #if CONFIG_SOC_RTC_FAST_MEM_SUPPORTED || CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED // Forces data into RTC memory. See "docs/deep-sleep-stub.rst" diff --git a/docs/en/api-guides/performance/size.rst b/docs/en/api-guides/performance/size.rst index 88156f6cbda..3be105bf22f 100644 --- a/docs/en/api-guides/performance/size.rst +++ b/docs/en/api-guides/performance/size.rst @@ -79,6 +79,12 @@ The following configuration options reduces the final binary size of almost any .. list:: - Set :ref:`CONFIG_COMPILER_OPTIMIZATION` to ``Optimize for size (-Os)``. In some cases, ``Optimize for performance (-O2)`` will also reduce the binary size compared to the default. Note that if your code contains C or C++ Undefined Behavior then increasing the compiler optimization level may expose bugs that otherwise do not happen. + - Set :ref:`CONFIG_COMPILER_LTO_LINKTIME` to enable link-time optimization, and enable LTO at compile time for some of the components (either by setting :ref:`CONFIG_COMPILER_LTO_COMPILETIME`, or by adding the ``-flto=auto`` compile option to specific components). When used together with optimization for size, this option can reduce the binary size. ``idf.py size``, ``idf.py size-components``, and ``idf.py size-files`` all report accurate sizes for LTO-enabled builds. Note that enabling LTO has a few downsides: + + - Enabling LTO increases the build time. + - Enabling LTO can increase the stack usage of your tasks. Inlining across translation units replaces several small stack frames with fewer but larger ones, so the peak stack depth along a call path may grow. The effect is usually small, but it can be significant for applications with deep call chains that span many LTO-optimized components. After enabling LTO, check the stack high water mark of your tasks (for example, with :cpp:func:`uxTaskGetStackHighWaterMark`) and raise the stack sizes of the affected tasks if necessary. + - LTO must not be enabled for components with custom :doc:`linker fragments <../linker-script-generation>`. This is because LTO merges the code from multiple object files into one, so the linker fragment is not able to control the placement of individual functions and variables. + - Enabling LTO may make the code more difficult to debug. - Reduce the compiled-in log output by lowering the app :ref:`CONFIG_LOG_DEFAULT_LEVEL`. If the :ref:`CONFIG_LOG_MAXIMUM_LEVEL` is changed from the default then this setting controls the binary size instead. Reducing compiled-in logging reduces the number of strings in the binary, and also the code size of the calls to logging functions. - If your application does not require dynamic log level changes and you do not need to control logs per module using tags, consider disabling :ref:`CONFIG_LOG_DYNAMIC_LEVEL_CONTROL` and changing :ref:`CONFIG_LOG_TAG_LEVEL_IMPL`. It reduces IRAM usage by approximately 260 bytes, DRAM usage by approximately 264 bytes, and flash usage by approximately 1 KB compared to the default option, it also speeds up logging. - Set the :ref:`CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL` to ``Silent``. This avoids compiling in a dedicated assertion string and source file name for each assert that may fail. It is still possible to find the failed assert in the code by looking at the memory address where the assertion failed. diff --git a/docs/zh_CN/api-guides/performance/size.rst b/docs/zh_CN/api-guides/performance/size.rst index c4661b09b4c..a4d82c0ae1d 100644 --- a/docs/zh_CN/api-guides/performance/size.rst +++ b/docs/zh_CN/api-guides/performance/size.rst @@ -79,6 +79,12 @@ ESP-IDF 构建系统会编译项目和 ESP-IDF 中所有源文件,但只有程 .. list:: - 将 :ref:`CONFIG_COMPILER_OPTIMIZATION` 设置为 ``Optimize for size (-Os)``。在某些情况下,相较于默认设置, ``Optimize for size (-Os)`` 也可以减小二进制文件的大小。请注意,若代码包含 C 或 C++ 的未定义行为,提高编译器优化级别可能会暴露出原本不存在的错误。 + - 设置 :ref:`CONFIG_COMPILER_LTO_LINKTIME` 以启用链接时优化 (LTO),并为部分组件启用编译时 LTO(既可以设置 :ref:`CONFIG_COMPILER_LTO_COMPILETIME`,也可以为特定组件添加 ``-flto=auto`` 编译选项)。与优化大小 (``-Os``) 一同使用时,该选项可以减小二进制文件的大小。``idf.py size``、``idf.py size-components`` 和 ``idf.py size-files`` 均可为启用 LTO 的构建报告准确的大小。请注意,启用 LTO 有以下几点不足: + + - 启用 LTO 会增加构建时间。 + - 启用 LTO 可能会增加任务的栈使用量。跨翻译单元的内联会用更少但更大的栈帧替代多个较小的栈帧,因此某条调用路径上的峰值栈深度可能增加。该影响通常较小,但对于调用链较深、且跨越多个经 LTO 优化组件的应用程序,影响可能较为明显。启用 LTO 后,请检查任务的栈高水位(例如使用 :cpp:func:`uxTaskGetStackHighWaterMark`),并在必要时增大受影响任务的栈大小。 + - 不得为使用自定义 :doc:`链接器片段 <../linker-script-generation>` 的组件启用 LTO。这是因为 LTO 会将多个目标文件中的代码合并为一个,导致链接器片段无法控制单个函数和变量的放置位置。 + - 启用 LTO 可能会使代码更难调试。 - 通过降低应用程序的 :ref:`CONFIG_LOG_DEFAULT_LEVEL` ,可以减少编译时的日志输出。如果改变 :ref:`CONFIG_LOG_MAXIMUM_LEVEL` 的默认选项,则可以控制二进制文件的大小。减少编译时的日志输出可以减少二进制文件中的字符串数量,并减小调用日志函数的代码大小。 - 如果应用程序不需要动态更改日志级别,并且不需要使用标签来控制每个模块的日志,建议禁用 :ref:`CONFIG_LOG_DYNAMIC_LEVEL_CONTROL` 并更改 :ref:`CONFIG_LOG_TAG_LEVEL_IMPL`。与默认选项相比,这可以节约大概 260 字节的 IRAM、264 字节的 DRAM、以及 1 KB 的 flash,同时还可以加快日志记录的速度。 - 将 :ref:`CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL` 设置为 ``Silent``,可以避免为所有可能失败的断言编译专门的断言字符串和源文件名。尽管如此,仍可以通过查看断言失败时的内存地址以在代码中找到失败断言。 diff --git a/tools/cmake/lto.cmake b/tools/cmake/lto.cmake new file mode 100644 index 00000000000..3c31ebc1b52 --- /dev/null +++ b/tools/cmake/lto.cmake @@ -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( ...) +# +# 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() diff --git a/tools/cmake/toolchain.cmake b/tools/cmake/toolchain.cmake index 75f1a2a28a6..7ab605cd856 100644 --- a/tools/cmake/toolchain.cmake +++ b/tools/cmake/toolchain.cmake @@ -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. diff --git a/tools/cmakev2/component.cmake b/tools/cmakev2/component.cmake index 8e4c4bc48d1..b8ec4c22577 100644 --- a/tools/cmakev2/component.cmake +++ b/tools/cmakev2/component.cmake @@ -1011,6 +1011,43 @@ function(idf_component_include name) list(APPEND components_included "${component_name}") idf_build_set_property(COMPONENTS_INCLUDED "${components_included}") + # Compile-time LTO (CONFIG_COMPILER_LTO_COMPILETIME): record which components + # must NOT be compiled with LTO. Linker fragments place object code by + # archive / object-file name, which LTO renames and merges, so any component + # that participates in fragment placement must be excluded. The decision is + # recorded in the NO_LTO component property and consumed later, when the + # component's static library is created, via a generator expression. Because + # the generator expression is evaluated only after every component has been + # processed, it does not matter that a component may be placed by a fragment + # belonging to a component that is included after it. + # A subproject that must not use LTO (for example the bootloader) opts out by + # setting the SET_COMPILER_LTO build property to NO, the same way it uses + # SET_COMPILER_OPTIMIZATION; an unset property means LTO is allowed. The + # result is reused by the apply block further below. + idf_build_get_property(set_compiler_lto SET_COMPILER_LTO) + if(NOT DEFINED set_compiler_lto OR set_compiler_lto STREQUAL "") + set(set_compiler_lto YES) + endif() + if(CONFIG_COMPILER_LTO_COMPILETIME AND set_compiler_lto) + # A component with its own linker fragments relies on object-file-name + # placement and must not be compiled with LTO. + idf_component_get_property(component_ldfragments "${component_name}" LDFRAGMENTS) + if(component_ldfragments) + idf_component_set_property("${component_name}" NO_LTO 1) + endif() + # A component may also place the object code of *other* components by + # naming their archive ("archive: libNAME.a") in its own fragments; mark + # those too. Names that do not resolve to a build component (for example + # toolchain archives such as libc.a) are skipped. + __lto_collect_fragment_placed_components(lto_placed_components "${component_name}") + foreach(placed_component IN LISTS lto_placed_components) + __get_component_interface(COMPONENT "${placed_component}" OUTPUT placed_interface) + if(NOT "${placed_interface}" STREQUAL "NOTFOUND") + idf_component_set_property("${placed_component}" NO_LTO 1) + endif() + endforeach() + endif() + idf_component_get_property(component_interface "${name}" COMPONENT_INTERFACE) if(DEFINED ARG_INTERFACE) set(${ARG_INTERFACE} ${component_interface} PARENT_SCOPE) @@ -1064,6 +1101,19 @@ function(idf_component_include name) else() target_link_libraries("${component_interface}" INTERFACE "${component_real_target}") endif() + + # Compile-time LTO (CONFIG_COMPILER_LTO_COMPILETIME): compile this static + # library with -flto=auto unless it was marked NO_LTO during inclusion + # (because it, or another component, places its object code via a linker + # fragment, or it opted out explicitly). NO_LTO is read as a generator + # expression so that vetoes recorded by components processed after this + # one are still taken into account. set_compiler_lto was resolved during + # inclusion above (NO when a subproject opts out via SET_COMPILER_LTO). + if(CONFIG_COMPILER_LTO_COMPILETIME AND set_compiler_lto) + idf_component_get_property(no_lto_genex "${component_name}" NO_LTO GENERATOR_EXPRESSION) + target_compile_options("${component_real_target}" PRIVATE + "$<$>:-flto=auto>") + endif() else() idf_die("Unsupported target type '${component_real_target_type}' in component '${component_name}'") endif() diff --git a/tools/cmakev2/idf.cmake b/tools/cmakev2/idf.cmake index b19a12137b7..dfe29b70dee 100644 --- a/tools/cmakev2/idf.cmake +++ b/tools/cmakev2/idf.cmake @@ -41,6 +41,10 @@ include(${CMAKE_CURRENT_LIST_DIR}/../cmake/depgraph.cmake) # which generates per-component error code tables placed in a link-time array. include(${CMAKE_CURRENT_LIST_DIR}/../cmake/err_codes.cmake) +# The lto.cmake file from cmakev1 provides __lto_collect_fragment_placed_components(), +# used to decide which components must be excluded from link-time optimization. +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/lto.cmake) + include(component) include(build) include(kconfig) diff --git a/tools/cmakev2/project.cmake b/tools/cmakev2/project.cmake index b5f7e1f5289..bc5ae404200 100644 --- a/tools/cmakev2/project.cmake +++ b/tools/cmakev2/project.cmake @@ -385,7 +385,18 @@ function(__init_project_configuration) endif() endif() - list(APPEND link_options "-fno-lto") + # A subproject that must not use LTO (for example the bootloader) opts out by + # setting the SET_COMPILER_LTO build property to NO, the same way it uses + # SET_COMPILER_OPTIMIZATION; an unset property means LTO is allowed. + idf_build_get_property(set_compiler_lto SET_COMPILER_LTO) + if(NOT DEFINED set_compiler_lto OR set_compiler_lto STREQUAL "") + set(set_compiler_lto YES) + endif() + if(CONFIG_COMPILER_LTO_LINKTIME AND set_compiler_lto) + list(APPEND link_options "-flto=auto") + else() + list(APPEND compile_options "-fno-lto") + endif() if(CONFIG_IDF_TARGET_LINUX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") # Not all versions of the MacOS linker support the -warn_commons flag. @@ -473,16 +484,17 @@ function(__init_project_configuration) # paths back to the real paths in the filesystem. set(gdbinit_dir ${build_dir}/gdbinit) set(gdbinit_path "${gdbinit_dir}/prefix_map") + set(prefix_map_compile_options) if(CONFIG_COMPILER_HIDE_PATHS_MACROS) - list(APPEND compile_options "-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=.") - list(APPEND compile_options "-fmacro-prefix-map=${idf_path}=/IDF") + list(APPEND prefix_map_compile_options "-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=.") + list(APPEND prefix_map_compile_options "-fmacro-prefix-map=${idf_path}=/IDF") endif() if(CONFIG_APP_REPRODUCIBLE_BUILD) - list(APPEND compile_options "-fdebug-prefix-map=${idf_path}=/IDF") - list(APPEND compile_options "-fdebug-prefix-map=${project_dir}=/IDF_PROJECT") - list(APPEND compile_options "-fdebug-prefix-map=${build_dir}=/IDF_BUILD") + list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${idf_path}=/IDF") + list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${project_dir}=/IDF_PROJECT") + list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${build_dir}=/IDF_BUILD") # Generate mapping for component paths set(gdbinit_file_lines) @@ -492,7 +504,7 @@ function(__init_project_configuration) string(TOUPPER ${component_name} component_name_uppercase) set(substituted_path "/COMPONENT_${component_name_uppercase}_DIR") - list(APPEND compile_options "-fdebug-prefix-map=${component_dir}=${substituted_path}") + list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${component_dir}=${substituted_path}") string(APPEND gdbinit_file_lines "set substitute-path ${substituted_path} ${component_dir}\n") endforeach() @@ -506,15 +518,28 @@ function(__init_project_configuration) endif() string(STRIP "${compiler_sysroot}" compiler_sysroot) get_filename_component(compiler_sysroot "${compiler_sysroot}/.." REALPATH) - list(APPEND compile_options "-fdebug-prefix-map=${compiler_sysroot}=/TOOLCHAIN") + list(APPEND prefix_map_compile_options "-fdebug-prefix-map=${compiler_sysroot}=/TOOLCHAIN") string(APPEND gdbinit_file_lines "set substitute-path /TOOLCHAIN ${compiler_sysroot}\n") else() set(gdbinit_file_lines "# There is no prefix map defined for the project.\n") endif() + list(APPEND compile_options ${prefix_map_compile_options}) # Write the prefix_map file even if it is empty. file(MAKE_DIRECTORY ${gdbinit_dir}) file(WRITE "${gdbinit_path}" "${gdbinit_file_lines}") + if(CONFIG_APP_REPRODUCIBLE_BUILD AND CONFIG_COMPILER_LTO_LINKTIME AND set_compiler_lto) + # 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 of + # "feat(build): add options to enable link-time optimization (LTO)" + # for details. + list(APPEND link_options ${prefix_map_compile_options}) + list(APPEND link_options "-save-temps") + list(APPEND compile_options "-frandom-seed=1") + endif() + idf_build_set_property(GDBINIT_FILES_PREFIX_MAP "${gdbinit_path}") idf_build_set_property(COMPILE_OPTIONS "${compile_options}" APPEND) idf_build_set_property(C_COMPILE_OPTIONS "${c_compile_options}" APPEND) diff --git a/tools/test_apps/system/startup/pytest_startup.py b/tools/test_apps/system/startup/pytest_startup.py index 3a3dea1905c..1f0c638ce6f 100644 --- a/tools/test_apps/system/startup/pytest_startup.py +++ b/tools/test_apps/system/startup/pytest_startup.py @@ -14,6 +14,7 @@ from pytest_embedded_idf.utils import idf_parametrize ('no_vfs_partial', 'supported_targets'), ('opt_o0', 'supported_targets'), ('opt_o2', 'supported_targets'), + ('opt_os_lto', 'supported_targets'), ('stack_check_verbose_log', 'supported_targets'), ('sram1_iram', 'esp32'), ('main_task_cpu1', 'esp32'), diff --git a/tools/test_apps/system/startup/sdkconfig.ci.opt_os_lto b/tools/test_apps/system/startup/sdkconfig.ci.opt_os_lto new file mode 100644 index 00000000000..e694d554057 --- /dev/null +++ b/tools/test_apps/system/startup/sdkconfig.ci.opt_os_lto @@ -0,0 +1,3 @@ +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_LTO_LINKTIME=y +CONFIG_COMPILER_LTO_COMPILETIME=y