[Lldb-commits] [lldb] [lldb] Add StringPool and per-debugger and system pool handles (NFC) (PR #228611)

via lldb-commits lldb-commits at lists.llvm.org
Fri Oct 2 15:50:02 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

<details>
<summary>Changes</summary>

Move the ConstString pool into a StringPool class that can be instantiated, and add StringPoolRef, a non-owning handle to one. Debugger and SystemInitializerCommon each expose a handle for strings handed out through the SB API. For now both refer to the global pool.

---

Patch is 29.50 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/228611.diff


9 Files Affected:

- (modified) lldb/include/lldb/Core/Debugger.h (+6) 
- (modified) lldb/include/lldb/Initialization/SystemInitializerCommon.h (+5) 
- (added) lldb/include/lldb/Utility/StringPool.h (+76) 
- (modified) lldb/source/Initialization/SystemInitializerCommon.cpp (+10) 
- (modified) lldb/source/Utility/CMakeLists.txt (+1) 
- (modified) lldb/source/Utility/ConstString.cpp (+15-231) 
- (added) lldb/source/Utility/StringPool.cpp (+238) 
- (modified) lldb/unittests/Utility/CMakeLists.txt (+1) 
- (added) lldb/unittests/Utility/StringPoolTest.cpp (+66) 


``````````diff
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index d3352504497e4..bcc14479c3360 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -34,6 +34,7 @@
 #include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/Status.h"
+#include "lldb/Utility/StringPool.h"
 #include "lldb/Utility/StructuredData.h"
 #include "lldb/Utility/UserID.h"
 #include "lldb/lldb-defines.h"
@@ -418,6 +419,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
 
   const std::string &GetInstanceName() const { return m_instance_name; }
 
+  /// The pool for strings handed out through the SB API that belong to this
+  /// debugger.
+  StringPoolRef GetStringPool() const { return m_string_pool; }
+
   bool GetShowInlineDiagnostics() const;
 
   bool SetShowInlineDiagnostics(bool);
@@ -814,6 +819,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
   llvm::StringMap<std::weak_ptr<LogHandler>> m_stream_handlers;
   std::shared_ptr<CallbackLogHandler> m_callback_handler_sp;
   const std::string m_instance_name;
+  StringPoolRef m_string_pool;
   static LoadPluginCallbackType g_load_plugin_callback;
   typedef std::vector<llvm::sys::DynamicLibrary> LoadedPluginsList;
   LoadedPluginsList m_loaded_plugins;
diff --git a/lldb/include/lldb/Initialization/SystemInitializerCommon.h b/lldb/include/lldb/Initialization/SystemInitializerCommon.h
index 6884e4664f69c..772e117f59bbf 100644
--- a/lldb/include/lldb/Initialization/SystemInitializerCommon.h
+++ b/lldb/include/lldb/Initialization/SystemInitializerCommon.h
@@ -11,6 +11,7 @@
 
 #include "SystemInitializer.h"
 #include "lldb/Host/HostInfo.h"
+#include "lldb/Utility/StringPool.h"
 
 namespace lldb_private {
 /// Initializes common lldb functionality.
@@ -28,6 +29,10 @@ class SystemInitializerCommon : public SystemInitializer {
 
   llvm::Error Initialize() override;
   void Terminate() override;
+
+  /// The pool for strings that are not owned by a Debugger. Only valid between
+  /// Initialize and Terminate.
+  static StringPoolRef GetStringPool();
 };
 
 } // namespace lldb_private
diff --git a/lldb/include/lldb/Utility/StringPool.h b/lldb/include/lldb/Utility/StringPool.h
new file mode 100644
index 0000000000000..d5151e43c228d
--- /dev/null
+++ b/lldb/include/lldb/Utility/StringPool.h
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UTILITY_STRINGPOOL_H
+#define LLDB_UTILITY_STRINGPOOL_H
+
+#include "lldb/Utility/ConstString.h"
+
+#include "llvm/ADT/StringRef.h"
+
+#include <cstddef>
+#include <memory>
+
+namespace lldb_private {
+
+/// A thread-safe pool of interned, null-terminated strings. A string returned
+/// by a pool stays valid for the lifetime of that pool.
+class StringPool {
+public:
+  StringPool();
+  ~StringPool();
+
+  StringPool(const StringPool &) = delete;
+  StringPool &operator=(const StringPool &) = delete;
+
+  /// The pool backing ConstString. It is never destroyed.
+  static StringPool &GetGlobal();
+
+  /// Returns the pooled copy of \p str, or nullptr if \p str has no data.
+  const char *Intern(llvm::StringRef str);
+
+  const char *GetConstCString(const char *cstr);
+  const char *GetConstCStringWithLength(const char *cstr, size_t cstr_len);
+  const char *GetConstTrimmedCStringWithLength(const char *cstr,
+                                               size_t cstr_len);
+
+  /// Interns \p demangled and links it with the already interned \p mangled.
+  const char *GetConstCStringAndSetMangledCounterPart(llvm::StringRef demangled,
+                                                      llvm::StringRef mangled);
+  const char *GetMangledCounterpart(llvm::StringRef str);
+
+  /// Length of a string returned by any pool.
+  static size_t GetConstCStringLength(const char *ccstr);
+
+  ConstString::MemoryStats GetMemoryStats() const;
+
+private:
+  struct PoolEntry;
+
+  PoolEntry &selectPool(uint32_t hash);
+  PoolEntry &selectPool(llvm::StringRef str);
+
+  std::unique_ptr<PoolEntry[]> m_string_pools;
+};
+
+/// A handle to a StringPool that does not own it.
+class StringPoolRef {
+public:
+  /// Refers to the global pool.
+  StringPoolRef() : m_pool(&StringPool::GetGlobal()) {}
+  explicit StringPoolRef(StringPool &pool) : m_pool(&pool) {}
+
+  const char *Intern(llvm::StringRef str) const { return m_pool->Intern(str); }
+
+private:
+  StringPool *m_pool;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_UTILITY_STRINGPOOL_H
diff --git a/lldb/source/Initialization/SystemInitializerCommon.cpp b/lldb/source/Initialization/SystemInitializerCommon.cpp
index 97aad944ece93..43fcd9a69c69d 100644
--- a/lldb/source/Initialization/SystemInitializerCommon.cpp
+++ b/lldb/source/Initialization/SystemInitializerCommon.cpp
@@ -35,11 +35,19 @@
 
 using namespace lldb_private;
 
+static StringPool *g_system_string_pool = nullptr;
+
 SystemInitializerCommon::SystemInitializerCommon() = default;
 
 SystemInitializerCommon::~SystemInitializerCommon() = default;
 
+StringPoolRef SystemInitializerCommon::GetStringPool() {
+  assert(g_system_string_pool && "system initializers not initialized");
+  return StringPoolRef(*g_system_string_pool);
+}
+
 llvm::Error SystemInitializerCommon::Initialize() {
+  g_system_string_pool = &StringPool::GetGlobal();
 #if defined(_WIN32)
   const char *disable_crash_dialog_var = getenv("LLDB_DISABLE_CRASH_DIALOG");
   if (disable_crash_dialog_var &&
@@ -96,6 +104,8 @@ llvm::Error SystemInitializerCommon::Initialize() {
 void SystemInitializerCommon::Terminate() {
   LLDB_SCOPED_TIMER();
 
+  g_system_string_pool = nullptr;
+
 #if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) ||       \
     defined(__OpenBSD__)
   ProcessPOSIXLog::Terminate();
diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt
index 5033edb8773f7..a13aec8327868 100644
--- a/lldb/source/Utility/CMakeLists.txt
+++ b/lldb/source/Utility/CMakeLists.txt
@@ -68,6 +68,7 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES
   StringExtractor.cpp
   StringExtractorGDBRemote.cpp
   StringList.cpp
+  StringPool.cpp
   StructuredData.cpp
   TildeExpressionResolver.cpp
   Timer.cpp
diff --git a/lldb/source/Utility/ConstString.cpp b/lldb/source/Utility/ConstString.cpp
index 8e97b7bb382a2..9890e46d6b0f1 100644
--- a/lldb/source/Utility/ConstString.cpp
+++ b/lldb/source/Utility/ConstString.cpp
@@ -9,18 +9,12 @@
 #include "lldb/Utility/ConstString.h"
 
 #include "lldb/Utility/Stream.h"
+#include "lldb/Utility/StringPool.h"
 
-#include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/iterator.h"
-#include "llvm/Support/Allocator.h"
-#include "llvm/Support/DJB.h"
 #include "llvm/Support/FormatProviders.h"
-#include "llvm/Support/Threading.h"
 
-#include <array>
-#include <mutex>
-#include <shared_mutex>
 #include <utility>
 
 #include <cinttypes>
@@ -29,227 +23,15 @@
 
 using namespace lldb_private;
 
-#if !defined(__APPLE__)
-using PoolMutex = std::shared_mutex;
-#else
-#include <os/lock.h>
-
-namespace {
-/// On Apple platforms os_unfair_lock is significantly faster than
-/// pthread_rwlock for concurrent writes, and roughly on par for concurrent
-/// reads.
-///
-/// The class satisfies both Lockable and SharedLockable so it composes with
-/// std::lock_guard and std::shared_lock.
-class PoolMutex {
-public:
-  void lock() { os_unfair_lock_lock(&m_lock); }
-  void unlock() { os_unfair_lock_unlock(&m_lock); }
-  void lock_shared() { os_unfair_lock_lock(&m_lock); }
-  void unlock_shared() { os_unfair_lock_unlock(&m_lock); }
-
-private:
-  os_unfair_lock m_lock = OS_UNFAIR_LOCK_INIT;
-};
-} // namespace
-#endif
-
-class Pool {
-public:
-  /// The default BumpPtrAllocatorImpl slab size.
-  static const size_t AllocatorSlabSize = 4096;
-  static const size_t SizeThreshold = AllocatorSlabSize;
-  /// Every Pool has its own allocator which receives an equal share of
-  /// the ConstString allocations. This means that when allocating many
-  /// ConstStrings, every allocator sees only its small share of allocations and
-  /// assumes LLDB only allocated a small amount of memory so far. In reality
-  /// LLDB allocated a total memory that is N times as large as what the
-  /// allocator sees (where N is the number of string pools). This causes that
-  /// the BumpPtrAllocator continues a long time to allocate memory in small
-  /// chunks which only makes sense when allocating a small amount of memory
-  /// (which is true from the perspective of a single allocator). On some
-  /// systems doing all these small memory allocations causes LLDB to spend
-  /// a lot of time in malloc, so we need to force all these allocators to
-  /// behave like one allocator in terms of scaling their memory allocations
-  /// with increased demand. To do this we set the growth delay for each single
-  /// allocator to a rate so that our pool of allocators scales their memory
-  /// allocations similar to a single BumpPtrAllocatorImpl.
-  ///
-  /// Currently we have 256 string pools and the normal growth delay of the
-  /// BumpPtrAllocatorImpl is 128 (i.e., the memory allocation size increases
-  /// every 128 full chunks), so by changing the delay to 1 we get a
-  /// total growth delay in our allocator collection of 256/1 = 256. This is
-  /// still only half as fast as a normal allocator but we can't go any faster
-  /// without decreasing the number of string pools.
-  static const size_t AllocatorGrowthDelay = 1;
-  typedef llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator, AllocatorSlabSize,
-                                     SizeThreshold, AllocatorGrowthDelay>
-      Allocator;
-  typedef const char *StringPoolValueType;
-  typedef llvm::StringMap<StringPoolValueType, Allocator> StringPool;
-  typedef llvm::StringMapEntry<StringPoolValueType> StringPoolEntryType;
-
-  static StringPoolEntryType &
-  GetStringMapEntryFromKeyData(const char *keyData) {
-    return StringPoolEntryType::GetStringMapEntryFromKeyData(keyData);
-  }
-
-  static size_t GetConstCStringLength(const char *ccstr) {
-    if (ccstr != nullptr) {
-      // Since the entry is read only, and we derive the entry entirely from
-      // the pointer, we don't need the lock.
-      const StringPoolEntryType &entry = GetStringMapEntryFromKeyData(ccstr);
-      return entry.getKeyLength();
-    }
-    return 0;
-  }
-
-  StringPoolValueType GetMangledCounterpart(llvm::StringRef str) {
-    const char *const ccstr = str.data();
-    if (ccstr != nullptr) {
-      const PoolEntry &pool = selectPool(str);
-      std::shared_lock<PoolMutex> lock(pool.m_mutex);
-      return GetStringMapEntryFromKeyData(ccstr).getValue();
-    }
-    return nullptr;
-  }
-
-  const char *GetConstCString(const char *cstr) {
-    if (cstr != nullptr)
-      return GetConstCStringWithLength(cstr, strlen(cstr));
-    return nullptr;
-  }
-
-  const char *GetConstCStringWithLength(const char *cstr, size_t cstr_len) {
-    if (cstr != nullptr)
-      return GetConstCStringWithStringRef(llvm::StringRef(cstr, cstr_len));
-    return nullptr;
-  }
-
-  const char *GetConstCStringWithStringRef(llvm::StringRef string_ref) {
-    if (string_ref.data()) {
-      const uint32_t string_hash = StringPool::hash(string_ref);
-      PoolEntry &pool = selectPool(string_hash);
-
-      {
-        std::shared_lock<PoolMutex> lock(pool.m_mutex);
-        auto it = pool.m_string_map.find(string_ref, string_hash);
-        if (it != pool.m_string_map.end())
-          return it->getKeyData();
-      }
-
-      std::lock_guard<PoolMutex> lock(pool.m_mutex);
-      pool.used_bytes += string_ref.size();
-      StringPoolEntryType &entry =
-          *pool.m_string_map
-               .insert(std::make_pair(string_ref, nullptr), string_hash)
-               .first;
-      return entry.getKeyData();
-    }
-    return nullptr;
-  }
-
-  const char *GetConstCStringAndSetMangledCounterPart(llvm::StringRef demangled,
-                                                      llvm::StringRef mangled) {
-    const char *demangled_ccstr = nullptr;
-    const char *const mangled_ccstr = mangled.data();
-
-    {
-      const uint32_t demangled_hash = StringPool::hash(demangled);
-      PoolEntry &pool = selectPool(demangled_hash);
-      std::lock_guard<PoolMutex> lock(pool.m_mutex);
-
-      // Make or update string pool entry with the mangled counterpart
-      StringPool &map = pool.m_string_map;
-      auto [entry, inserted] =
-          map.try_emplace_with_hash(demangled, demangled_hash);
-      if (inserted)
-        pool.used_bytes += demangled.size();
-
-      entry->second = mangled_ccstr;
-
-      // Extract the const version of the demangled_cstr
-      demangled_ccstr = entry->getKeyData();
-    }
-
-    {
-      // Now assign the demangled const string as the counterpart of the
-      // mangled const string...
-      PoolEntry &pool = selectPool(mangled);
-      std::lock_guard<PoolMutex> lock(pool.m_mutex);
-      GetStringMapEntryFromKeyData(mangled_ccstr).setValue(demangled_ccstr);
-    }
-
-    // Return the constant demangled C string
-    return demangled_ccstr;
-  }
-
-  const char *GetConstTrimmedCStringWithLength(const char *cstr,
-                                               size_t cstr_len) {
-    if (cstr != nullptr) {
-      const size_t trimmed_len = strnlen(cstr, cstr_len);
-      return GetConstCStringWithLength(cstr, trimmed_len);
-    }
-    return nullptr;
-  }
-
-  ConstString::MemoryStats GetMemoryStats() const {
-    ConstString::MemoryStats stats;
-    for (const auto &pool : m_string_pools) {
-      std::shared_lock<PoolMutex> lock(pool.m_mutex);
-      const Allocator &alloc = pool.m_string_map.getAllocator();
-      stats.bytes_total += alloc.getTotalMemory();
-      stats.bytes_used += pool.used_bytes;
-    }
-    return stats;
-  }
-
-protected:
-  struct PoolEntry {
-    mutable PoolMutex m_mutex;
-    StringPool m_string_map;
-    /// The exact number of bytes used by this pool.
-    /// This excludes alignment, padding and redzones.
-    std::size_t used_bytes = 0;
-  };
-
-  std::array<PoolEntry, 256> m_string_pools;
-
-  PoolEntry &selectPool(const llvm::StringRef &s) {
-    return selectPool(StringPool::hash(s));
-  }
-
-  PoolEntry &selectPool(uint32_t h) {
-    return m_string_pools[((h >> 24) ^ (h >> 16) ^ (h >> 8) ^ h) & 0xff];
-  }
-};
-
-// Frameworks and dylibs aren't supposed to have global C++ initializers so we
-// hide the string pool in a static function so that it will get initialized on
-// the first call to this static function.
-//
-// Note, for now we make the string pool a pointer to the pool, because we
-// can't guarantee that some objects won't get destroyed after the global
-// destructor chain is run, and trying to make sure no destructors touch
-// ConstStrings is difficult.  So we leak the pool instead.
-static Pool &StringPool() {
-  static llvm::once_flag g_pool_initialization_flag;
-  static Pool *g_string_pool = nullptr;
-
-  llvm::call_once(g_pool_initialization_flag,
-                  []() { g_string_pool = new Pool(); });
-
-  return *g_string_pool;
-}
-
 ConstString::ConstString(const char *cstr)
-    : m_string(StringPool().GetConstCString(cstr)) {}
+    : m_string(StringPool::GetGlobal().GetConstCString(cstr)) {}
 
 ConstString::ConstString(const char *cstr, size_t cstr_len)
-    : m_string(StringPool().GetConstCStringWithLength(cstr, cstr_len)) {}
+    : m_string(
+          StringPool::GetGlobal().GetConstCStringWithLength(cstr, cstr_len)) {}
 
 ConstString::ConstString(llvm::StringRef s)
-    : m_string(StringPool().GetConstCStringWithStringRef(s)) {}
+    : m_string(StringPool::GetGlobal().Intern(s)) {}
 
 bool ConstString::operator<(ConstString rhs) const {
   if (m_string == rhs.m_string)
@@ -275,7 +57,7 @@ Stream &lldb_private::operator<<(Stream &s, ConstString str) {
 }
 
 size_t ConstString::GetLength() const {
-  return Pool::GetConstCStringLength(m_string);
+  return StringPool::GetConstCStringLength(m_string);
 }
 
 bool ConstString::Equals(ConstString lhs, ConstString rhs,
@@ -339,35 +121,37 @@ void ConstString::DumpDebug(Stream *s) const {
 }
 
 void ConstString::SetCString(const char *cstr) {
-  m_string = StringPool().GetConstCString(cstr);
+  m_string = StringPool::GetGlobal().GetConstCString(cstr);
 }
 
 void ConstString::SetString(llvm::StringRef s) {
-  m_string = StringPool().GetConstCStringWithStringRef(s);
+  m_string = StringPool::GetGlobal().Intern(s);
 }
 
 void ConstString::SetStringWithMangledCounterpart(llvm::StringRef demangled,
                                                   ConstString mangled) {
-  m_string = StringPool().GetConstCStringAndSetMangledCounterPart(
+  m_string = StringPool::GetGlobal().GetConstCStringAndSetMangledCounterPart(
       demangled, mangled.GetStringRef());
 }
 
 bool ConstString::GetMangledCounterpart(ConstString &counterpart) const {
-  counterpart.m_string = StringPool().GetMangledCounterpart(GetStringRef());
+  counterpart.m_string =
+      StringPool::GetGlobal().GetMangledCounterpart(GetStringRef());
   return (bool)counterpart;
 }
 
 void ConstString::SetCStringWithLength(const char *cstr, size_t cstr_len) {
-  m_string = StringPool().GetConstCStringWithLength(cstr, cstr_len);
+  m_string = StringPool::GetGlobal().GetConstCStringWithLength(cstr, cstr_len);
 }
 
 void ConstString::SetTrimmedCStringWithLength(const char *cstr,
                                               size_t cstr_len) {
-  m_string = StringPool().GetConstTrimmedCStringWithLength(cstr, cstr_len);
+  m_string =
+      StringPool::GetGlobal().GetConstTrimmedCStringWithLength(cstr, cstr_len);
 }
 
 ConstString::MemoryStats ConstString::GetMemoryStats() {
-  return StringPool().GetMemoryStats();
+  return StringPool::GetGlobal().GetMemoryStats();
 }
 
 void llvm::format_provider<ConstString>::format(const ConstString &CS,
diff --git a/lldb/source/Utility/StringPool.cpp b/lldb/source/Utility/StringPool.cpp
new file mode 100644
index 0000000000000..9be1dd6065027
--- /dev/null
+++ b/lldb/source/Utility/StringPool.cpp
@@ -0,0 +1,238 @@
+//===-- StringPool.cpp ----------------------------------------------------===//
+//
+// 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 "lldb/Utility/StringPool.h"
+
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Threading.h"
+
+#include <array>
+#include <mutex>
+#include <shared_mutex>
+#include <utility>
+
+#include <cstdint>
+#include <cstring>
+
+using namespace lldb_private;
+
+#if !defined(__APPLE__)
+using PoolMutex = std::shared_mutex;
+#else
+#include <os/lock.h>
+
+namespace {
+/// On Apple platforms os_unfair_lock is significantly faster than
+/// pthread_rwlock for concurrent writes, and roughly on par for concurrent
+/// reads.
+///
+/// The class satisfies both Lockable and SharedLockable so it composes with
+/// std::lock_guard and std::shared_lock.
+class PoolMutex {
+public:
+  void lock() { os_unfair_lock_lock(&m_lock); }
+  void unlock() { os_unfair_lock_unlock(&m_lock); }
+  void lock_shared() { os_unfair_lock_lock(&m_lock); }
+  void unlock_shared() { os_unfair_lock_unlock(&m_lock); }
+
+private:
+  os_unfair_lock m_lock = OS_UNFAIR_LOCK_INIT;
+};
+} // namespace
+#endif
+
+namespace {
+/// The default BumpPtrAllocatorImpl slab size.
+constexpr size_t AllocatorSlabSize = 4096;
+constexpr size_t SizeThreshold = AllocatorSlabSize;
+constexpr size_t NumPools = 256;
+/// Every pool shard has its own allocator which receives an equal share of
+/// the string allocations. This means that when allocating many strings, every
+/// allocator sees only its small share of allocations and assumes LLDB only
+/// allocated a small amount of memory so far. In reality LLDB allocated a total
+/// memory that is N times as large as what the allocator sees (where N is the
+/// numb...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/228611


More information about the lldb-commits mailing list