mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'feature/enable_atomic_lock_policy_on_riscv' into 'master'
feat(build): add CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY option See merge request espressif/esp-idf!49913
This commit is contained in:
22
Kconfig
22
Kconfig
@@ -889,6 +889,28 @@ mainmenu "Espressif IoT Development Framework Configuration"
|
||||
Define _GLIBCXX23_CONSTEXPR=__attribute__((cold)).
|
||||
endchoice
|
||||
|
||||
config COMPILER_CXX_ATOMIC_LOCK_POLICY
|
||||
bool "Force libstdc++ lock-free atomic policy"
|
||||
default n
|
||||
depends on IDF_TARGET_ARCH_RISCV && !IDF_TARGET_ESP32C2 && !IDF_TARGET_ESP32C3
|
||||
help
|
||||
Define _GLIBCXX_HAVE_ATOMIC_LOCK_POLICY=1 when compiling C++ code.
|
||||
|
||||
libstdc++ selects between a lock-free and a mutex-based policy for
|
||||
std::shared_ptr reference counting based on this macro. Enabling this
|
||||
option forces the lock-free policy, which avoids pulling in the
|
||||
mutex-based implementation and can reduce code size.
|
||||
|
||||
Note: the lock-free policy avoids embedding a mutex in each control
|
||||
block, saving about 88 bytes per shared_ptr, and improves performance
|
||||
by using atomic operations instead of mutex locking.
|
||||
|
||||
Note: this option changes the ABI of libstdc++ atomic/shared_ptr
|
||||
helpers. It can be incompatible when linking against prebuilt
|
||||
libraries that were compiled without it, leading to link errors or
|
||||
undefined runtime behavior. Only enable it if all C++ objects and
|
||||
libraries in the build use the same setting.
|
||||
|
||||
config COMPILER_KASAN
|
||||
bool "Enable Kernel Address Sanitizer (KASAN)"
|
||||
depends on IDF_EXPERIMENTAL_FEATURES && IDF_TOOLCHAIN_GCC && !IDF_TARGET_LINUX
|
||||
|
||||
@@ -94,3 +94,36 @@ target_link_libraries(${COMPONENT_LIB} PUBLIC libgcc_cxx)
|
||||
if(NOT CONFIG_COMPILER_CXX_EXCEPTIONS)
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u __cxx_fatal_exception")
|
||||
endif()
|
||||
|
||||
# When the lock-free atomic policy is forced, verify the final executable does
|
||||
# not end up with mixed __gnu_cxx::_Lock_policy instantiations (which can happen
|
||||
# when linking prebuilt libraries built with a different setting).
|
||||
if(CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY)
|
||||
if(IDF_BUILD_V2)
|
||||
function(cxx_check_atomic_policy target)
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DELF_FILE=$<TARGET_FILE:${target}>
|
||||
-DCMAKE_OBJDUMP=${CMAKE_OBJDUMP}
|
||||
-P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/check_cxx_atomic_policy.cmake"
|
||||
COMMENT "Checking C++ atomic lock policy in linked executable"
|
||||
VERBATIM)
|
||||
endfunction()
|
||||
idf_component_register_build_event_callback(EVENT POST_ELF CALLBACK cxx_check_atomic_policy)
|
||||
else()
|
||||
idf_build_get_property(build_dir BUILD_DIR)
|
||||
idf_build_get_property(elf_target EXECUTABLE GENERATOR_EXPRESSION)
|
||||
set(_cxx_atomic_policy_check_marker "${build_dir}/.cxx_atomic_policy_checked")
|
||||
add_custom_command(OUTPUT "${_cxx_atomic_policy_check_marker}"
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DELF_FILE=$<TARGET_FILE:$<GENEX_EVAL:${elf_target}>>
|
||||
-DCMAKE_OBJDUMP=${CMAKE_OBJDUMP}
|
||||
-P "${CMAKE_CURRENT_LIST_DIR}/check_cxx_atomic_policy.cmake"
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${_cxx_atomic_policy_check_marker}"
|
||||
DEPENDS "$<TARGET_FILE:$<GENEX_EVAL:${elf_target}>>"
|
||||
COMMENT "Checking C++ atomic lock policy in linked executable"
|
||||
VERBATIM)
|
||||
add_custom_target(cxx_check_atomic_policy DEPENDS "${_cxx_atomic_policy_check_marker}")
|
||||
idf_build_add_post_elf_dependency("${CMAKE_PROJECT_NAME}.elf" cxx_check_atomic_policy)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
60
components/cxx/check_cxx_atomic_policy.cmake
Normal file
60
components/cxx/check_cxx_atomic_policy.cmake
Normal file
@@ -0,0 +1,60 @@
|
||||
# check_cxx_atomic_policy.cmake
|
||||
#
|
||||
# Post-build check that verifies the linked executable uses a single libstdc++
|
||||
# atomic lock policy (__gnu_cxx::_Lock_policy).
|
||||
#
|
||||
# libstdc++ selects the policy used for std::shared_ptr reference counting based
|
||||
# on _GLIBCXX_HAVE_ATOMIC_LOCK_POLICY. Mixing object files or prebuilt libraries
|
||||
# that were compiled with different settings results in different _Lock_policy
|
||||
# template instantiations being linked into the same image, which is an ODR
|
||||
# violation and can cause undefined behavior at runtime.
|
||||
#
|
||||
# The policy is encoded in mangled symbol names, e.g. the substring
|
||||
# "_Lock_policyE2" (atomic) or "_Lock_policyE1" (mutex). If the symbol table
|
||||
# contains more than one distinct value, the policies are mixed.
|
||||
#
|
||||
# Invoked via "cmake -P" with:
|
||||
# ELF_FILE - path to the linked executable
|
||||
# CMAKE_OBJDUMP - objdump executable to inspect the symbol table
|
||||
|
||||
if(NOT DEFINED ELF_FILE)
|
||||
message(FATAL_ERROR "check_cxx_atomic_policy.cmake: ELF_FILE is not set")
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_OBJDUMP)
|
||||
message(WARNING "check_cxx_atomic_policy.cmake: CMAKE_OBJDUMP is not set, skipping check")
|
||||
return()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_OBJDUMP}" -t "${ELF_FILE}"
|
||||
OUTPUT_VARIABLE objdump_output
|
||||
ERROR_VARIABLE objdump_error
|
||||
RESULT_VARIABLE objdump_result
|
||||
)
|
||||
|
||||
if(NOT objdump_result EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"check_cxx_atomic_policy.cmake: failed to run objdump on '${ELF_FILE}': ${objdump_error}")
|
||||
endif()
|
||||
|
||||
string(REGEX MATCHALL "_Lock_policyE[0-9]" lock_policy_matches "${objdump_output}")
|
||||
|
||||
set(lock_policies "")
|
||||
foreach(match IN LISTS lock_policy_matches)
|
||||
string(REGEX REPLACE "^.*_Lock_policyE([0-9])$" "\\1" policy "${match}")
|
||||
list(APPEND lock_policies "${policy}")
|
||||
endforeach()
|
||||
|
||||
if(lock_policies)
|
||||
list(REMOVE_DUPLICATES lock_policies)
|
||||
list(SORT lock_policies)
|
||||
endif()
|
||||
list(LENGTH lock_policies num_policies)
|
||||
|
||||
if(num_policies GREATER 1)
|
||||
string(REPLACE ";" ", " policies_str "${lock_policies}")
|
||||
message(FATAL_ERROR
|
||||
"Mixed libstdc++ atomic lock policies detected in '${ELF_FILE}' "
|
||||
"(__gnu_cxx::_Lock_policy values: ${policies_str}).")
|
||||
endif()
|
||||
3
components/cxx/hints.yml
Normal file
3
components/cxx/hints.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
-
|
||||
re: "Mixed libstdc\\+\\+ atomic lock policies detected"
|
||||
hint: "Some object files or prebuilt libraries were compiled with a different _GLIBCXX_HAVE_ATOMIC_LOCK_POLICY setting than the rest of the project. std::shared_ptr reference counting may exhibit undefined behavior. Rebuild all C++ code and libraries with a consistent CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY setting."
|
||||
7
components/cxx/project_include.cmake
Normal file
7
components/cxx/project_include.cmake
Normal file
@@ -0,0 +1,7 @@
|
||||
if(NOT CONFIG_IDF_TARGET_LINUX)
|
||||
if(CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY)
|
||||
idf_toolchain_add_flags(CXX_COMPILE_OPTIONS "-D_GLIBCXX_HAVE_ATOMIC_LOCK_POLICY=1")
|
||||
else()
|
||||
idf_toolchain_remove_flags(CXX_COMPILE_OPTIONS "-D_GLIBCXX_HAVE_ATOMIC_LOCK_POLICY=1")
|
||||
endif()
|
||||
endif()
|
||||
@@ -9,3 +9,10 @@ components/cxx/test_apps:
|
||||
- cxx
|
||||
- pthread
|
||||
- freertos
|
||||
|
||||
components/cxx/test_apps/atomic_lock_policy:
|
||||
enable:
|
||||
- if: IDF_TARGET == "esp32c5"
|
||||
disable:
|
||||
- if: IDF_TARGET != "esp32c5"
|
||||
reason: only built on ESP32-C5
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
set(COMPONENTS main)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(test_cxx_atomic_lock_policy)
|
||||
12
components/cxx/test_apps/atomic_lock_policy/README.md
Normal file
12
components/cxx/test_apps/atomic_lock_policy/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
| Supported Targets | ESP32-C5 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
# C++ atomic lock policy test
|
||||
|
||||
Build-only app. Validates ``CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY``: both
|
||||
translation units use ``std::shared_ptr`` under the forced lock-free policy, and
|
||||
the cxx post-build hook accepts the linked executable.
|
||||
|
||||
For the negative build-system check (mixed ``__gnu_cxx::_Lock_policy``), see
|
||||
``tools/test_build_system/test_cxx_atomic_policy.py``, which builds this app with
|
||||
``-DTEST_INVALID_LOCK_POLICIES=1``.
|
||||
@@ -0,0 +1,12 @@
|
||||
idf_component_register(SRCS "test_cxx_atomic_lock_policy.cpp"
|
||||
"cxx_atomic_a.cpp"
|
||||
"cxx_atomic_b.cpp")
|
||||
|
||||
# Default build: both TUs inherit CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY.
|
||||
# With -DTEST_INVALID_LOCK_POLICIES=1, undefine the macro for one TU so the
|
||||
# linked image mixes __gnu_cxx::_Lock_policy instantiations; the cxx post-build
|
||||
# hook (check_cxx_atomic_policy.cmake) must then fail the build.
|
||||
if(TEST_INVALID_LOCK_POLICIES)
|
||||
set_source_files_properties("cxx_atomic_b.cpp" PROPERTIES
|
||||
COMPILE_OPTIONS "-U_GLIBCXX_HAVE_ATOMIC_LOCK_POLICY")
|
||||
endif()
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <memory>
|
||||
|
||||
// Compiled with the project-wide atomic lock policy (CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY).
|
||||
// std::make_shared instantiates the __gnu_cxx::_Lock_policy-parameterized
|
||||
// reference counting templates.
|
||||
int cxx_atomic_use_a(void)
|
||||
{
|
||||
auto p = std::make_shared<int>(1);
|
||||
return *p;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <memory>
|
||||
|
||||
// By default compiled with the same project-wide atomic lock policy as
|
||||
// cxx_atomic_a.cpp. When TEST_INVALID_LOCK_POLICIES is set, main/CMakeLists.txt
|
||||
// adds -U_GLIBCXX_HAVE_ATOMIC_LOCK_POLICY so this TU instantiates a different
|
||||
// __gnu_cxx::_Lock_policy.
|
||||
int cxx_atomic_use_b(void)
|
||||
{
|
||||
auto p = std::make_shared<int>(2);
|
||||
return *p;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
int cxx_atomic_use_a(void);
|
||||
int cxx_atomic_use_b(void);
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
// Reference both translation units so the linker keeps their
|
||||
// _Lock_policy instantiations (needed for the invalid-policy build check).
|
||||
volatile int r = cxx_atomic_use_a() + cxx_atomic_use_b();
|
||||
printf("cxx atomic lock policy: %d\n", r);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY=y
|
||||
@@ -160,6 +160,7 @@ There are some ESP-IDF configuration options that can reduce heap usage at runti
|
||||
:esp32: - In single-core mode only, it is possible to use IRAM as byte-accessible memory added to the regular heap by enabling :ref:`CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY`. Note that this option carries a performance penalty, and the risk of security issues caused by executable data. If this option is enabled, then it is possible to set other options to prefer certain buffers allocated from this memory: :ref:`CONFIG_MBEDTLS_MEM_ALLOC_MODE`, :ref:`NimBLE <CONFIG_BT_NIMBLE_MEM_ALLOC_MODE>`.
|
||||
:esp32: - Reduce :ref:`CONFIG_BTDM_CTRL_BLE_MAX_CONN` if using Bluetooth LE.
|
||||
:esp32: - Reduce :ref:`CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN` if using Bluetooth Classic.
|
||||
:CONFIG_IDF_TARGET_ARCH_RISCV and not esp32c2 and not esp32c3: - Enabling :ref:`CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY` forces libstdc++ lock-free ``std::shared_ptr`` reference counting instead of a mutex-based implementation, avoiding an embedded mutex in each control block and saving about 88 bytes of heap per ``shared_ptr``. All C++ object files and linked libraries must use the same setting; see the config help for ABI details.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ ESP-IDF 包含一系列堆 API,可以在运行时测量空闲堆内存,请
|
||||
:esp32: - 仅在单核模式下,启用 :ref:`CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY`,可以将 IRAM 作为可按字节访问的内存添加到常规堆内存中使用。注意,此选项会影响性能,并存在由可执行数据引发安全问题的风险。若启用此选项,可以通过设置 :ref:`CONFIG_MBEDTLS_MEM_ALLOC_MODE` 和 :ref:`CONFIG_BT_NIMBLE_MEM_ALLOC_MODE` 选项,优先从内存中分配某些缓冲区。
|
||||
:esp32: - 若使用 Bluetooth LE,请优化 :ref:`CONFIG_BTDM_CTRL_BLE_MAX_CONN`。
|
||||
:esp32: - 若使用经典蓝牙,请优化 :ref:`CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN`。
|
||||
:CONFIG_IDF_TARGET_ARCH_RISCV and not esp32c2 and not esp32c3: - 启用 :ref:`CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY` 可强制 libstdc++ 对 ``std::shared_ptr`` 引用计数使用无锁实现,而非基于互斥锁的实现,从而避免在每个控制块中嵌入互斥锁,每个 ``shared_ptr`` 约可节省 88 字节堆内存。所有 C++ 目标文件及链接的库必须使用相同设置;有关 ABI 详情,请参阅该配置项的帮助说明。
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
25
tools/test_build_system/test_cxx_atomic_policy.py
Normal file
25
tools/test_build_system/test_cxx_atomic_policy.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import pytest
|
||||
from test_build_system_helpers import IdfPyFunc
|
||||
|
||||
CXX_ATOMIC_POLICY_TEST_APP = 'components/cxx/test_apps/atomic_lock_policy'
|
||||
|
||||
|
||||
@pytest.mark.test_app_copy(CXX_ATOMIC_POLICY_TEST_APP)
|
||||
@pytest.mark.usefixtures('test_app_copy')
|
||||
def test_cxx_atomic_policy_checking(idf_py: IdfPyFunc) -> None:
|
||||
"""
|
||||
Build the atomic_lock_policy test app with -DTEST_INVALID_LOCK_POLICIES=1.
|
||||
|
||||
CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY adds -D_GLIBCXX_HAVE_ATOMIC_LOCK_POLICY=1
|
||||
to all C++ files, but TEST_INVALID_LOCK_POLICIES undefines it for one
|
||||
translation unit. The linked executable therefore contains mixed
|
||||
__gnu_cxx::_Lock_policy instantiations, and the cxx post-build hook
|
||||
(check_cxx_atomic_policy.cmake) must fail the build.
|
||||
"""
|
||||
ret = idf_py('-DIDF_TARGET=esp32c5', '-DTEST_INVALID_LOCK_POLICIES=1', 'build', check=False)
|
||||
assert ret.returncode != 0, 'Build must fail when atomic lock policies are mixed'
|
||||
assert 'Mixed libstdc++ atomic lock policies detected' in (ret.stdout + ret.stderr), (
|
||||
'Expected the cxx post-build hook to report mixed atomic lock policies'
|
||||
)
|
||||
Reference in New Issue
Block a user