[Lldb-commits] [lldb] [lldb] Add libstdcpp atomic data formatters (PR #174218)
Ebuka Ezike via lldb-commits
lldb-commits at lists.llvm.org
Wed Oct 28 15:06:35 PDT 2026
https://github.com/da-viper updated https://github.com/llvm/llvm-project/pull/174218
>From c74fed938ea223bbd8610a33caa67e17e9da0551 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Wed, 19 Nov 2025 18:14:43 +0000
Subject: [PATCH 1/3] [lldb] Add libstdcpp atomic data formatters - #174218
This patch adds a new synthetic child provider and a summary formatter
for the libstdc++ implementation of std::atomic<>.
# Conflicts:
# lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
# lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h
---
.../Plugins/Language/CPlusPlus/CMakeLists.txt | 1 +
.../Language/CPlusPlus/CPlusPlusLanguage.cpp | 31 +++-
.../Plugins/Language/CPlusPlus/LibStdcpp.cpp | 45 ++++-
.../Plugins/Language/CPlusPlus/LibStdcpp.h | 13 +-
.../Language/CPlusPlus/LibStdcppAtomic.cpp | 136 +++++++++++++++
.../libstdcpp-atomic/Makefile | 4 +
...stDataFormatterLibstdcppAtomicSimulator.py | 83 +++++++++
.../libstdcpp-atomic/main.cpp | 160 ++++++++++++++++++
.../generic/atomic/Makefile | 1 +
.../atomic/TestDataFormatterStdAtomic.py | 67 ++++++++
.../generic/atomic/main.cpp | 41 +++++
11 files changed, 570 insertions(+), 12 deletions(-)
create mode 100644 lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp
create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/Makefile
create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/TestDataFormatterLibstdcppAtomicSimulator.py
create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/main.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index f71a7da5616329..495e8f7dfb9fa1 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -37,6 +37,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
LibCxxValarray.cpp
LibCxxVector.cpp
LibStdcpp.cpp
+ LibStdcppAtomic.cpp
LibStdcppSpan.cpp
LibStdcppTuple.cpp
LibStdcppUniquePointer.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 283c8356354a9a..a9f560bfae3000 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1715,6 +1715,24 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
stl_summary_flags);
}
+static lldb_private::SyntheticChildrenFrontEnd *
+GenericAtomicSyntheticFrontEndCreator(CXXSyntheticChildren *children,
+ const lldb::ValueObjectSP &valobj_sp) {
+ if (!valobj_sp)
+ return nullptr;
+
+ if (IsMsvcStlAtomic(*valobj_sp))
+ return MsvcStlAtomicSyntheticFrontEndCreator(children, valobj_sp);
+ return LibStdcppAtomicSyntheticFrontEndCreator(children, valobj_sp);
+}
+
+static bool GenericAtomicSummaryProvider(ValueObject &valobj, Stream &stream,
+ const TypeSummaryOptions &options) {
+ if (IsMsvcStlAtomic(valobj))
+ return MsvcStlAtomicSummaryProvider(valobj, stream, options);
+ return LibStdcppAtomicSummaryProvider(valobj, stream, options);
+}
+
static lldb_private::SyntheticChildrenFrontEnd *
GenericSmartPointerSyntheticFrontEndCreator(CXXSyntheticChildren *children,
lldb::ValueObjectSP valobj_sp) {
@@ -2064,6 +2082,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
},
"MSVC STL/libstdc++ std::wstring_view summary provider"));
+ AddCXXSynthetic(cpp_category_sp, GenericAtomicSyntheticFrontEndCreator,
+ "std::atomic synthetic children", "^std::atomic<.+>$",
+ stl_synth_flags, true);
// NOTE: it is loaded as a common formatter because the libc++ version is not
// in the `__1` namespace, hence we need to dispatch based on the class
// layout.
@@ -2132,6 +2153,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider,
"std::initializer_list summary provider",
"^std::initializer_list<.+>$", stl_summary_flags, true);
+ AddCXXSummary(cpp_category_sp, GenericAtomicSummaryProvider,
+ "std::atomic summary provider", "^std::atomic<.+>$",
+ stl_summary_flags, true);
AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider,
"MSVC STL/libstdc++ std::shared_ptr summary provider",
"^std::shared_ptr<.+>(( )?&)?$", stl_summary_flags, true);
@@ -2280,13 +2304,6 @@ static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
stl_summary_flags.SetDontShowChildren(false);
- AddCXXSynthetic(cpp_category_sp, MsvcStlAtomicSyntheticFrontEndCreator,
- "MSVC STL std::atomic synthetic children",
- "^std::atomic<.+>$", stl_synth_flags, true);
-
- AddCXXSummary(cpp_category_sp, MsvcStlAtomicSummaryProvider,
- "MSVC STL std::atomic summary provider", "^std::atomic<.+>$",
- stl_summary_flags, true);
AddCXXSynthetic(cpp_category_sp, MsvcStlTreeIterSyntheticFrontEndCreator,
"MSVC STL tree iterator synthetic children",
"^std::_Tree(_const)?_iterator<.+>(( )?&)?$", stl_synth_flags,
diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp
index b888ee0d2f2dc8..96d171d9294000 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp
@@ -353,8 +353,49 @@ lldb_private::formatters::LibStdcppSharedPtrSyntheticFrontEndCreator(
: nullptr);
}
+static ValueObjectSP GetReferenceCountPointer(ValueObject &parent,
+ bool is_atomic_child) {
+ ValueObjectSP refcount_sp = parent.GetChildMemberWithName("_M_refcount");
+ if (!refcount_sp)
+ return nullptr;
+
+ if (!is_atomic_child)
+ return refcount_sp->GetChildMemberWithName("_M_pi");
+
+ ValueObjectSP val_sp = refcount_sp->GetChildMemberWithName("_M_val");
+ if (!val_sp || !val_sp->GetCompilerType())
+ return nullptr;
+
+ // May be a scalar value depending on libstdc++ version
+ if (!val_sp->GetCompilerType().IsScalarType()) {
+ val_sp = val_sp->GetChildMemberWithName("_M_i");
+ if (!val_sp)
+ return nullptr;
+ }
+
+ // Extract and align the raw value
+ bool is_success = false;
+ uint64_t raw_val = val_sp->GetValueAsUnsigned(0, &is_success);
+ if (!is_success || raw_val == 0)
+ return nullptr;
+ // Clear the least significant bit. See
+ // https://github.com/gcc-mirror/gcc/blob/releases/gcc-15.2.0/libstdc%2B%2B-v3/include/bits/shared_ptr_atomic.h#L465
+ raw_val &= ~1ULL;
+
+ // Get the pointer type
+ CompilerType ref_count_ptr_type =
+ refcount_sp->GetCompilerType().GetDirectNestedTypeWithName("pointer");
+ if (!ref_count_ptr_type)
+ return nullptr;
+
+ return ValueObject::CreateValueObjectFromAddress(
+ "", raw_val, parent.GetExecutionContextRef(), ref_count_ptr_type,
+ /*do_deref=*/false);
+}
+
bool lldb_private::formatters::LibStdcppSmartPointerSummaryProvider(
- ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
+ ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options,
+ bool is_atomic_child) {
ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue());
if (!valobj_sp)
return false;
@@ -365,7 +406,7 @@ bool lldb_private::formatters::LibStdcppSmartPointerSummaryProvider(
DumpCxxSmartPtrPointerSummary(stream, *ptr_sp, options);
- ValueObjectSP pi_sp = valobj_sp->GetChildAtNamePath({"_M_refcount", "_M_pi"});
+ ValueObjectSP pi_sp = GetReferenceCountPointer(*valobj_sp, is_atomic_child);
if (!pi_sp)
return false;
diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h
index 7da72177e0bc71..ec2b3585d58dd2 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h
@@ -32,9 +32,9 @@ bool LibStdcppWStringViewSummaryProvider(
const TypeSummaryOptions &options); // libstdc++ std::wstring_view
bool LibStdcppSmartPointerSummaryProvider(
- ValueObject &valobj, Stream &stream,
- const TypeSummaryOptions
- &options); // libstdc++ std::shared_ptr<> and std::weak_ptr<>
+ ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options,
+ bool is_atomic_child =
+ false); // libstdc++ std::shared_ptr<> and std::weak_ptr<>
bool LibStdcppUniquePointerSummaryProvider(
ValueObject &valobj, Stream &stream,
@@ -44,6 +44,9 @@ bool LibStdcppVariantSummaryProvider(
ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &options); // libstdc++ std::variant<>
+bool LibStdcppAtomicSummaryProvider(ValueObject &valobj, Stream &stream,
+ const TypeSummaryOptions &options);
+
bool LibStdcppPartialOrderingSummaryProvider(
ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &options); // libstdc++ std::partial_ordering
@@ -60,6 +63,10 @@ bool LibStdcppSourceLocationSummaryProvider(
ValueObject &valobj, Stream &stream,
const TypeSummaryOptions &options); // libstdc++ std::source_location
+SyntheticChildrenFrontEnd *
+LibStdcppAtomicSyntheticFrontEndCreator(CXXSyntheticChildren *,
+ const lldb::ValueObjectSP &);
+
SyntheticChildrenFrontEnd *
LibstdcppMapIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP);
diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp
new file mode 100644
index 00000000000000..60fea8d3a947a6
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp
@@ -0,0 +1,136 @@
+//===---------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "LibStdcpp.h"
+
+#include "lldb/DataFormatters/TypeSynthetic.h"
+#include "lldb/Utility/ConstString.h"
+#include "lldb/ValueObject/ValueObject.h"
+#include "lldb/lldb-enumerations.h"
+#include "llvm/Support/Error.h"
+
+using namespace lldb;
+
+namespace lldb_private::formatters {
+
+class LibStdcppAtomicSyntheticFrontEnd final
+ : public SyntheticChildrenFrontEnd {
+public:
+ explicit LibStdcppAtomicSyntheticFrontEnd(ValueObject &valobj)
+ : SyntheticChildrenFrontEnd(valobj) {}
+
+ llvm::Expected<uint32_t> CalculateNumChildren() override {
+ if (!m_inner)
+ return llvm::createStringError("Invalid atomic ValueObject.");
+ return 1;
+ }
+
+ ValueObjectSP GetChildAtIndex(uint32_t idx) override {
+ if (idx == 0 && m_inner)
+ return m_inner->GetSP()->Clone(k_inner_name);
+
+ return {};
+ }
+
+ lldb::ChildCacheState Update() override {
+ if (ValueObjectSP value = ContainerFieldName(m_backend)) {
+ // show the Type, instead of std::__atomic_base<Type>::__Type_type.
+ value = value->Cast(value->GetCompilerType().GetCanonicalType());
+ m_inner = value.get();
+ }
+
+ return lldb::ChildCacheState::eRefetch;
+ }
+
+ llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
+ if (name == k_inner_name)
+ return 0;
+
+ return llvm::createStringErrorV("Type has no child named '{}'",
+ name.GetStringRef());
+ }
+
+ static ValueObjectSP ContainerFieldName(ValueObject &backend_syn) {
+ const ValueObjectSP non_synthetic = backend_syn.GetNonSyntheticValue();
+ if (!non_synthetic)
+ return {};
+ ValueObject &backend = *non_synthetic;
+
+ const CompilerType type = backend.GetCompilerType();
+ if (!type || type.GetNumTemplateArguments() == 0)
+ return {};
+
+ const CompilerType first_type = type.GetTypeTemplateArgument(0);
+ if (!first_type)
+ return {};
+
+ if (first_type.IsBoolean())
+ return backend.GetChildAtNamePath({"_M_base", "_M_i"});
+
+ const uint32_t float_mask = lldb::eTypeIsFloat | lldb::eTypeIsBuiltIn;
+ if ((first_type.GetTypeInfo() & float_mask) == float_mask) {
+ // added float types specialization in c++20
+ if (const auto child = backend.GetChildMemberWithName("_M_fp"))
+ return child;
+
+ return backend.GetChildMemberWithName("_M_i");
+ }
+
+ if (first_type.IsPointerType())
+ return backend.GetChildAtNamePath({"_M_b", "_M_p"});
+
+ const auto first_typename = first_type.GetDisplayTypeName().GetStringRef();
+ if (first_typename.starts_with("std::shared_ptr<") ||
+ first_typename.starts_with("std::weak_ptr<"))
+ return backend.GetChildAtNamePath({"_M_impl", "_M_ptr"});
+
+ return backend.GetChildMemberWithName("_M_i");
+ }
+
+private:
+ inline static const ConstString k_inner_name = ConstString("Value");
+ ValueObject *m_inner = nullptr;
+};
+
+SyntheticChildrenFrontEnd *
+LibStdcppAtomicSyntheticFrontEndCreator(CXXSyntheticChildren * /*unused*/,
+ const lldb::ValueObjectSP &valobj_sp) {
+ if (!valobj_sp)
+ return nullptr;
+
+ const ValueObjectSP member =
+ LibStdcppAtomicSyntheticFrontEnd::ContainerFieldName(*valobj_sp);
+ if (!member)
+ return nullptr;
+
+ return new LibStdcppAtomicSyntheticFrontEnd(*valobj_sp);
+}
+
+bool LibStdcppAtomicSummaryProvider(ValueObject &valobj, Stream &stream,
+ const TypeSummaryOptions &options) {
+
+ if (const ValueObjectSP atomic_value =
+ LibStdcppAtomicSyntheticFrontEnd::ContainerFieldName(valobj)) {
+ std::string summary;
+ if (atomic_value->GetSummaryAsCString(summary, options) &&
+ !summary.empty()) {
+ stream << summary;
+ return true;
+ }
+
+ ValueObject *aparent = atomic_value->GetParent();
+ if (aparent && aparent->GetName() == "_M_impl") {
+ return LibStdcppSmartPointerSummaryProvider(*aparent, stream, options,
+ /*is_atomic_child=*/true);
+ }
+ }
+
+ return false;
+}
+
+} // namespace lldb_private::formatters
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/Makefile
new file mode 100644
index 00000000000000..f827444e1b1223
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/Makefile
@@ -0,0 +1,4 @@
+CXX_SOURCES := main.cpp
+override CXXFLAGS_EXTRAS += -std=c++17
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/TestDataFormatterLibstdcppAtomicSimulator.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/TestDataFormatterLibstdcppAtomicSimulator.py
new file mode 100644
index 00000000000000..7fdada675afda2
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/TestDataFormatterLibstdcppAtomicSimulator.py
@@ -0,0 +1,83 @@
+"""
+Test we can understand the layouts of libstdc++'s std::atomic that change
+between libstdc++ versions.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class LibstdcppAtomicSimulatorTestCase(TestBase):
+ SHARED_BUILD_TESTCASE = False
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def get_inner(self, name):
+ """Returns the value wrapped in the std::atomic `name`."""
+ atomic = self.frame().FindVariable(name)
+ self.assertTrue(atomic.IsValid(), msg=f"{name} is not valid.")
+ atomic.SetPreferSyntheticValue(True)
+ self.assertEqual(atomic.GetNumChildren(), 1, msg=f"{name} has one child.")
+ inner = atomic.GetChildAtIndex(0)
+ self.assertTrue(inner.IsValid(), msg=f"{name} child is not valid.")
+ self.assertEqual(inner.GetName(), "Value")
+ return inner
+
+ def do_test(self, float_layout: int, refcount_layout: int):
+ """
+ float_layout:
+ - 0 uses the generic `_M_i` for std::atomic<float>.
+ - 1 uses the specialization with `_M_fp`.
+ refcount_layout:
+ - 0 uses a scalar `_M_val` for the reference count of std::atomic<std::shared_ptr<T>>.
+ - 1 wraps it in a struct with `_M_i`.
+ """
+ defines = [
+ f"-DFLOAT_LAYOUT={float_layout}",
+ f"-DREFCOUNT_LAYOUT={refcount_layout}",
+ ]
+ cxx_flags = " ".join(defines)
+ self.build(dictionary=dict(CXXFLAGS_EXTRAS=cxx_flags))
+ lldbutil.run_to_source_breakpoint(
+ self, "Set break point at this line.", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.assertEqual(self.get_inner("atomic_int").GetValueAsSigned(), 5)
+
+ atomic_struct = self.get_inner("atomic_struct")
+ self.assertEqual(atomic_struct.GetNumChildren(), 2)
+ self.assertEqual(atomic_struct.GetChildAtIndex(0).GetValueAsSigned(), 1)
+ self.assertEqual(atomic_struct.GetChildAtIndex(1).GetValueAsSigned(), 2)
+
+ self.assertEqual(self.get_inner("atomic_bool").GetValueAsUnsigned(), 1)
+
+ # The value is in `_M_fp` or `_M_i` depending on the layout.
+ self.assertAlmostEqual(float(self.get_inner("atomic_float").value), 3.14, 2)
+ self.assertAlmostEqual(float(self.get_inner("atomic_double").value), 6.28, 2)
+
+ # Complex and vector floating point types use the generic layout.
+ self.get_inner("atomic_complex_float")
+ self.assertEqual(self.get_inner("atomic_vector_float").GetNumChildren(), 4)
+
+ atomic_pointer = self.get_inner("atomic_pointer")
+ self.assertEqual(atomic_pointer.Dereference().GetValueAsSigned(), 42)
+
+ # The summary of atomic smart pointers reads the reference count.
+ atomic_shared = self.get_inner("atomic_shared")
+ self.assertEqual(atomic_shared.Dereference().GetValueAsSigned(), 300)
+ self.expect(
+ "frame variable atomic_shared", substrs=["strong=1", "weak=0"]
+ )
+
+ atomic_weak = self.get_inner("atomic_weak")
+ self.assertEqual(atomic_weak.Dereference().GetValueAsSigned(), 300)
+ self.expect("frame variable atomic_weak", substrs=["strong=1", "weak=1"])
+
+ def test_generic_float_scalar_refcount(self):
+ # std::atomic<float> uses `_M_i` and the reference count is a scalar.
+ self.do_test(float_layout=0, refcount_layout=0)
+
+ def test_specialized_float_struct_refcount(self):
+ # std::atomic<float> uses `_M_fp` and the reference count is a struct.
+ self.do_test(float_layout=1, refcount_layout=1)
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/main.cpp
new file mode 100644
index 00000000000000..617355ba7cd088
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/main.cpp
@@ -0,0 +1,160 @@
+#include <cstdint>
+
+// Simulates the layouts of libstdc++'s std::atomic. The layout of some
+// specializations depends on the libstdc++ version:
+//
+// FLOAT_LAYOUT:
+// 0: std::atomic<float> and std::atomic<double> use the generic layout
+// (`_M_i`), as before the C++20 floating point specializations.
+// 1: std::atomic<float> and std::atomic<double> are specialized and store
+// their value in `_M_fp`.
+//
+// REFCOUNT_LAYOUT:
+// 0: The reference count of std::atomic<std::shared_ptr<T>> is a scalar
+// `_M_val`.
+// 1: `_M_val` is a struct wrapping the scalar in `_M_i`.
+
+#ifndef FLOAT_LAYOUT
+#define FLOAT_LAYOUT 1
+#endif
+#ifndef REFCOUNT_LAYOUT
+#define REFCOUNT_LAYOUT 1
+#endif
+
+namespace std {
+
+struct _Sp_counted_base {
+ int _M_use_count;
+ int _M_weak_count;
+};
+
+struct __shared_count {
+ _Sp_counted_base *_M_pi;
+};
+
+template <typename _Tp> struct shared_ptr {
+ using element_type = _Tp;
+ element_type *_M_ptr;
+ __shared_count _M_refcount;
+};
+
+template <typename _Tp> struct weak_ptr {
+ using element_type = _Tp;
+ element_type *_M_ptr;
+ __shared_count _M_refcount;
+};
+
+#if REFCOUNT_LAYOUT == 0
+using _Atomic_val = uintptr_t;
+#else
+struct _Atomic_val {
+ uintptr_t _M_i;
+};
+#endif
+
+struct _Atomic_count {
+ using pointer = _Sp_counted_base *;
+ // Never called, it makes sure the debug info contains `pointer`.
+ pointer get() const;
+
+ _Atomic_val _M_val;
+};
+
+template <typename _Tp> struct _Sp_atomic {
+ typename _Tp::element_type *_M_ptr;
+ _Atomic_count _M_refcount;
+};
+
+template <typename _Tp> struct __atomic_base {
+ _Tp _M_i;
+};
+
+template <typename _Tp> struct __atomic_base<_Tp *> {
+ _Tp *_M_p;
+};
+
+#if FLOAT_LAYOUT == 1
+template <typename _Fp> struct __atomic_float {
+ _Fp _M_fp;
+};
+#endif
+
+template <typename _Tp> struct atomic {
+ _Tp _M_i;
+};
+
+template <> struct atomic<bool> {
+ __atomic_base<bool> _M_base;
+};
+
+template <typename _Tp> struct atomic<_Tp *> {
+ __atomic_base<_Tp *> _M_b;
+};
+
+#if FLOAT_LAYOUT == 1
+template <> struct atomic<float> : __atomic_float<float> {};
+template <> struct atomic<double> : __atomic_float<double> {};
+#endif
+
+template <typename _Tp> struct atomic<shared_ptr<_Tp>> {
+ _Sp_atomic<shared_ptr<_Tp>> _M_impl;
+};
+
+template <typename _Tp> struct atomic<weak_ptr<_Tp>> {
+ _Sp_atomic<weak_ptr<_Tp>> _M_impl;
+};
+
+} // namespace std
+
+static std::_Atomic_count make_count(std::_Sp_counted_base *control) {
+ std::_Atomic_count count{};
+ // The least significant bit is used as a lock by libstdc++, it is not set
+ // while the atomic is idle.
+#if REFCOUNT_LAYOUT == 0
+ count._M_val = reinterpret_cast<uintptr_t>(control);
+#else
+ count._M_val._M_i = reinterpret_cast<uintptr_t>(control);
+#endif
+ return count;
+}
+
+struct S {
+ int x = 1;
+ int y = 2;
+};
+
+typedef float vector4float __attribute__((__vector_size__(16)));
+
+static _Complex float make_complex(float real, float imag) {
+ _Complex float value;
+ __real__ value = real;
+ __imag__ value = imag;
+ return value;
+}
+
+int main() {
+ std::atomic<int> atomic_int{5};
+ std::atomic<S> atomic_struct{S()};
+ std::atomic<bool> atomic_bool{{true}};
+ std::atomic<float> atomic_float{3.14f};
+ std::atomic<double> atomic_double{6.28};
+ std::atomic<_Complex float> atomic_complex_float{
+ make_complex(1.5f, 2.5f)};
+ std::atomic<vector4float> atomic_vector_float{vector4float{1, 2, 3, 4}};
+
+ int pointee = 42;
+ std::atomic<int *> atomic_pointer{{&pointee}};
+
+ int shared_value = 300;
+ // use_count = 1 and weak_count = 1, the weak count includes one reference
+ // for all the strong references.
+ std::_Sp_counted_base shared_control{1, 1};
+ std::atomic<std::shared_ptr<int>> atomic_shared{
+ {&shared_value, make_count(&shared_control)}};
+ // use_count = 1 and weak_count = 2.
+ std::_Sp_counted_base weak_control{1, 2};
+ std::atomic<std::weak_ptr<int>> atomic_weak{
+ {&shared_value, make_count(&weak_control)}};
+
+ return 0; // Set break point at this line.
+}
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/Makefile
index 99998b20bcb050..4f79c0a900c3ac 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/Makefile
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/Makefile
@@ -1,3 +1,4 @@
CXX_SOURCES := main.cpp
+CXXFLAGS_EXTRAS := -std=c++20
include Makefile.rules
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/TestDataFormatterStdAtomic.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/TestDataFormatterStdAtomic.py
index 5194208a09556f..21729ebb016480 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/TestDataFormatterStdAtomic.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/TestDataFormatterStdAtomic.py
@@ -56,6 +56,51 @@ def do_test(self):
self.assertEqual(s.GetChildAtIndex(0).GetValueAsUnsigned(0), 1, "s.x == 1")
self.assertEqual(s.GetChildAtIndex(1).GetValueAsUnsigned(0), 2, "s.y == 2")
+ # float, only available if std::atomic<float> is supported.
+ atomic_float = self.get_variable("atomic_float")
+ if atomic_float.IsValid():
+ val_float = atomic_float.child[0]
+ self.assertIsNotNone(val_float, msg="atomic_float child is None.")
+ self.assertAlmostEqual(float(val_float.value), 3.14, places=2)
+ # double
+ atomic_double = self.get_variable("atomic_double")
+ val_double = atomic_double.child[0]
+ self.assertIsNotNone(val_double, msg="atomic_double child is None.")
+ self.assertAlmostEqual(float(val_double.value), 6.28, places=2)
+
+ # complex float, only available with libstdc++.
+ atomic_complex_float = self.get_variable("atomic_complex_float")
+ if atomic_complex_float.IsValid():
+ self.assertEqual(atomic_complex_float.GetNumChildren(), 1)
+ val_complex = atomic_complex_float.child[0]
+ self.assertIsNotNone(val_complex, msg="atomic_complex_float child is None.")
+ self.assertTrue(val_complex.IsValid())
+
+ # vector of floats
+ atomic_vector_float = self.get_variable("atomic_vector_float")
+ self.assertEqual(atomic_vector_float.GetNumChildren(), 1)
+ val_vector = atomic_vector_float.child[0]
+ self.assertIsNotNone(val_vector, msg="atomic_vector_float child is None.")
+ self.assertEqual(val_vector.GetNumChildren(), 4)
+
+ # bool
+ atomic_bool = self.get_variable("atomic_bool")
+ val_bool = atomic_bool.child[0]
+ self.assertIsNotNone(val_bool, msg="atomic_bool child is None.")
+ self.assertEqual(bool(val_bool.unsigned), True)
+
+ # function
+ atomic_func = self.get_variable("atomic_func")
+ val_func = atomic_func.child[0]
+ self.assertIsNotNone(val_func, msg="atomic_func child is None.")
+ self.assertNotEqual(val_func.unsigned, 0)
+
+ # pointer
+ atomic_pointer = self.get_variable("atomic_pointer")
+ val_pointer = atomic_pointer.child[0]
+ self.assertIsNotNone(val_pointer, msg="atomic_pointer child is None.")
+ self.assertNotEqual(val_pointer.unsigned, 0)
+
# Try printing the child that points to its own parent object.
# This should just treat the atomic pointer as a normal pointer.
self.expect("frame var p.child", substrs=["Value = 0x"])
@@ -64,12 +109,34 @@ def do_test(self):
"frame var p.child.parent", substrs=["p.child.parent = {\n Value = 0x"]
)
+ # Atomic smart pointers, only available if supported by the library.
+ atomic_shared = self.get_variable("atomic_shared")
+ if atomic_shared.IsValid():
+ # shared_pointer
+ val_shared = atomic_shared.child[0]
+ self.assertIsNotNone(val_shared, msg="atomic_shared child is None.")
+ self.assertEqual(300, val_shared.deref.unsigned)
+
+ # weak_pointer
+ atomic_weak = self.get_variable("atomic_weak")
+ val_weak = atomic_weak.child[0]
+ self.assertIsNotNone(val_weak, msg="atomic_weak child is None.")
+ self.assertEqual(300, val_weak.deref.unsigned)
+
+ def verify_floating_point_equal(self, value: str, expected: float):
+ self.assertAlmostEqual(float(value), float(expected), places=4)
+
@skipIf(compiler=["gcc"])
@add_test_categories(["libc++"])
def test_libcxx(self):
self.build(dictionary={"USE_LIBCPP": 1})
self.do_test()
+ @add_test_categories(["libstdcxx"])
+ def test_libstdcxx(self):
+ self.build(dictionary={"USE_LIBSTDCPP": 1})
+ self.do_test()
+
@add_test_categories(["msvcstl"])
def test_msvcstl(self):
# No flags, because the "msvcstl" category checks that the MSVC STL is used by default.
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/main.cpp
index ee77a880a0c5da..88c28667ee9535 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/main.cpp
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/atomic/main.cpp
@@ -1,4 +1,9 @@
#include <atomic>
+#include <version>
+
+#if __cpp_lib_atomic_shared_ptr
+#include <memory>
+#endif // __cpp_lib_atomic_shared_ptr
// Define a Parent and Child struct that can point to each other.
class Parent;
@@ -17,6 +22,11 @@ struct S {
int y = 2;
};
+typedef float vector4float __attribute__((__vector_size__(16)));
+
+static void func() {}
+using func_t = void (*)();
+
int main() {
std::atomic<S> s;
s.store(S());
@@ -27,5 +37,36 @@ int main() {
// Let the child node know what its parent is.
p.child.parent = &p;
+ // libstdcpp has different layout depending on the data structure
+ std::atomic<bool> atomic_bool{true};
+#if __cpp_lib_atomic_float
+ std::atomic<float> atomic_float{3.14};
+ std::atomic<double> atomic_double{6.28};
+#endif // __cpp_lib_atomic_float
+
+ // The type system treats these as floating point types as well, but they
+ // use the generic std::atomic layout.
+#ifdef __GLIBCXX__
+ // Other standard libraries cannot instantiate std::atomic<_Complex float>.
+ _Complex float complex_value;
+ __real__ complex_value = 1.5f;
+ __imag__ complex_value = 2.5f;
+ std::atomic<_Complex float> atomic_complex_float;
+ atomic_complex_float.store(complex_value);
+#endif // __GLIBCXX__
+ std::atomic<vector4float> atomic_vector_float{vector4float{1, 2, 3, 4}};
+
+ std::atomic<func_t> atomic_func(func);
+
+ S data;
+ std::atomic<S *> atomic_pointer{&data};
+
+ // atomic smart pointers were added in c++20
+#if __cpp_lib_atomic_shared_ptr
+ std::shared_ptr<int> s_value = std::make_shared<int>(300);
+ std::atomic<std::shared_ptr<int>> atomic_shared{s_value};
+ std::atomic<std::weak_ptr<int>> atomic_weak{s_value};
+#endif // __cpp_lib_atomic_shared_ptr
+
return 0; // Set break point at this line.
}
>From e1494ab1605e972caf4e89e36d1b95caad930c1c Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <e_ezike at apple.com>
Date: Mon, 26 Oct 2026 16:34:53 +0000
Subject: [PATCH 2/3] fix format
---
.../generic-simulators/libstdcpp-atomic/main.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/main.cpp
index 617355ba7cd088..295a075cb3ee60 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/main.cpp
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic-simulators/libstdcpp-atomic/main.cpp
@@ -138,8 +138,7 @@ int main() {
std::atomic<bool> atomic_bool{{true}};
std::atomic<float> atomic_float{3.14f};
std::atomic<double> atomic_double{6.28};
- std::atomic<_Complex float> atomic_complex_float{
- make_complex(1.5f, 2.5f)};
+ std::atomic<_Complex float> atomic_complex_float{make_complex(1.5f, 2.5f)};
std::atomic<vector4float> atomic_vector_float{vector4float{1, 2, 3, 4}};
int pointee = 42;
>From 2c8e7071e2eaec1f1b4a92c9844fd360ff727642 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Wed, 28 Oct 2026 22:06:13 +0000
Subject: [PATCH 3/3] fix build failure
---
lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp
index 60fea8d3a947a6..ad23b0727450a9 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppAtomic.cpp
@@ -47,12 +47,12 @@ class LibStdcppAtomicSyntheticFrontEnd final
return lldb::ChildCacheState::eRefetch;
}
- llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
+ llvm::Expected<size_t>
+ GetIndexOfChildWithName(llvm::StringRef name) override {
if (name == k_inner_name)
return 0;
- return llvm::createStringErrorV("Type has no child named '{}'",
- name.GetStringRef());
+ return llvm::createStringErrorV("Type has no child named '{}'", name);
}
static ValueObjectSP ContainerFieldName(ValueObject &backend_syn) {
More information about the lldb-commits
mailing list