[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