From df71a35360835d242dd0ff30204b1eda19759633 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Tue, 25 Mar 2025 14:28:46 -0700 Subject: [PATCH 1/2] cmake: Pass optimization flag to linker too The linker may want to know the desired optimization level as when the multilib configuration includes -Os as a selector. Adopt the same pattern as the assembler optimization flags by allowing toolchains to specify linker optimization flags which default to the compiler optimization flags and the apply those using add_link_options. Signed-off-by: Keith Packard --- CMakeLists.txt | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7383e0bd582a..16ec1963eacdc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,12 @@ get_property(ASM_OPTIMIZE_FOR_SPEED_FLAG TARGET asm PROPERTY optimization_speed) get_property(ASM_OPTIMIZE_FOR_SIZE_FLAG TARGET asm PROPERTY optimization_size) get_property(ASM_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG TARGET asm PROPERTY optimization_size_aggressive) +get_property(LINKER_OPTIMIZE_FOR_NO_OPTIMIZATIONS_FLAG TARGET linker PROPERTY no_optimization) +get_property(LINKER_OPTIMIZE_FOR_DEBUG_FLAG TARGET linker PROPERTY optimization_debug) +get_property(LINKER_OPTIMIZE_FOR_SPEED_FLAG TARGET linker PROPERTY optimization_speed) +get_property(LINKER_OPTIMIZE_FOR_SIZE_FLAG TARGET linker PROPERTY optimization_size) +get_property(LINKER_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG TARGET linker PROPERTY optimization_size_aggressive) + # Let the assembler inherit the optimization flags of the compiler if it is # not set explicitly. if(NOT ASM_OPTIMIZE_FOR_NO_OPTIMIZATIONS_FLAG) @@ -244,23 +250,46 @@ if(NOT ASM_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG) set(ASM_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG ${COMPILER_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG}) endif() +# Let the linker inherit the optimization flags of the compiler if it is +# not set explicitly. +if(NOT LINKER_OPTIMIZE_FOR_NO_OPTIMIZATIONS_FLAG) + set(LINKER_OPTIMIZE_FOR_NO_OPTIMIZATIONS_FLAG ${COMPILER_OPTIMIZE_FOR_NO_OPTIMIZATIONS_FLAG}) +endif() +if(NOT LINKER_OPTIMIZE_FOR_DEBUG_FLAG) + set(LINKER_OPTIMIZE_FOR_DEBUG_FLAG ${COMPILER_OPTIMIZE_FOR_DEBUG_FLAG}) +endif() +if(NOT LINKER_OPTIMIZE_FOR_SPEED_FLAG) + set(LINKER_OPTIMIZE_FOR_SPEED_FLAG ${COMPILER_OPTIMIZE_FOR_SPEED_FLAG}) +endif() +if(NOT LINKER_OPTIMIZE_FOR_SIZE_FLAG) + set(LINKER_OPTIMIZE_FOR_SIZE_FLAG ${COMPILER_OPTIMIZE_FOR_SIZE_FLAG}) +endif() +if(NOT LINKER_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG) + set(LINKER_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG ${COMPILER_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG}) +endif() + # From kconfig choice, pick the actual OPTIMIZATION_FLAG to use. # Kconfig choice ensures only one of these CONFIG_*_OPTIMIZATIONS is set. if(CONFIG_NO_OPTIMIZATIONS) set(COMPILER_OPTIMIZATION_FLAG ${COMPILER_OPTIMIZE_FOR_NO_OPTIMIZATIONS_FLAG}) set(ASM_OPTIMIZATION_FLAG ${ASM_OPTIMIZE_FOR_NO_OPTIMIZATIONS_FLAG}) + set(LINKER_OPTIMIZATION_FLAG ${LINKER_OPTIMIZE_FOR_NO_OPTIMIZATIONS_FLAG}) elseif(CONFIG_DEBUG_OPTIMIZATIONS) set(COMPILER_OPTIMIZATION_FLAG ${COMPILER_OPTIMIZE_FOR_DEBUG_FLAG}) set(ASM_OPTIMIZATION_FLAG ${ASM_OPTIMIZE_FOR_DEBUG_FLAG}) + set(LINKER_OPTIMIZATION_FLAG ${LINKER_OPTIMIZE_FOR_DEBUG_FLAG}) elseif(CONFIG_SPEED_OPTIMIZATIONS) set(COMPILER_OPTIMIZATION_FLAG ${COMPILER_OPTIMIZE_FOR_SPEED_FLAG}) set(ASM_OPTIMIZATION_FLAG ${ASM_OPTIMIZE_FOR_SPEED_FLAG}) + set(LINKER_OPTIMIZATION_FLAG ${LINKER_OPTIMIZE_FOR_SPEED_FLAG}) elseif(CONFIG_SIZE_OPTIMIZATIONS) set(COMPILER_OPTIMIZATION_FLAG ${COMPILER_OPTIMIZE_FOR_SIZE_FLAG}) # Default in kconfig set(ASM_OPTIMIZATION_FLAG ${ASM_OPTIMIZE_FOR_SIZE_FLAG}) + set(LINKER_OPTIMIZATION_FLAG ${LINKER_OPTIMIZE_FOR_SIZE_FLAG}) elseif(CONFIG_SIZE_OPTIMIZATIONS_AGGRESSIVE) set(COMPILER_OPTIMIZATION_FLAG ${COMPILER_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG}) set(ASM_OPTIMIZATION_FLAG ${ASM_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG}) + set(LINKER_OPTIMIZATION_FLAG ${LINKER_OPTIMIZE_FOR_SIZE_AGGRESSIVE_FLAG}) else() message(FATAL_ERROR "Unreachable code. Expected optimization level to have been chosen. See Kconfig.zephyr") @@ -277,6 +306,7 @@ endif() zephyr_compile_options($<$:${ASM_OPTIMIZATION_FLAG}>) zephyr_compile_options($<$:${COMPILER_OPTIMIZATION_FLAG}>) zephyr_compile_options($<$:${COMPILER_OPTIMIZATION_FLAG}>) +add_link_options(${LINKER_OPTIMIZATION_FLAG}) if(CONFIG_LTO) zephyr_compile_options($) From 19cc9f04980ddc3bfcf3cf36e3e2bea49306b470 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Mon, 11 Aug 2025 11:51:02 -0700 Subject: [PATCH 2/2] cmake: Delay computation of linker paths until needed With inclusion of the optimization flag into the multilib selection process, we cannot compute the compiler library path when the compiler's target.cmake is processed as LINKER_OPTIMIZATION_FLAG is not computed until much later. Instead, add a function (compiler_file_path) which can be used to locate the appropriate crtbegin.o and crtend.o files. Delay computation of lib_include_dir and rt_library until after all compiler flags have been computed by adding compiler_set_linker_properties and calling that just before toolchain_linker_finalize is invoked. Place default implementations of both of these functions in a new file, cmake/compiler/target_template.cmake, where we assume the compiler works like gcc or clang and handlers the --print-file-name and --print-libgcc-file-name options. Compilers needing alternate implementations can override these functions in their target.cmake files. These implementations require that no generator expressions are necessary for the compiler to compute the right library paths. This mechanism is also used to take any additional compiler options by adding a new (optional) linker function, toolchain_linker_add_compiler_options, which maps compiler options to equivalent linker options, discarding any that aren't applicable. Signed-off-by: Keith Packard --- CMakeLists.txt | 5 ++ cmake/compiler/clang/target.cmake | 15 ---- cmake/compiler/gcc/target.cmake | 15 ---- cmake/compiler/icx/target.cmake | 15 ---- cmake/compiler/target_template.cmake | 108 +++++++++++++++++++++++++++ cmake/compiler/xcc/target.cmake | 11 --- cmake/linker/ld/target.cmake | 14 +++- cmake/linker/lld/target.cmake | 8 ++ cmake/linker/target_template.cmake | 10 +++ cmake/linker/xt-ld/target.cmake | 14 +++- cmake/modules/FindTargetTools.cmake | 1 + 11 files changed, 156 insertions(+), 60 deletions(-) create mode 100644 cmake/compiler/target_template.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 16ec1963eacdc..7fb0f6d9870d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -307,6 +307,8 @@ zephyr_compile_options($<$:${ASM_OPTIMIZATION_FLAG}>) zephyr_compile_options($<$:${COMPILER_OPTIMIZATION_FLAG}>) zephyr_compile_options($<$:${COMPILER_OPTIMIZATION_FLAG}>) add_link_options(${LINKER_OPTIMIZATION_FLAG}) +compiler_simple_options(simple_options) +toolchain_linker_add_compiler_options(${simple_options}) if(CONFIG_LTO) zephyr_compile_options($) @@ -2401,6 +2403,9 @@ add_subdirectory_ifdef( cmake/makefile_exports ) +# Ask the compiler to set the lib_include_dir and rt_library properties +compiler_set_linker_properties() + toolchain_linker_finalize() # export build information diff --git a/cmake/compiler/clang/target.cmake b/cmake/compiler/clang/target.cmake index e3d4982726725..85eaa5a4d5481 100644 --- a/cmake/compiler/clang/target.cmake +++ b/cmake/compiler/clang/target.cmake @@ -100,21 +100,6 @@ if(NOT "${ARCH}" STREQUAL "posix") endif() endif() - # This libgcc code is partially duplicated in compiler/*/target.cmake - execute_process( - COMMAND ${CMAKE_C_COMPILER} ${clang_target_flag} ${TOOLCHAIN_C_FLAGS} - --print-libgcc-file-name - OUTPUT_VARIABLE RTLIB_FILE_NAME - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - get_filename_component(RTLIB_DIR ${RTLIB_FILE_NAME} DIRECTORY) - get_filename_component(RTLIB_NAME_WITH_PREFIX ${RTLIB_FILE_NAME} NAME_WLE) - string(REPLACE lib "" RTLIB_NAME ${RTLIB_NAME_WITH_PREFIX}) - - set_property(TARGET linker PROPERTY lib_include_dir "-L${RTLIB_DIR}") - set_property(TARGET linker PROPERTY rt_library "-l${RTLIB_NAME}") - list(APPEND CMAKE_REQUIRED_FLAGS -nostartfiles -nostdlib ${isystem_include_flags}) string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") diff --git a/cmake/compiler/gcc/target.cmake b/cmake/compiler/gcc/target.cmake index 65e37d4bc5ec4..4b20e398981c6 100644 --- a/cmake/compiler/gcc/target.cmake +++ b/cmake/compiler/gcc/target.cmake @@ -98,21 +98,6 @@ if(SYSROOT_DIR) set(LIBC_LIBRARY_DIR "\"${SYSROOT_DIR}\"/lib/${NEWLIB_DIR}") endif() -# This libgcc code is partially duplicated in compiler/*/target.cmake -execute_process( - COMMAND ${CMAKE_C_COMPILER} ${TOOLCHAIN_C_FLAGS} --print-libgcc-file-name - OUTPUT_VARIABLE LIBGCC_FILE_NAME - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - -assert_exists(LIBGCC_FILE_NAME) - -get_filename_component(LIBGCC_DIR ${LIBGCC_FILE_NAME} DIRECTORY) - -assert_exists(LIBGCC_DIR) - -set_linker_property(PROPERTY lib_include_dir "-L\"${LIBGCC_DIR}\"") - # For CMake to be able to test if a compiler flag is supported by the # toolchain we need to give CMake the necessary flags to compile and # link a dummy C file. diff --git a/cmake/compiler/icx/target.cmake b/cmake/compiler/icx/target.cmake index cd389497a5d38..9f1387e4758bb 100644 --- a/cmake/compiler/icx/target.cmake +++ b/cmake/compiler/icx/target.cmake @@ -45,21 +45,6 @@ else() list(APPEND TOOLCHAIN_C_FLAGS "-m32") endif() - -# This libgcc code is partially duplicated in compiler/*/target.cmake -execute_process( - COMMAND ${CMAKE_C_COMPILER} ${TOOLCHAIN_C_FLAGS} --print-libgcc-file-name - OUTPUT_VARIABLE LIBGCC_FILE_NAME - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - -get_filename_component(LIBGCC_DIR ${LIBGCC_FILE_NAME} DIRECTORY) - -list(APPEND LIB_INCLUDE_DIR "-L\"${LIBGCC_DIR}\"") -if(LIBGCC_DIR) - list(APPEND TOOLCHAIN_LIBS gcc) -endif() - set(CMAKE_REQUIRED_FLAGS -nostartfiles -nostdlib ${isystem_include_flags}) string(REPLACE ";" " " CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") diff --git a/cmake/compiler/target_template.cmake b/cmake/compiler/target_template.cmake new file mode 100644 index 0000000000000..0c83868beba3e --- /dev/null +++ b/cmake/compiler/target_template.cmake @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (c) 2025, Nordic Semiconductor ASA + +# Template file for optional Zephyr compiler functions. +# +# This file will define optional compiler functions for toolchains that are not +# defining these functions themselves. + +# Extract all of the compiler options which don't involve generator +# expressions. We hope that none of the flags required to compute compiler +# support library paths depend upon those. + +function(compiler_simple_options simple_options_out) + + get_property(flags TARGET zephyr_interface PROPERTY INTERFACE_COMPILE_OPTIONS) + + set(simple_options "") + + foreach(flag ${flags}) + + # Include this flag if GENEX_STRIP has no effect, + # otherwise skip the whole thing + + string(GENEX_STRIP "${flag}" sflag) + if(flag STREQUAL sflag) + if(flag MATCHES "^SHELL:[ ]*(.*)") + separate_arguments(flag UNIX_COMMAND ${CMAKE_MATCH_1}) + endif() + list(APPEND simple_options ${flag}) + endif() + + endforeach() + + set(${simple_options_out} "${simple_options}" PARENT_SCOPE) +endfunction() + +if(NOT COMMAND compiler_file_path) + + # Search for filename in default compiler library path using the + # --print-file-name option which is common to gcc and clang. If the + # file is not found, filepath_out will be set to an empty string. + # + # This only works if none of the compiler flags used to compute + # the library path involve generator expressions as we cannot + # evaluate those in this function. + # + # Compilers needing a different implementation should provide this + # function in their target.cmake file + + function(compiler_file_path filename filepath_out) + + compiler_simple_options(simple_options) + + execute_process( + COMMAND ${CMAKE_C_COMPILER} ${TOOLCHAIN_C_FLAGS} ${COMPILER_OPTIMIZATION_FLAG} ${simple_options} + --print-file-name ${filename} + OUTPUT_VARIABLE filepath + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(${filepath} STREQUAL ${filename}) + set(filepath "") + endif() + set(${filepath_out} "${filepath}" PARENT_SCOPE) + endfunction() + +endif() + +if(NOT COMMAND compiler_set_linker_properties) + + # Set the lib_include_dir and rt_library linker properties + # by searching for the runtime library in the compiler default + # library search path. If no runtime library is found, these + # properties will remain unset + # + # Compilers needing a different implementation should provide this + # function in their target.cmake file + + function(compiler_set_linker_properties) + + compiler_simple_options(simple_options) + + # Compute complete path to the runtime library using the + # --print-libgcc-file-name compiler flag + execute_process( + COMMAND ${CMAKE_C_COMPILER} ${TOOLCHAIN_C_FLAGS} ${COMPILER_OPTIMIZATION_FLAG} ${simple_options} + --print-libgcc-file-name + OUTPUT_VARIABLE library_path + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # Compute the library directory name + + get_filename_component(library_dir ${library_path} DIRECTORY) + set_linker_property(PROPERTY lib_include_dir "-L${library_dir}") + + # Compute the linker option for this library + + get_filename_component(library_basename ${library_path} NAME_WLE) + + # Remove the leading 'lib' prefix to leave a value suitable for use with + # the linker -l flag + string(REPLACE lib "" library_name ${library_basename}) + + set_linker_property(PROPERTY rt_library "-l${library_name}") + endfunction() + +endif() diff --git a/cmake/compiler/xcc/target.cmake b/cmake/compiler/xcc/target.cmake index 56d15451d3d5f..32e5e2288f32f 100644 --- a/cmake/compiler/xcc/target.cmake +++ b/cmake/compiler/xcc/target.cmake @@ -55,17 +55,6 @@ foreach(file_name include/stddef.h include-fixed/limits.h) endif() endforeach() -# This libgcc code is partially duplicated in compiler/*/target.cmake -execute_process( - COMMAND ${CMAKE_C_COMPILER} ${TOOLCHAIN_C_FLAGS} --print-libgcc-file-name - OUTPUT_VARIABLE LIBGCC_FILE_NAME - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - -get_filename_component(LIBGCC_DIR ${LIBGCC_FILE_NAME} DIRECTORY) - -list(APPEND LIB_INCLUDE_DIR "-L\"${LIBGCC_DIR}\"") - # For CMake to be able to test if a compiler flag is supported by the # toolchain we need to give CMake the necessary flags to compile and # link a dummy C file. diff --git a/cmake/linker/ld/target.cmake b/cmake/linker/ld/target.cmake index 1b3ce9ec9d816..592596576d111 100644 --- a/cmake/linker/ld/target.cmake +++ b/cmake/linker/ld/target.cmake @@ -159,15 +159,25 @@ macro(toolchain_linker_finalize) set(cpp_link "${common_link}") if(NOT "${ZEPHYR_TOOLCHAIN_VARIANT}" STREQUAL "host") - if(CONFIG_CPP_EXCEPTIONS AND LIBGCC_DIR) + compiler_file_path(crtbegin.o CRTBEGIN_PATH) + compiler_file_path(crtend.o CRTEND_PATH) + if(CONFIG_CPP_EXCEPTIONS AND CRTBEGIN_PATH AND CRTEND_PATH) # When building with C++ Exceptions, it is important that crtbegin and crtend # are linked at specific locations. - set(cpp_link " ${LIBGCC_DIR}/crtbegin.o ${link_libraries} ${LIBGCC_DIR}/crtend.o") + set(cpp_link " ${CRTBEGIN_PATH} ${link_libraries} ${CRTEND_PATH}") endif() endif() set(CMAKE_CXX_LINK_EXECUTABLE " ${cpp_link}") endmacro() +# Function to map compiler flags into suitable linker flags +# When using the compiler driver to run the linker, just pass +# them all through + +function(toolchain_linker_add_compiler_options) + add_link_options(${ARGV}) +endfunction() + # Load toolchain_ld-family macros include(${ZEPHYR_BASE}/cmake/linker/${LINKER}/target_relocation.cmake) include(${ZEPHYR_BASE}/cmake/linker/${LINKER}/target_configure.cmake) diff --git a/cmake/linker/lld/target.cmake b/cmake/linker/lld/target.cmake index b6b96525e7062..96df1c123796a 100644 --- a/cmake/linker/lld/target.cmake +++ b/cmake/linker/lld/target.cmake @@ -128,6 +128,14 @@ macro(toolchain_linker_finalize) set(CMAKE_CXX_LINK_EXECUTABLE " ${common_link}") endmacro() +# Function to map compiler flags into suitable linker flags +# When using the compiler driver to run the linker, just pass +# them all through + +function(toolchain_linker_add_compiler_options) + add_link_options(${ARGV}) +endfunction() + # Load toolchain_ld-family macros include(${ZEPHYR_BASE}/cmake/linker/ld/target_relocation.cmake) include(${ZEPHYR_BASE}/cmake/linker/ld/target_configure.cmake) diff --git a/cmake/linker/target_template.cmake b/cmake/linker/target_template.cmake index efa27de6fb4fa..59e9d8d84f0bc 100644 --- a/cmake/linker/target_template.cmake +++ b/cmake/linker/target_template.cmake @@ -11,3 +11,13 @@ if(NOT COMMAND toolchain_linker_finalize) macro(toolchain_linker_finalize) endmacro() endif() + +if(NOT COMMAND toolchain_linker_add_compiler_options) + + # If the linker doesn't provide a method for mapping compiler options + # to linker options, then assume we can't. This matters when the linker + # is using additional flags when computing toolchain library paths. + + function(toolchain_linker_add_compiler_options) + endfunction() +endif() diff --git a/cmake/linker/xt-ld/target.cmake b/cmake/linker/xt-ld/target.cmake index 9a6b76cda621e..bdaf9829fcdd2 100644 --- a/cmake/linker/xt-ld/target.cmake +++ b/cmake/linker/xt-ld/target.cmake @@ -11,14 +11,16 @@ find_program(CMAKE_LINKER xt-ld ${LD_SEARCH_PATH}) set_ifndef(LINKERFLAGPREFIX -Wl) -if(CONFIG_CPP_EXCEPTIONS) +compiler_file_path(crtbegin.o CRTBEGIN_PATH) +compiler_file_path(crtend.o CRTEND_PATH) +if(CONFIG_CPP_EXCEPTIONS AND CRTBEGIN_PATH AND CRTEND_PATH) # When building with C++ Exceptions, it is important that crtbegin and crtend # are linked at specific locations. # The location is so important that we cannot let this be controlled by normal # link libraries, instead we must control the link command specifically as # part of toolchain. set(CMAKE_CXX_LINK_EXECUTABLE - " ${LIBGCC_DIR}/crtbegin.o -o ${LIBGCC_DIR}/crtend.o") + " ${CRTBEGIN_PATH} -o ${CRTEND_PATH}") endif() # Run $LINKER_SCRIPT file through the C preprocessor, producing ${linker_script_gen} @@ -156,6 +158,14 @@ macro(toolchain_linker_finalize) set(CMAKE_CXX_LINK_EXECUTABLE " ${common_link}") endmacro() +# Function to map compiler flags into suitable linker flags +# When using the compiler driver to run the linker, just pass +# them all through + +function(toolchain_linker_add_compiler_options) + add_link_options(${ARGV}) +endfunction() + # xt-ld is Xtensa's own version of binutils' ld. # So we can reuse most of the ld configurations. include(${ZEPHYR_BASE}/cmake/linker/ld/target_relocation.cmake) diff --git a/cmake/modules/FindTargetTools.cmake b/cmake/modules/FindTargetTools.cmake index e35f577d69793..d5236aa8268cb 100644 --- a/cmake/modules/FindTargetTools.cmake +++ b/cmake/modules/FindTargetTools.cmake @@ -106,6 +106,7 @@ include(${ZEPHYR_BASE}/cmake/bintools/bintools_template.cmake) include(${TOOLCHAIN_ROOT}/cmake/bintools/${BINTOOLS}/target.cmake OPTIONAL) include(${TOOLCHAIN_ROOT}/cmake/linker/target_template.cmake) +include(${TOOLCHAIN_ROOT}/cmake/compiler/target_template.cmake) set(TargetTools_FOUND TRUE) set(TARGETTOOLS_FOUND TRUE)