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:
Alexey Gerenkov
2026-08-04 20:05:40 +08:00
16 changed files with 239 additions and 0 deletions

22
Kconfig
View File

@@ -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

View File

@@ -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()

View 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
View 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."

View 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()

View File

@@ -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

View File

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

View 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``.

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -0,0 +1 @@
CONFIG_COMPILER_CXX_ATOMIC_LOCK_POLICY=y

View File

@@ -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::

View File

@@ -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::

View 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'
)