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/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)