[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