mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
Merge branch 'feat/llvm-opt-integration' into 'master'
feat(compiler): add ESP-IDF LLVM optimization enablement framework See merge request espressif/esp-idf!51154
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/llvm_optimizations.cmake)
|
||||
|
||||
#
|
||||
# Internal function for retrieving component properties from a component target.
|
||||
#
|
||||
@@ -440,8 +442,9 @@ endfunction()
|
||||
# @param[in, optional] KCONFIG (single value) override the default Kconfig
|
||||
# @param[in, optional] KCONFIG_PROJBUILD (single value) override the default Kconfig
|
||||
# @param[in, optional] WHOLE_ARCHIVE (option) link the component as --whole-archive
|
||||
# @param[in, optional] ENABLE_LLVM_OPT (option) enable the LLVM optimizations selected in menuconfig
|
||||
function(idf_component_register)
|
||||
set(options WHOLE_ARCHIVE)
|
||||
set(options WHOLE_ARCHIVE ENABLE_LLVM_OPT)
|
||||
set(single_value KCONFIG KCONFIG_PROJBUILD)
|
||||
set(multi_value SRCS SRC_DIRS EXCLUDE_SRCS
|
||||
INCLUDE_DIRS PRIV_INCLUDE_DIRS LDFRAGMENTS REQUIRES
|
||||
@@ -487,9 +490,16 @@ function(idf_component_register)
|
||||
|
||||
idf_build_get_property(config_dir CONFIG_DIR)
|
||||
|
||||
# Publish IDF_LLVM_OPT_* even when this component does not opt in, so the
|
||||
# application can apply them to a third-party target with standard CMake.
|
||||
__idf_llvm_opt_publish_flags()
|
||||
|
||||
# The contents of 'sources' is from the __component_add_sources call
|
||||
if(sources OR __EMBED_FILES OR __EMBED_TXTFILES)
|
||||
add_library(${component_lib} STATIC ${sources})
|
||||
if(__ENABLE_LLVM_OPT)
|
||||
__idf_apply_llvm_opt_to_target(${component_lib})
|
||||
endif()
|
||||
__component_set_property(${component_target} COMPONENT_TYPE LIBRARY)
|
||||
__component_add_include_dirs(${component_lib} "${__INCLUDE_DIRS}" PUBLIC)
|
||||
__component_add_include_dirs(${component_lib} "${__PRIV_INCLUDE_DIRS}" PRIVATE)
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# ESP-IDF LLVM optimization framework: map menuconfig-selected optimizations to
|
||||
# compiler flags. Component CMakeLists only mark opt-in scopes (file / list /
|
||||
# component). Flags for each menuconfig option are also published as
|
||||
# IDF_LLVM_OPT_* for standard CMake (third-party).
|
||||
|
||||
function(__idf_llvm_opt_collect_flags memcpy_out all_out)
|
||||
set(memcpy "")
|
||||
set(all "")
|
||||
|
||||
if(CONFIG_IDF_TOOLCHAIN_CLANG AND CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||
if(CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION)
|
||||
list(APPEND memcpy "-mllvm=-riscv-esp32-p4-mem-intrin")
|
||||
# PIE 2.1 vs 2.2 comes from -mcpu (components/soc/project_include.cmake).
|
||||
# +espv-lowering is still required for .m intrinsic isel (vld/vst.128.ip.m).
|
||||
# Do not pass +xespv2p1: that name is not a Clang target-feature and is ignored.
|
||||
list(APPEND memcpy "-Xclang" "-target-feature" "-Xclang" "+espv-lowering")
|
||||
endif()
|
||||
list(APPEND all ${memcpy})
|
||||
endif()
|
||||
|
||||
set(${memcpy_out} "${memcpy}" PARENT_SCOPE)
|
||||
set(${all_out} "${all}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# SHELL-wrapped lists for target_compile_options. Empty when Kconfig is off or not Clang.
|
||||
function(__idf_llvm_opt_publish_flags)
|
||||
__idf_llvm_opt_collect_flags(memcpy_flags all_flags)
|
||||
__idf_llvm_opt_shell_wrap_xclang("${memcpy_flags}" memcpy_wrapped)
|
||||
__idf_llvm_opt_shell_wrap_xclang("${all_flags}" all_wrapped)
|
||||
set(IDF_LLVM_OPT_MEMCPY "${memcpy_wrapped}" CACHE INTERNAL
|
||||
"Flags for CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION; empty if off or not Clang" FORCE)
|
||||
set(IDF_LLVM_OPT_ALL "${all_wrapped}" CACHE INTERNAL
|
||||
"Flags for all LLVM optimizations enabled in menuconfig; empty if none or not Clang" FORCE)
|
||||
endfunction()
|
||||
|
||||
function(__idf_llvm_opt_get_options output)
|
||||
set(options ${ARGN})
|
||||
__idf_llvm_opt_publish_flags()
|
||||
|
||||
if(NOT CONFIG_IDF_TOOLCHAIN_CLANG OR NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||
set(${output} "" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
__idf_llvm_opt_collect_flags(_memcpy all_flags)
|
||||
list(APPEND options ${all_flags})
|
||||
set(${output} "${options}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# target_compile_options de-duplicates identical tokens, which splits
|
||||
# "-Xclang" "<arg>" pairs. SHELL: keeps each consecutive -Xclang group intact.
|
||||
# Source COMPILE_OPTIONS are not de-duplicated and do not honour SHELL:.
|
||||
function(__idf_llvm_opt_shell_wrap_xclang input output)
|
||||
set(result "")
|
||||
set(i 0)
|
||||
list(LENGTH input n)
|
||||
while(i LESS n)
|
||||
list(GET input ${i} item)
|
||||
if(item STREQUAL "-Xclang")
|
||||
set(group "")
|
||||
while(i LESS n)
|
||||
list(GET input ${i} tok)
|
||||
if(NOT tok STREQUAL "-Xclang")
|
||||
break()
|
||||
endif()
|
||||
math(EXPR nxt "${i} + 1")
|
||||
if(nxt GREATER_EQUAL n)
|
||||
message(FATAL_ERROR "LLVM opt flags: -Xclang missing argument")
|
||||
endif()
|
||||
list(GET input ${nxt} arg)
|
||||
if(group STREQUAL "")
|
||||
set(group "-Xclang ${arg}")
|
||||
else()
|
||||
string(APPEND group " -Xclang ${arg}")
|
||||
endif()
|
||||
math(EXPR i "${nxt} + 1")
|
||||
endwhile()
|
||||
list(APPEND result "SHELL:${group}")
|
||||
else()
|
||||
list(APPEND result "${item}")
|
||||
math(EXPR i "${i} + 1")
|
||||
endif()
|
||||
endwhile()
|
||||
set(${output} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(__idf_apply_llvm_opt_to_target target)
|
||||
__idf_llvm_opt_get_options(options ${ARGN})
|
||||
if(options)
|
||||
__idf_llvm_opt_shell_wrap_xclang("${options}" options)
|
||||
target_compile_options(${target} PRIVATE ${options})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(__idf_apply_llvm_opt_to_sources)
|
||||
set(multi_value SRCS OPTIONS)
|
||||
cmake_parse_arguments(ARG "" "" "${multi_value}" ${ARGN})
|
||||
|
||||
if(ARG_UNPARSED_ARGUMENTS)
|
||||
message(FATAL_ERROR "Unknown idf_component_enable_llvm_opt arguments: ${ARG_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
|
||||
__idf_llvm_opt_get_options(options ${ARG_OPTIONS})
|
||||
if(options)
|
||||
set_property(SOURCE ${ARG_SRCS} APPEND PROPERTY COMPILE_OPTIONS ${options})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Apply menuconfig-selected LLVM optimizations to one source, a source list, or
|
||||
# the whole current component. OPTIONS is an advanced escape hatch for custom
|
||||
# Clang/LLVM optimization options and is ignored with non-Clang toolchains.
|
||||
function(idf_component_enable_llvm_opt)
|
||||
set(multi_value SRCS OPTIONS)
|
||||
cmake_parse_arguments(ARG "" "" "${multi_value}" ${ARGN})
|
||||
|
||||
if(ARG_UNPARSED_ARGUMENTS)
|
||||
message(FATAL_ERROR "Unknown idf_component_enable_llvm_opt arguments: ${ARG_UNPARSED_ARGUMENTS}")
|
||||
endif()
|
||||
|
||||
if(ARG_SRCS)
|
||||
__idf_apply_llvm_opt_to_sources(SRCS ${ARG_SRCS} OPTIONS ${ARG_OPTIONS})
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(COMPONENT_LIB AND TARGET ${COMPONENT_LIB})
|
||||
set(component_target ${COMPONENT_LIB})
|
||||
elseif(COMPONENT_TARGET AND TARGET ${COMPONENT_TARGET})
|
||||
set(component_target ${COMPONENT_TARGET})
|
||||
else()
|
||||
message(FATAL_ERROR "idf_component_enable_llvm_opt must be called after idf_component_register")
|
||||
endif()
|
||||
|
||||
__idf_apply_llvm_opt_to_target(${component_target} ${ARG_OPTIONS})
|
||||
endfunction()
|
||||
@@ -0,0 +1,93 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
# Never compiles: only inspects COMPILE_OPTIONS. Host CI (Windows minimal
|
||||
# cmake jobs) has no C compiler on PATH, so skip compiler detection.
|
||||
set(CMAKE_C_COMPILER "${CMAKE_COMMAND}" CACHE FILEPATH "" FORCE)
|
||||
set(CMAKE_C_COMPILER_WORKS TRUE CACHE BOOL "" FORCE)
|
||||
set(CMAKE_C_COMPILER_FORCED TRUE)
|
||||
project(llvm_optimizations_test C)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../../llvm_optimizations.cmake)
|
||||
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/component.c "void component(void) {}\n")
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/selected.c "void selected(void) {}\n")
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/plain.c "void plain(void) {}\n")
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gcc.c "void gcc_source(void) {}\n")
|
||||
|
||||
set(CMAKE_C_COMPILER_ID Clang)
|
||||
set(CONFIG_IDF_TOOLCHAIN_CLANG ON)
|
||||
set(CONFIG_COMPILER_LLVM_MEMCPY_OPTIMIZATION ON)
|
||||
|
||||
add_library(component STATIC ${CMAKE_CURRENT_BINARY_DIR}/component.c)
|
||||
set(COMPONENT_LIB component)
|
||||
idf_component_enable_llvm_opt()
|
||||
get_target_property(component_options component COMPILE_OPTIONS)
|
||||
list(JOIN component_options " " component_options)
|
||||
if(NOT component_options MATCHES "riscv-esp32-p4-mem-intrin")
|
||||
message(FATAL_ERROR "Component scope is missing the selected LLVM optimization")
|
||||
endif()
|
||||
if(NOT component_options MATCHES "SHELL:")
|
||||
message(FATAL_ERROR "Component scope must SHELL-wrap -Xclang pairs")
|
||||
endif()
|
||||
if(component_options MATCHES "xespv2p1")
|
||||
message(FATAL_ERROR "PIE revision must come from -mcpu, not +xespv2p1")
|
||||
endif()
|
||||
if(NOT component_options MATCHES "espv-lowering")
|
||||
message(FATAL_ERROR "Component scope is missing +espv-lowering for .m intrinsic isel")
|
||||
endif()
|
||||
|
||||
if(NOT IDF_LLVM_OPT_ALL OR NOT IDF_LLVM_OPT_MEMCPY)
|
||||
message(FATAL_ERROR "Exported IDF_LLVM_OPT_ALL / IDF_LLVM_OPT_MEMCPY must be set when memcpy is enabled")
|
||||
endif()
|
||||
if(NOT "${IDF_LLVM_OPT_ALL}" STREQUAL "${IDF_LLVM_OPT_MEMCPY}")
|
||||
message(FATAL_ERROR "With only the memcpy optimization enabled, IDF_LLVM_OPT_ALL must equal IDF_LLVM_OPT_MEMCPY")
|
||||
endif()
|
||||
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/third_party.c "void third_party(void) {}\n")
|
||||
add_library(fake_lvgl STATIC ${CMAKE_CURRENT_BINARY_DIR}/third_party.c)
|
||||
target_compile_options(fake_lvgl PRIVATE ${IDF_LLVM_OPT_ALL})
|
||||
get_target_property(third_party_options fake_lvgl COMPILE_OPTIONS)
|
||||
list(JOIN third_party_options " " third_party_options)
|
||||
if(NOT third_party_options MATCHES "riscv-esp32-p4-mem-intrin")
|
||||
message(FATAL_ERROR "Standard CMake apply of IDF_LLVM_OPT_ALL missed the memcpy flags")
|
||||
endif()
|
||||
if(NOT third_party_options MATCHES "SHELL:")
|
||||
message(FATAL_ERROR "IDF_LLVM_OPT_ALL must be SHELL-wrapped for target_compile_options")
|
||||
endif()
|
||||
|
||||
add_library(selected STATIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}/selected.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/plain.c)
|
||||
set(COMPONENT_LIB selected)
|
||||
idf_component_enable_llvm_opt(
|
||||
SRCS ${CMAKE_CURRENT_BINARY_DIR}/selected.c
|
||||
OPTIONS "-custom-llvm-option")
|
||||
get_source_file_property(selected_options ${CMAKE_CURRENT_BINARY_DIR}/selected.c COMPILE_OPTIONS)
|
||||
get_source_file_property(plain_options ${CMAKE_CURRENT_BINARY_DIR}/plain.c COMPILE_OPTIONS)
|
||||
list(JOIN selected_options " " selected_options)
|
||||
if(NOT selected_options MATCHES "riscv-esp32-p4-mem-intrin"
|
||||
OR NOT selected_options MATCHES "custom-llvm-option")
|
||||
message(FATAL_ERROR "Source scope is missing selected or custom options")
|
||||
endif()
|
||||
if(selected_options MATCHES "SHELL:")
|
||||
message(FATAL_ERROR "Source COMPILE_OPTIONS must not use SHELL:")
|
||||
endif()
|
||||
if(plain_options)
|
||||
message(FATAL_ERROR "LLVM optimization leaked to an unselected source")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_COMPILER_ID GNU)
|
||||
unset(CONFIG_IDF_TOOLCHAIN_CLANG)
|
||||
add_library(gcc_component STATIC ${CMAKE_CURRENT_BINARY_DIR}/gcc.c)
|
||||
set(COMPONENT_LIB gcc_component)
|
||||
idf_component_enable_llvm_opt(OPTIONS "-custom-llvm-option")
|
||||
get_target_property(gcc_options gcc_component COMPILE_OPTIONS)
|
||||
if(gcc_options)
|
||||
message(FATAL_ERROR "LLVM optimization options must be ignored for non-Clang compilers")
|
||||
endif()
|
||||
if(IDF_LLVM_OPT_ALL OR IDF_LLVM_OPT_MEMCPY)
|
||||
message(FATAL_ERROR "Exported LLVM opt flags must be empty for non-Clang compilers")
|
||||
endif()
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/llvm_optimizations.cmake)
|
||||
|
||||
#[[
|
||||
check_expected_tool_version()
|
||||
|
||||
@@ -607,6 +609,7 @@ endfunction()
|
||||
[REQUIRED_IDF_TARGETS <target>...]
|
||||
[EMBED_FILES <file>...]
|
||||
[EMBED_TXTFILES <file>...]
|
||||
[ENABLE_LLVM_OPT]
|
||||
[WHOLE_ARCHIVE])
|
||||
|
||||
*SRCS[in,opt]*
|
||||
@@ -659,12 +662,16 @@ endfunction()
|
||||
|
||||
Link the component as --whole-archive.
|
||||
|
||||
*ENABLE_LLVM_OPT[in,opt]*
|
||||
|
||||
Enable the LLVM optimizations selected in menuconfig for this component.
|
||||
|
||||
Register a new component with the build system using the provided options.
|
||||
This function also automatically links all commonly required and managed
|
||||
components to the component's target.
|
||||
#]]
|
||||
function(idf_component_register)
|
||||
set(options WHOLE_ARCHIVE)
|
||||
set(options WHOLE_ARCHIVE ENABLE_LLVM_OPT)
|
||||
set(one_value KCONFIG KCONFIG_PROJBUILD)
|
||||
set(multi_value SRCS SRC_DIRS EXCLUDE_SRCS
|
||||
INCLUDE_DIRS PRIV_INCLUDE_DIRS LDFRAGMENTS REQUIRES
|
||||
@@ -737,8 +744,13 @@ function(idf_component_register)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
__idf_llvm_opt_publish_flags()
|
||||
|
||||
if(sources OR ARG_EMBED_FILES OR ARG_EMBED_TXTFILES)
|
||||
add_library("${COMPONENT_TARGET}" STATIC ${sources})
|
||||
if(ARG_ENABLE_LLVM_OPT)
|
||||
__idf_apply_llvm_opt_to_target("${COMPONENT_TARGET}")
|
||||
endif()
|
||||
|
||||
foreach(include_dir IN LISTS include_dirs)
|
||||
target_include_directories("${COMPONENT_TARGET}" PUBLIC "${include_dir}")
|
||||
|
||||
@@ -440,3 +440,14 @@ def test_cmake_preset_sdkconfig_defaults_integration(test_app_copy: Path) -> Non
|
||||
sdkconfig_content = sdkconfig_path.read_text()
|
||||
assert 'CONFIG_LWIP_IPV6=y' in sdkconfig_content
|
||||
assert 'CONFIG_ESP_TASK_WDT_TIMEOUT_S=15' in sdkconfig_content
|
||||
|
||||
|
||||
def test_cmake_llvm_optimizations_framework(idf_copy: Path) -> None:
|
||||
logging.info('Test CMake configuration of the LLVM optimization framework')
|
||||
test_project = idf_copy / 'tools' / 'cmake' / 'tests' / 'llvm_optimizations'
|
||||
# The standalone test project fakes the compiler ID and CONFIG_* variables to
|
||||
# verify component/source scope flag selection, custom options, non-leakage
|
||||
# to unselected sources, exported IDF_LLVM_OPT_* for stock CMake, and the
|
||||
# non-Clang no-op. run_cmake raises on failure.
|
||||
# Windows runners default to NMake; nmake is absent. Ninja is on PATH.
|
||||
run_cmake(str(test_project), '-G', 'Ninja')
|
||||
|
||||
Reference in New Issue
Block a user