From b3ae652f213ca17019a8698d549d934f2a6fd339 Mon Sep 17 00:00:00 2001 From: Alexey Lapshin Date: Fri, 19 Jun 2026 13:08:15 +0700 Subject: [PATCH] feat(build): add CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY option 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. --- Kconfig | 22 +++++++ components/cxx/CMakeLists.txt | 33 ++++++++++ components/cxx/check_cxx_atomic_policy.cmake | 60 +++++++++++++++++++ components/cxx/hints.yml | 3 + components/cxx/project_include.cmake | 7 +++ .../cxx/test_apps/.build-test-rules.yml | 7 +++ .../atomic_lock_policy/CMakeLists.txt | 7 +++ .../test_apps/atomic_lock_policy/README.md | 12 ++++ .../atomic_lock_policy/main/CMakeLists.txt | 12 ++++ .../atomic_lock_policy/main/cxx_atomic_a.cpp | 15 +++++ .../atomic_lock_policy/main/cxx_atomic_b.cpp | 16 +++++ .../main/test_cxx_atomic_lock_policy.cpp | 17 ++++++ .../atomic_lock_policy/sdkconfig.defaults | 1 + docs/en/api-guides/performance/ram-usage.rst | 1 + .../api-guides/performance/ram-usage.rst | 1 + .../test_cxx_atomic_policy.py | 25 ++++++++ 16 files changed, 239 insertions(+) create mode 100644 components/cxx/check_cxx_atomic_policy.cmake create mode 100644 components/cxx/hints.yml create mode 100644 components/cxx/project_include.cmake create mode 100644 components/cxx/test_apps/atomic_lock_policy/CMakeLists.txt create mode 100644 components/cxx/test_apps/atomic_lock_policy/README.md create mode 100644 components/cxx/test_apps/atomic_lock_policy/main/CMakeLists.txt create mode 100644 components/cxx/test_apps/atomic_lock_policy/main/cxx_atomic_a.cpp create mode 100644 components/cxx/test_apps/atomic_lock_policy/main/cxx_atomic_b.cpp create mode 100644 components/cxx/test_apps/atomic_lock_policy/main/test_cxx_atomic_lock_policy.cpp create mode 100644 components/cxx/test_apps/atomic_lock_policy/sdkconfig.defaults create mode 100644 tools/test_build_system/test_cxx_atomic_policy.py diff --git a/Kconfig b/Kconfig index 1a70c322166..1c2a78144c0 100644 --- a/Kconfig +++ b/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 diff --git a/components/cxx/CMakeLists.txt b/components/cxx/CMakeLists.txt index a16ca7e941e..01c26b4c9ba 100644 --- a/components/cxx/CMakeLists.txt +++ b/components/cxx/CMakeLists.txt @@ -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=$ + -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=$> + -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 "$>" + 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() diff --git a/components/cxx/check_cxx_atomic_policy.cmake b/components/cxx/check_cxx_atomic_policy.cmake new file mode 100644 index 00000000000..0f5c62b1080 --- /dev/null +++ b/components/cxx/check_cxx_atomic_policy.cmake @@ -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() diff --git a/components/cxx/hints.yml b/components/cxx/hints.yml new file mode 100644 index 00000000000..8099bcee2e9 --- /dev/null +++ b/components/cxx/hints.yml @@ -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." diff --git a/components/cxx/project_include.cmake b/components/cxx/project_include.cmake new file mode 100644 index 00000000000..01bd8a5013f --- /dev/null +++ b/components/cxx/project_include.cmake @@ -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() diff --git a/components/cxx/test_apps/.build-test-rules.yml b/components/cxx/test_apps/.build-test-rules.yml index b06f9d253ec..cccce1bfa83 100644 --- a/components/cxx/test_apps/.build-test-rules.yml +++ b/components/cxx/test_apps/.build-test-rules.yml @@ -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 diff --git a/components/cxx/test_apps/atomic_lock_policy/CMakeLists.txt b/components/cxx/test_apps/atomic_lock_policy/CMakeLists.txt new file mode 100644 index 00000000000..01ca0d87b75 --- /dev/null +++ b/components/cxx/test_apps/atomic_lock_policy/CMakeLists.txt @@ -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) diff --git a/components/cxx/test_apps/atomic_lock_policy/README.md b/components/cxx/test_apps/atomic_lock_policy/README.md new file mode 100644 index 00000000000..76d5838e0bb --- /dev/null +++ b/components/cxx/test_apps/atomic_lock_policy/README.md @@ -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``. diff --git a/components/cxx/test_apps/atomic_lock_policy/main/CMakeLists.txt b/components/cxx/test_apps/atomic_lock_policy/main/CMakeLists.txt new file mode 100644 index 00000000000..c14d691020f --- /dev/null +++ b/components/cxx/test_apps/atomic_lock_policy/main/CMakeLists.txt @@ -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() diff --git a/components/cxx/test_apps/atomic_lock_policy/main/cxx_atomic_a.cpp b/components/cxx/test_apps/atomic_lock_policy/main/cxx_atomic_a.cpp new file mode 100644 index 00000000000..f0b66dce4b4 --- /dev/null +++ b/components/cxx/test_apps/atomic_lock_policy/main/cxx_atomic_a.cpp @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +// 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(1); + return *p; +} diff --git a/components/cxx/test_apps/atomic_lock_policy/main/cxx_atomic_b.cpp b/components/cxx/test_apps/atomic_lock_policy/main/cxx_atomic_b.cpp new file mode 100644 index 00000000000..c0656f2ff79 --- /dev/null +++ b/components/cxx/test_apps/atomic_lock_policy/main/cxx_atomic_b.cpp @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +// 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(2); + return *p; +} diff --git a/components/cxx/test_apps/atomic_lock_policy/main/test_cxx_atomic_lock_policy.cpp b/components/cxx/test_apps/atomic_lock_policy/main/test_cxx_atomic_lock_policy.cpp new file mode 100644 index 00000000000..92ac3fe5047 --- /dev/null +++ b/components/cxx/test_apps/atomic_lock_policy/main/test_cxx_atomic_lock_policy.cpp @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +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); +} diff --git a/components/cxx/test_apps/atomic_lock_policy/sdkconfig.defaults b/components/cxx/test_apps/atomic_lock_policy/sdkconfig.defaults new file mode 100644 index 00000000000..7a36f380abe --- /dev/null +++ b/components/cxx/test_apps/atomic_lock_policy/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY=y diff --git a/docs/en/api-guides/performance/ram-usage.rst b/docs/en/api-guides/performance/ram-usage.rst index 9457d6b57d1..b51ddf04dab 100644 --- a/docs/en/api-guides/performance/ram-usage.rst +++ b/docs/en/api-guides/performance/ram-usage.rst @@ -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 `. :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:: diff --git a/docs/zh_CN/api-guides/performance/ram-usage.rst b/docs/zh_CN/api-guides/performance/ram-usage.rst index cd5f1c19dd8..319cd475a6c 100644 --- a/docs/zh_CN/api-guides/performance/ram-usage.rst +++ b/docs/zh_CN/api-guides/performance/ram-usage.rst @@ -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:: diff --git a/tools/test_build_system/test_cxx_atomic_policy.py b/tools/test_build_system/test_cxx_atomic_policy.py new file mode 100644 index 00000000000..65f589cdacf --- /dev/null +++ b/tools/test_build_system/test_cxx_atomic_policy.py @@ -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' + )