[Lldb-commits] [lldb] 2c10250 - [lldb] Add StringPool and per-debugger and system pool handles (NFC) (#228611)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Oct 6 12:55:46 PDT 2026
Author: Jonas Devlieghere
Date: 2026-10-06T19:55:38Z
New Revision: 2c10250e85242b85040d7b8592dd47de429717ad
URL: https://github.com/llvm/llvm-project/commit/2c10250e85242b85040d7b8592dd47de429717ad
DIFF: https://github.com/llvm/llvm-project/commit/2c10250e85242b85040d7b8592dd47de429717ad.diff
LOG: [lldb] Add StringPool and per-debugger and system pool handles (NFC) (#228611)
Move the ConstString pool into a StringPool class and add a non-owning
StringPoolRef handle. This is in preparation for having one global and
multiple per-Debugger ConstString pool. Strings at the SB API layer will
be interned into the appropriate pool. As long as these remain
references, this operation is a NOOP.
Added:
lldb/include/lldb/Utility/StringPool.h
lldb/source/Utility/StringPool.cpp
lldb/unittests/Utility/StringPoolTest.cpp
Modified:
lldb/include/lldb/Core/Debugger.h
lldb/source/Initialization/SystemInitializerCommon.cpp
lldb/source/Utility/CMakeLists.txt
lldb/source/Utility/ConstString.cpp
lldb/unittests/Utility/CMakeLists.txt
Removed:
################################################################################
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index d3352504497e43..bcc14479c3360d 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/Utility/StringPool.h b/lldb/include/lldb/Utility/StringPool.h
new file mode 100644
index 00000000000000..d5a7a5597975ed
--- /dev/null
+++ b/lldb/include/lldb/Utility/StringPool.h
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 {
+
+class StringPoolRef;
+
+/// 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();
+
+ /// Set up and tear down the system pool, for strings that are not owned by a
+ /// Debugger.
+ static void Initialize();
+ static void Terminate();
+
+ /// The system pool. Only valid between Initialize and Terminate.
+ static StringPoolRef GetSystem();
+
+ /// 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 97aad944ece933..b2e5a84746a862 100644
--- a/lldb/source/Initialization/SystemInitializerCommon.cpp
+++ b/lldb/source/Initialization/SystemInitializerCommon.cpp
@@ -15,6 +15,7 @@
#include "lldb/Host/Socket.h"
#include "lldb/Target/Statistics.h"
#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/StringPool.h"
#include "lldb/Utility/Timer.h"
#include "lldb/Version/Version.h"
@@ -40,6 +41,7 @@ SystemInitializerCommon::SystemInitializerCommon() = default;
SystemInitializerCommon::~SystemInitializerCommon() = default;
llvm::Error SystemInitializerCommon::Initialize() {
+ StringPool::Initialize();
#if defined(_WIN32)
const char *disable_crash_dialog_var = getenv("LLDB_DISABLE_CRASH_DIALOG");
if (disable_crash_dialog_var &&
@@ -112,4 +114,6 @@ void SystemInitializerCommon::Terminate() {
FileSystem::Terminate();
Diagnostics::Terminate();
LLDBLogChannel::Terminate();
+
+ StringPool::Terminate();
}
diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt
index 5033edb8773f77..a13aec8327868b 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 8e97b7bb382a2b..9890e46d6b0f1e 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
diff icult. 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 00000000000000..84cf7df02d9293
--- /dev/null
+++ b/lldb/source/Utility/StringPool.cpp
@@ -0,0 +1,252 @@
+//===-- 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
+/// 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.
+constexpr size_t AllocatorGrowthDelay = 1;
+using Allocator =
+ llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator, AllocatorSlabSize,
+ SizeThreshold, AllocatorGrowthDelay>;
+using StringPoolValueType = const char *;
+using StringMapType = llvm::StringMap<StringPoolValueType, Allocator>;
+using StringPoolEntryType = llvm::StringMapEntry<StringPoolValueType>;
+
+StringPoolEntryType &GetStringMapEntryFromKeyData(const char *keyData) {
+ return StringPoolEntryType::GetStringMapEntryFromKeyData(keyData);
+}
+} // namespace
+
+struct StringPool::PoolEntry {
+ mutable PoolMutex m_mutex;
+ StringMapType m_string_map;
+ /// The exact number of bytes used by this pool.
+ /// This excludes alignment, padding and redzones.
+ std::size_t used_bytes = 0;
+};
+
+StringPool::StringPool() : m_string_pools(new PoolEntry[NumPools]) {}
+
+StringPool::~StringPool() = default;
+
+// 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
diff icult. So we leak the pool instead.
+StringPool &StringPool::GetGlobal() {
+ static llvm::once_flag g_pool_initialization_flag;
+ static StringPool *g_string_pool = nullptr;
+
+ llvm::call_once(g_pool_initialization_flag,
+ []() { g_string_pool = new StringPool(); });
+
+ return *g_string_pool;
+}
+
+static StringPool *g_system_pool = nullptr;
+
+void StringPool::Initialize() {
+ assert(!g_system_pool && "system pool already initialized");
+ g_system_pool = &GetGlobal();
+}
+
+void StringPool::Terminate() { g_system_pool = nullptr; }
+
+StringPoolRef StringPool::GetSystem() {
+ assert(g_system_pool && "system pool not initialized");
+ return StringPoolRef(*g_system_pool);
+}
+
+StringPool::PoolEntry &StringPool::selectPool(uint32_t h) {
+ return m_string_pools[((h >> 24) ^ (h >> 16) ^ (h >> 8) ^ h) & 0xff];
+}
+
+StringPool::PoolEntry &StringPool::selectPool(llvm::StringRef s) {
+ return selectPool(StringMapType::hash(s));
+}
+
+size_t StringPool::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;
+}
+
+const char *StringPool::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 *StringPool::GetConstCString(const char *cstr) {
+ if (cstr != nullptr)
+ return GetConstCStringWithLength(cstr, strlen(cstr));
+ return nullptr;
+}
+
+const char *StringPool::GetConstCStringWithLength(const char *cstr,
+ size_t cstr_len) {
+ if (cstr != nullptr)
+ return Intern(llvm::StringRef(cstr, cstr_len));
+ return nullptr;
+}
+
+const char *StringPool::Intern(llvm::StringRef string_ref) {
+ if (string_ref.data()) {
+ const uint32_t string_hash = StringMapType::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 *
+StringPool::GetConstCStringAndSetMangledCounterPart(llvm::StringRef demangled,
+ llvm::StringRef mangled) {
+ const char *demangled_ccstr = nullptr;
+ const char *const mangled_ccstr = mangled.data();
+
+ {
+ const uint32_t demangled_hash = StringMapType::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
+ StringMapType &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 *StringPool::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 StringPool::GetMemoryStats() const {
+ ConstString::MemoryStats stats;
+ for (size_t i = 0; i < NumPools; ++i) {
+ const PoolEntry &pool = m_string_pools[i];
+ 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;
+}
diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt
index e306b7216680ec..3e3f402697f3c8 100644
--- a/lldb/unittests/Utility/CMakeLists.txt
+++ b/lldb/unittests/Utility/CMakeLists.txt
@@ -8,6 +8,7 @@ add_lldb_unittest(UtilityTests
BroadcasterTest.cpp
ChecksumTest.cpp
ConstStringTest.cpp
+ StringPoolTest.cpp
CompletionRequestTest.cpp
DataBufferTest.cpp
DataEncoderTest.cpp
diff --git a/lldb/unittests/Utility/StringPoolTest.cpp b/lldb/unittests/Utility/StringPoolTest.cpp
new file mode 100644
index 00000000000000..80d785aad9d43e
--- /dev/null
+++ b/lldb/unittests/Utility/StringPoolTest.cpp
@@ -0,0 +1,74 @@
+//===-- StringPoolTest.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 "gtest/gtest.h"
+
+#include <string>
+#include <thread>
+#include <vector>
+
+using namespace lldb_private;
+
+TEST(StringPoolTest, InternDeduplicates) {
+ StringPool pool;
+ std::string a = "foo";
+ const char *p1 = pool.Intern(a);
+ // The pool owns a copy, so changing the source does not affect it.
+ a[0] = 'x';
+ const char *p2 = pool.Intern("foo");
+ EXPECT_EQ(p1, p2);
+ EXPECT_STREQ("foo", p1);
+ EXPECT_NE(p1, pool.Intern("bar"));
+}
+
+TEST(StringPoolTest, InternNullAndEmpty) {
+ StringPool pool;
+ EXPECT_EQ(nullptr, pool.Intern(llvm::StringRef()));
+ const char *empty = pool.Intern("");
+ ASSERT_NE(nullptr, empty);
+ EXPECT_STREQ("", empty);
+}
+
+TEST(StringPoolTest, GlobalPoolBacksConstString) {
+ ConstString cs("global_pool_string");
+ EXPECT_EQ(cs.GetCString(),
+ StringPool::GetGlobal().Intern("global_pool_string"));
+ EXPECT_EQ(cs.GetCString(), StringPoolRef().Intern("global_pool_string"));
+}
+
+TEST(StringPoolTest, SystemPool) {
+ StringPool::Initialize();
+ EXPECT_EQ(StringPool::GetGlobal().Intern("system"),
+ StringPool::GetSystem().Intern("system"));
+ StringPool::Terminate();
+}
+
+TEST(StringPoolTest, PoolsAreIndependent) {
+ StringPool a, b;
+ EXPECT_NE(a.Intern("shared"), b.Intern("shared"));
+ EXPECT_NE(a.Intern("shared"), StringPool::GetGlobal().Intern("shared"));
+ EXPECT_EQ(5u, StringPool::GetConstCStringLength(a.Intern("12345")));
+}
+
+TEST(StringPoolTest, ConcurrentIntern) {
+ StringPool pool;
+ constexpr int num_threads = 8;
+ constexpr int num_strings = 1000;
+ std::vector<std::vector<const char *>> results(num_threads);
+ std::vector<std::thread> threads;
+ for (int t = 0; t < num_threads; ++t)
+ threads.emplace_back([&, t] {
+ for (int i = 0; i < num_strings; ++i)
+ results[t].push_back(pool.Intern(std::to_string(i)));
+ });
+ for (std::thread &t : threads)
+ t.join();
+ for (int t = 1; t < num_threads; ++t)
+ EXPECT_EQ(results[0], results[t]);
+}
More information about the lldb-commits
mailing list