mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
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.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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."
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user