[Lldb-commits] [lldb] [lldb] Move DemangledNameInfo cache out of Mangled class (PR #225332)

Raphael Isemann via lldb-commits lldb-commits at lists.llvm.org
Tue Sep 29 00:37:39 PDT 2026


https://github.com/Teemperor updated https://github.com/llvm/llvm-project/pull/225332

>From 3f814d7d8a081378a1569067067f194b14ddcfd4 Mon Sep 17 00:00:00 2001
From: Raphael Isemann <rise at apple.com>
Date: Tue, 22 Sep 2026 09:44:30 +0100
Subject: [PATCH] [lldb] Move DemangledNameInfo cache out of Mangled class

This patch moves the per-Mangled DemangledInfoCache into a global
cache. The motivation for this is:

1. It makes `Mangled` a smaller which saves memory due to fact its very
frequently allocated.

2. The current Mangled class is sometimes accessed via the SB API from
multiple threads, and the stateful cache in Symbol causes crashes due
to the inherent race conditions of this approach.

3. It avoids potentially large memory usage in case many symbols have
their DemangledNameInfo computed.

The size of the global cache can be changed via the new
'symbols.demangled-name-info-cache-size' setting.

For the caching strategy, the current implementation drops a random
entry whenever the cache is full. Note that we can't use an LRU/LFU
cache as this won't allow read-write locking when we update the
use-counter.
---
 lldb/include/lldb/Core/DemangledNameInfo.h    |  52 ++++++
 lldb/include/lldb/Core/Mangled.h              |  58 ++-----
 lldb/include/lldb/Core/ModuleList.h           |   1 +
 lldb/include/lldb/Symbol/Symbol.h             |   2 +-
 lldb/source/Core/CoreProperties.td            |   4 +
 lldb/source/Core/DemangledNameInfo.cpp        |  71 ++++++++
 lldb/source/Core/Mangled.cpp                  |  48 +++---
 lldb/source/Core/ModuleList.cpp               |   6 +
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  |  11 +-
 lldb/source/Symbol/Symbol.cpp                 |   2 +-
 lldb/source/Symbol/Symtab.cpp                 |   2 +-
 lldb/unittests/Core/MangledTest.cpp           | 153 ++++++++++++++----
 12 files changed, 305 insertions(+), 105 deletions(-)

diff --git a/lldb/include/lldb/Core/DemangledNameInfo.h b/lldb/include/lldb/Core/DemangledNameInfo.h
index ac08e091d902b..d7ea6d40850f8 100644
--- a/lldb/include/lldb/Core/DemangledNameInfo.h
+++ b/lldb/include/lldb/Core/DemangledNameInfo.h
@@ -9,14 +9,22 @@
 #ifndef LLDB_CORE_DEMANGLEDNAMEINFO_H
 #define LLDB_CORE_DEMANGLEDNAMEINFO_H
 
+#include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/Locked.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/Demangle/ItaniumDemangle.h"
 #include "llvm/Demangle/Utility.h"
 
 #include <cstddef>
+#include <optional>
+#include <random>
 #include <utility>
+#include <vector>
 
 namespace lldb_private {
 
+class Mangled;
+
 /// Stores information about where certain portions of a demangled
 /// function name begin and end.
 struct DemangledNameInfo {
@@ -132,6 +140,50 @@ struct DemangledNameInfo {
   bool hasSuffix() const { return SuffixRange.second >= SuffixRange.first; }
 };
 
+/// A cache from mangled names to DemangledNameInfo.
+class DemangledNameInfoCache {
+public:
+  static constexpr size_t DefaultMaxEntries = 16 * 1024;
+
+  explicit DemangledNameInfoCache(size_t max_entries = DefaultMaxEntries)
+      : m_state(State(max_entries)) {}
+
+  /// Calculates the DemangledNameInfo and caches the result.
+  ///
+  /// \return std::nullopt if no info could be computed for \c mangled.
+  std::optional<DemangledNameInfo> Get(const Mangled &mangled);
+
+  void Clear();
+
+  size_t GetMaxEntries() const;
+
+  size_t GetNumEntries() const;
+
+  /// Changes the maximum number of entries. A limit of 0 disables the cache.
+  void SetMaxEntries(size_t max_entries);
+
+private:
+  struct State {
+    explicit State(size_t max_entries) : max_entries(max_entries) {}
+
+    /// Drops a random entry from the cache.
+    void EvictRandomEntry();
+
+    /// Maps a ConstString to its DemangledNameInfo.
+    ///  Values can be empty if no name info could be computed for a name.
+    llvm::DenseMap<ConstString, std::optional<DemangledNameInfo>> infos;
+
+    /// The mangled names.
+    std::vector<ConstString> mangled_names;
+
+    std::minstd_rand rng;
+
+    size_t max_entries;
+  };
+
+  Guarded<State> m_state;
+};
+
 /// An OutputBuffer which keeps a record of where certain parts of a
 /// demangled name begin/end (e.g., basename, scope, argument list, etc.).
 /// The tracking occurs during printing of the Itanium demangle tree.
diff --git a/lldb/include/lldb/Core/Mangled.h b/lldb/include/lldb/Core/Mangled.h
index 1c7bbeb05a37d..622c0f82f49f7 100644
--- a/lldb/include/lldb/Core/Mangled.h
+++ b/lldb/include/lldb/Core/Mangled.h
@@ -17,7 +17,7 @@
 #include "llvm/ADT/StringRef.h"
 
 #include <cstddef>
-#include <memory>
+#include <optional>
 
 namespace lldb_private {
 
@@ -53,28 +53,6 @@ class Mangled {
   /// Initialize with both mangled and demangled names empty.
   Mangled() = default;
 
-  Mangled(const Mangled &other)
-      : m_mangled(other.m_mangled), m_demangled(other.m_demangled),
-        m_demangled_info(
-            other.m_demangled_info
-                ? std::make_unique<DemangledNameInfo>(*other.m_demangled_info)
-                : nullptr) {}
-
-  Mangled &operator=(const Mangled &other) {
-    if (this != &other) {
-      m_mangled = other.m_mangled;
-      m_demangled = other.m_demangled;
-      m_demangled_info =
-          other.m_demangled_info
-              ? std::make_unique<DemangledNameInfo>(*other.m_demangled_info)
-              : nullptr;
-    }
-    return *this;
-  }
-
-  Mangled(Mangled &&) = default;
-  Mangled &operator=(Mangled &&) = default;
-
   /// Construct with name.
   ///
   /// Constructor with an optional string and auto-detect if \a name is
@@ -157,15 +135,9 @@ class Mangled {
   ///     A const reference to the display demangled name string object.
   ConstString GetDisplayDemangledName() const;
 
-  void SetDemangledName(ConstString name) {
-    m_demangled = name;
-    m_demangled_info.reset();
-  }
+  void SetDemangledName(ConstString name) { m_demangled = name; }
 
-  void SetMangledName(ConstString name) {
-    m_mangled = name;
-    m_demangled_info.reset();
-  }
+  void SetMangledName(ConstString name) { m_mangled = name; }
 
   /// Mangled name get accessor.
   ///
@@ -290,8 +262,15 @@ class Mangled {
   ///   table offsets in the cache data.
   void Encode(DataEncoder &encoder, ConstStringTable &strtab) const;
 
-  /// Retrieve \c DemangledNameInfo of the demangled name held by this object.
-  const DemangledNameInfo *GetDemangledInfo() const;
+  /// Compute the \c DemangledNameInfo of the demangled name.
+  ///
+  /// Note that this always re-runs the demangler. \see DemangledNameInfoCache
+  /// for caching the result.
+  ///
+  /// \return
+  ///     std::nullopt if no info could be computed (for example because the
+  ///     name is mangled with a scheme that doesn't provide any info).
+  std::optional<DemangledNameInfo> ComputeDemangledInfo() const;
 
   /// Compute the base name (without namespace/class qualifiers) from the
   /// demangled name.
@@ -305,25 +284,14 @@ class Mangled {
   ConstString GetBaseName() const;
 
 private:
-  /// If \c force is \c false, this function will re-use the previously
-  /// demangled name (if any). If \c force is \c true (or the mangled name
-  /// on this object was not previously demangled), demangle and cache the
-  /// name.
-  ConstString GetDemangledNameImpl(bool force) const;
-
   /// The mangled version of the name.
   ConstString m_mangled;
 
   /// Mutable so we can get it on demand with
   /// a const version of this object.
   mutable ConstString m_demangled;
-
-  /// If available, holds information about where in \c m_demangled certain
-  /// parts of the name (e.g., basename, arguments, etc.) begin and end.
-  mutable std::unique_ptr<DemangledNameInfo> m_demangled_info;
 };
-static_assert(sizeof(Mangled) <= 2 * sizeof(ConstString) +
-                                     sizeof(std::unique_ptr<DemangledNameInfo>),
+static_assert(sizeof(Mangled) <= 2 * sizeof(ConstString),
               "High-volume object, size of object must be increased with care");
 
 Stream &operator<<(Stream &s, const Mangled &obj);
diff --git a/lldb/include/lldb/Core/ModuleList.h b/lldb/include/lldb/Core/ModuleList.h
index aa0e7c674c1d2..4e815e8f32666 100644
--- a/lldb/include/lldb/Core/ModuleList.h
+++ b/lldb/include/lldb/Core/ModuleList.h
@@ -113,6 +113,7 @@ class ModuleListProperties : public Properties {
   FileSpec GetLLDBIndexCachePath() const;
   bool SetLLDBIndexCachePath(const FileSpec &path);
   bool GetLoadSymbolOnDemand() const;
+  uint64_t GetDemangledNameInfoCacheSize() const;
   lldb::SymbolDownload GetSymbolAutoDownload() const;
   PathMappingList GetSymlinkMappings() const;
 };
diff --git a/lldb/include/lldb/Symbol/Symbol.h b/lldb/include/lldb/Symbol/Symbol.h
index 184e1ca9bdb63..700b29b23a747 100644
--- a/lldb/include/lldb/Symbol/Symbol.h
+++ b/lldb/include/lldb/Symbol/Symbol.h
@@ -458,7 +458,7 @@ class Symbol : public SymbolContextScope {
 
 #if __SIZEOF_POINTER__ == 8
 static_assert(
-    sizeof(lldb_private::Symbol) == 80,
+    sizeof(lldb_private::Symbol) == 72,
     "Symbol is a high volume data type, size must be increased with care");
 #endif
 
diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td
index dee4cd40448bf..e28d6c77fe957 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -51,6 +51,10 @@ let Definition = "modulelist", Path = "symbols" in {
     Global,
     DefaultFalse,
     Desc<"Enable on demand symbol loading in LLDB. LLDB will load debug info on demand for each module based on various conditions (e.g. matched breakpoint, resolved stack frame addresses and matched global variables/function symbols in symbol table) to improve performance. Please refer to docs/use/ondemand.rst for details.">;
+  def DemangledNameInfoCacheSize: Property<"demangled-name-info-cache-size", "UInt64">,
+    Global,
+    DefaultUnsignedValue<16384>,
+    Desc<"The maximum number of formatted function names to cache. A value of 0 disables the cache.">;
 }
 
 let Definition = "diagnostics", Path = "diagnostics" in {
diff --git a/lldb/source/Core/DemangledNameInfo.cpp b/lldb/source/Core/DemangledNameInfo.cpp
index 16fbfda299b21..a7a578141898d 100644
--- a/lldb/source/Core/DemangledNameInfo.cpp
+++ b/lldb/source/Core/DemangledNameInfo.cpp
@@ -7,11 +7,82 @@
 //===----------------------------------------------------------------------===//
 
 #include "lldb/Core/DemangledNameInfo.h"
+#include "lldb/Core/Mangled.h"
 
 using namespace llvm::itanium_demangle;
 
 namespace lldb_private {
 
+std::optional<DemangledNameInfo>
+DemangledNameInfoCache::Get(const Mangled &mangled) {
+  ConstString mangled_name = mangled.GetMangledName();
+  if (!mangled_name)
+    return std::nullopt;
+
+  {
+    auto state = m_state.LockShared();
+    auto it = state->infos.find(mangled_name);
+    if (it != state->infos.end())
+      return it->second;
+  }
+
+  // Demangle outside of the lock. Two threads asking for the same name at the
+  // same time just compute the same info twice, which is cheaper than making
+  // every other thread wait for one demangle to finish.
+  std::optional<DemangledNameInfo> info = mangled.ComputeDemangledInfo();
+
+  auto state = m_state.Lock();
+  // Cache is disabled.
+  if (state->max_entries == 0)
+    return info;
+  // Another thread might have cached this name while we were demangling.
+  if (state->infos.contains(mangled_name))
+    return info;
+  // Make room for the new entry by dropping a random one.
+  if (state->infos.size() >= state->max_entries)
+    state->EvictRandomEntry();
+  state->infos.insert({mangled_name, info});
+  state->mangled_names.push_back(mangled_name);
+  return info;
+}
+
+void DemangledNameInfoCache::State::EvictRandomEntry() {
+  assert(!mangled_names.empty() && "evicting from an empty cache");
+  std::uniform_int_distribution<size_t> dist(0, mangled_names.size() - 1);
+  size_t index = dist(rng);
+  infos.erase(mangled_names[index]);
+  mangled_names[index] = mangled_names.back();
+  mangled_names.pop_back();
+}
+
+void DemangledNameInfoCache::Clear() {
+  auto state = m_state.Lock();
+  state->infos.clear();
+  state->mangled_names.clear();
+}
+
+size_t DemangledNameInfoCache::GetMaxEntries() const {
+  return m_state.LockShared()->max_entries;
+}
+
+size_t DemangledNameInfoCache::GetNumEntries() const {
+  return m_state.LockShared()->infos.size();
+}
+
+void DemangledNameInfoCache::SetMaxEntries(size_t max_entries) {
+  // This is called before every lookup, so avoid taking the exclusive lock
+  // when the limit didn't change.
+  if (GetMaxEntries() == max_entries)
+    return;
+
+  auto state = m_state.Lock();
+  state->max_entries = max_entries;
+  if (state->infos.size() > state->max_entries) {
+    state->infos.clear();
+    state->mangled_names.clear();
+  }
+}
+
 bool TrackingOutputBuffer::shouldTrack() const {
   if (!isPrintingTopLevelFunctionType())
     return false;
diff --git a/lldb/source/Core/Mangled.cpp b/lldb/source/Core/Mangled.cpp
index 3f278bfa40335..06aa1a97309c1 100644
--- a/lldb/source/Core/Mangled.cpp
+++ b/lldb/source/Core/Mangled.cpp
@@ -26,6 +26,7 @@
 #include "llvm/Support/Compiler.h"
 
 #include <mutex>
+#include <optional>
 #include <string>
 #include <string_view>
 #include <utility>
@@ -112,7 +113,6 @@ Mangled::operator bool() const { return m_mangled || m_demangled; }
 void Mangled::Clear() {
   m_mangled.Clear();
   m_demangled.Clear();
-  m_demangled_info.reset();
 }
 
 // Compare the string values.
@@ -126,16 +126,13 @@ void Mangled::SetValue(ConstString name) {
     if (IsMangledName(name.GetStringRef())) {
       m_demangled.Clear();
       m_mangled = name;
-      m_demangled_info.reset();
     } else {
       m_demangled = name;
       m_mangled.Clear();
-      m_demangled_info.reset();
     }
   } else {
     m_demangled.Clear();
     m_mangled.Clear();
-    m_demangled_info.reset();
   }
 }
 
@@ -281,30 +278,19 @@ bool Mangled::GetRichManglingInfo(RichManglingContext &context,
   llvm_unreachable("Fully covered switch above!");
 }
 
-ConstString Mangled::GetDemangledName() const {
-  return GetDemangledNameImpl(/*force=*/false);
-}
-
-const DemangledNameInfo *Mangled::GetDemangledInfo() const {
-  if (!m_demangled_info)
-    GetDemangledNameImpl(/*force=*/true);
-  return m_demangled_info.get();
-}
-
 // Generate the demangled name on demand using this accessor. Code in this
 // class will need to use this accessor if it wishes to decode the demangled
 // name. The result is cached and will be kept until a new string value is
 // supplied to this object, or until the end of the object's lifetime.
-ConstString Mangled::GetDemangledNameImpl(bool force) const {
+ConstString Mangled::GetDemangledName() const {
   if (!m_mangled)
     return m_demangled;
 
   // Re-use previously demangled names.
-  if (!force && !m_demangled.IsNull())
+  if (!m_demangled.IsNull())
     return m_demangled;
 
-  if (!force && m_mangled.GetMangledCounterpart(m_demangled) &&
-      !m_demangled.IsNull())
+  if (m_mangled.GetMangledCounterpart(m_demangled) && !m_demangled.IsNull())
     return m_demangled;
 
   // We didn't already mangle this name, demangle it and if all goes well
@@ -314,14 +300,9 @@ ConstString Mangled::GetDemangledNameImpl(bool force) const {
   case eManglingSchemeMSVC:
     demangled_name = GetMSVCDemangledStr(m_mangled);
     break;
-  case eManglingSchemeItanium: {
-    std::pair<char *, DemangledNameInfo> demangled =
-        GetItaniumDemangledStr(m_mangled.GetCString());
-    demangled_name = demangled.first;
-    m_demangled_info =
-        std::make_unique<DemangledNameInfo>(std::move(demangled.second));
+  case eManglingSchemeItanium:
+    demangled_name = GetItaniumDemangledStr(m_mangled.GetCString()).first;
     break;
-  }
   case eManglingSchemeRustV0:
     demangled_name = GetRustV0DemangledStr(m_mangled);
     break;
@@ -351,6 +332,18 @@ ConstString Mangled::GetDemangledNameImpl(bool force) const {
   return m_demangled;
 }
 
+std::optional<DemangledNameInfo> Mangled::ComputeDemangledInfo() const {
+  // Only supported for itanium.
+  if (!m_mangled ||
+      GetManglingScheme(m_mangled.GetStringRef()) != eManglingSchemeItanium)
+    return std::nullopt;
+
+  std::pair<char *, DemangledNameInfo> demangled =
+      GetItaniumDemangledStr(m_mangled.GetCString());
+  free(demangled.first);
+  return std::move(demangled.second);
+}
+
 ConstString Mangled::GetDisplayDemangledName() const {
   if (Language *lang = Language::FindPlugin(GuessLanguage()))
     return lang->GetDisplayDemangledName(*this);
@@ -469,7 +462,6 @@ bool Mangled::Decode(const DataExtractor &data, lldb::offset_t *offset_ptr,
                      const StringTableReader &strtab) {
   m_mangled.Clear();
   m_demangled.Clear();
-  m_demangled_info.reset();
   MangledEncoding encoding = (MangledEncoding)data.GetU8(offset_ptr);
   switch (encoding) {
     case Empty:
@@ -549,8 +541,8 @@ void Mangled::Encode(DataEncoder &file, ConstStringTable &strtab) const {
 }
 
 ConstString Mangled::GetBaseName() const {
-  const auto *demangled_info = GetDemangledInfo();
-  if (demangled_info == nullptr)
+  std::optional<DemangledNameInfo> demangled_info = ComputeDemangledInfo();
+  if (!demangled_info)
     return {};
 
   ConstString demangled_name = GetDemangledName();
diff --git a/lldb/source/Core/ModuleList.cpp b/lldb/source/Core/ModuleList.cpp
index 5b7e15f71c26c..c6a05757af6ac 100644
--- a/lldb/source/Core/ModuleList.cpp
+++ b/lldb/source/Core/ModuleList.cpp
@@ -198,6 +198,12 @@ bool ModuleListProperties::GetLoadSymbolOnDemand() const {
       idx, g_modulelist_properties[idx].default_uint_value != 0);
 }
 
+uint64_t ModuleListProperties::GetDemangledNameInfoCacheSize() const {
+  const uint32_t idx = ePropertyDemangledNameInfoCacheSize;
+  return GetPropertyAtIndexAs<uint64_t>(
+      idx, g_modulelist_properties[idx].default_uint_value);
+}
+
 ModuleList::ModuleList() : m_modules(), m_modules_mutex() {}
 
 ModuleList::ModuleList(const ModuleList &rhs) : m_modules(), m_modules_mutex() {
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index d453f9c9c4ae0..ea8e94c116a11 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -23,6 +23,7 @@
 #include "lldb/Core/DemangledNameInfo.h"
 #include "lldb/Core/Mangled.h"
 #include "lldb/Core/Module.h"
+#include "lldb/Core/ModuleList.h"
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Core/UniqueCStringMap.h"
 #include "lldb/DataFormatters/CXXFunctionPointer.h"
@@ -257,6 +258,14 @@ static bool PrettyPrintFunctionNameWithArgs(Stream &out_stream,
   return true;
 }
 
+static DemangledNameInfoCache &GetDemangledInfoCache() {
+  static DemangledNameInfoCache g_cache;
+  // Pick up any changes to the size the user made since the last call.
+  g_cache.SetMaxEntries(ModuleList::GetGlobalModuleListProperties()
+                            .GetDemangledNameInfoCacheSize());
+  return g_cache;
+}
+
 static llvm::Expected<std::pair<llvm::StringRef, DemangledNameInfo>>
 GetAndValidateInfo(const SymbolContext &sc) {
   Mangled mangled = sc.GetPossiblyInlinedFunctionName();
@@ -269,7 +278,7 @@ GetAndValidateInfo(const SymbolContext &sc) {
         "function '{0}' does not have a demangled name",
         mangled.GetMangledName());
 
-  const DemangledNameInfo *info = mangled.GetDemangledInfo();
+  std::optional<DemangledNameInfo> info = GetDemangledInfoCache().Get(mangled);
   if (!info)
     return llvm::createStringErrorV(
         "function '{0}' does not have demangled info", demangled_name);
diff --git a/lldb/source/Symbol/Symbol.cpp b/lldb/source/Symbol/Symbol.cpp
index 5fecd758fe84e..35804b600a590 100644
--- a/lldb/source/Symbol/Symbol.cpp
+++ b/lldb/source/Symbol/Symbol.cpp
@@ -772,7 +772,7 @@ bool Symbol::Decode(const DataExtractor &data, lldb::offset_t *offset_ptr,
 // the DataFileCache version number in Symtab::Encode
 // will need to be incremented as well.
 #if __SIZEOF_POINTER__ == 8
-static_assert(sizeof(lldb_private::Symbol) == 80,
+static_assert(sizeof(lldb_private::Symbol) == 72,
               "Symbol size has changed, Symbol::Encode and Decode likely need "
               "to be updated");
 #endif
diff --git a/lldb/source/Symbol/Symtab.cpp b/lldb/source/Symbol/Symtab.cpp
index 5bef28c58385d..22f3b26ed88c7 100644
--- a/lldb/source/Symbol/Symtab.cpp
+++ b/lldb/source/Symbol/Symtab.cpp
@@ -1234,7 +1234,7 @@ constexpr uint32_t CURRENT_CACHE_VERSION = 2;
 // CURRENT_CACHE_VERSION number will need to be incremented.
 #if __SIZEOF_POINTER__ == 8
 static_assert(
-    sizeof(lldb_private::Symbol) == 80,
+    sizeof(lldb_private::Symbol) == 72,
     "Symbol size has changed, DataFileCache version likely needs updating");
 #endif
 
diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp
index dc0ecde94b174..71f02053ff47b 100644
--- a/lldb/unittests/Core/MangledTest.cpp
+++ b/lldb/unittests/Core/MangledTest.cpp
@@ -353,78 +353,175 @@ static bool NameInfoEquals(const DemangledNameInfo &lhs,
                   rhs.QualifiersRange);
 }
 
-TEST(MangledTest, DemangledNameInfo_SetMangledResets) {
+TEST(MangledTest, DemangledNameInfo_SetMangled) {
   Mangled mangled;
-  EXPECT_EQ(mangled.GetDemangledInfo(), nullptr);
+  EXPECT_EQ(mangled.ComputeDemangledInfo(), std::nullopt);
 
   mangled.SetMangledName(ConstString("_Z3foov"));
   ASSERT_TRUE(mangled);
 
-  ASSERT_NE(mangled.GetDemangledInfo(), nullptr);
   // Keep a copy of the original demangled info.
-  DemangledNameInfo info1 = *mangled.GetDemangledInfo();
-  EXPECT_TRUE(info1.hasBasename());
+  std::optional<DemangledNameInfo> info1 = mangled.ComputeDemangledInfo();
+  ASSERT_NE(info1, std::nullopt);
+  EXPECT_TRUE(info1->hasBasename());
 
   mangled.SetMangledName(ConstString("_Z4funcv"));
 
   // Should have re-calculated demangled-info since mangled name changed.
-  ASSERT_NE(mangled.GetDemangledInfo(), nullptr);
-  DemangledNameInfo info2 = *mangled.GetDemangledInfo();
-  EXPECT_TRUE(info2.hasBasename());
+  std::optional<DemangledNameInfo> info2 = mangled.ComputeDemangledInfo();
+  ASSERT_NE(info2, std::nullopt);
+  EXPECT_TRUE(info2->hasBasename());
 
-  EXPECT_FALSE(NameInfoEquals(info1, info2));
+  EXPECT_FALSE(NameInfoEquals(*info1, *info2));
   EXPECT_EQ(mangled.GetDemangledName(), "func()");
 }
 
-TEST(MangledTest, DemangledNameInfo_SetDemangledResets) {
+TEST(MangledTest, DemangledNameInfo_SetDemangled) {
   Mangled mangled("_Z3foov");
   ASSERT_TRUE(mangled);
 
+  std::optional<DemangledNameInfo> info = mangled.ComputeDemangledInfo();
+  ASSERT_NE(info, std::nullopt);
+
   mangled.SetDemangledName(ConstString(""));
 
-  // Mangled name hasn't changed, so GetDemangledInfo causes re-demangling
-  // of previously set mangled name.
-  EXPECT_NE(mangled.GetDemangledInfo(), nullptr);
-  EXPECT_EQ(mangled.GetDemangledName(), "foo()");
+  // The info is computed from the mangled name, which hasn't changed.
+  std::optional<DemangledNameInfo> new_info = mangled.ComputeDemangledInfo();
+  ASSERT_NE(new_info, std::nullopt);
+  EXPECT_TRUE(NameInfoEquals(*new_info, *info));
 }
 
 TEST(MangledTest, DemangledNameInfo_Clear) {
   Mangled mangled("_Z3foov");
   ASSERT_TRUE(mangled);
-  EXPECT_NE(mangled.GetDemangledInfo(), nullptr);
+  EXPECT_NE(mangled.ComputeDemangledInfo(), std::nullopt);
 
   mangled.Clear();
 
-  EXPECT_EQ(mangled.GetDemangledInfo(), nullptr);
+  EXPECT_EQ(mangled.ComputeDemangledInfo(), std::nullopt);
 }
 
 TEST(MangledTest, DemangledNameInfo_SetValue) {
   Mangled mangled("_Z4funcv");
   ASSERT_TRUE(mangled);
 
-  ASSERT_NE(mangled.GetDemangledInfo(), nullptr);
   // Keep a copy of the original demangled info.
-  DemangledNameInfo demangled_func = *mangled.GetDemangledInfo();
+  std::optional<DemangledNameInfo> demangled_func =
+      mangled.ComputeDemangledInfo();
+  ASSERT_NE(demangled_func, std::nullopt);
 
-  // SetValue(mangled) resets demangled-info
+  // SetValue(mangled) re-computes demangled-info
   mangled.SetValue(ConstString("_Z3foov"));
-  ASSERT_NE(mangled.GetDemangledInfo(), nullptr);
-  DemangledNameInfo demangled_foo = *mangled.GetDemangledInfo();
-  EXPECT_FALSE(NameInfoEquals(demangled_foo, demangled_func));
+  std::optional<DemangledNameInfo> demangled_foo =
+      mangled.ComputeDemangledInfo();
+  ASSERT_NE(demangled_foo, std::nullopt);
+  EXPECT_FALSE(NameInfoEquals(*demangled_foo, *demangled_func));
 
-  // SetValue(demangled) resets demangled-info
+  // SetValue(demangled) re-computes demangled-info
   mangled.SetValue(ConstString("_Z4funcv"));
-  EXPECT_TRUE(NameInfoEquals(*mangled.GetDemangledInfo(), demangled_func));
+  ASSERT_NE(mangled.ComputeDemangledInfo(), std::nullopt);
+  EXPECT_TRUE(NameInfoEquals(*mangled.ComputeDemangledInfo(), *demangled_func));
 
-  // SetValue(empty) resets demangled-info
+  // SetValue(empty) leaves nothing to compute demangled-info from
   mangled.SetValue(ConstString());
-  EXPECT_EQ(mangled.GetDemangledInfo(), nullptr);
+  EXPECT_EQ(mangled.ComputeDemangledInfo(), std::nullopt);
 
   // Demangling invalid mangled name will set demangled-info
   // (without a valid basename).
   mangled.SetValue(ConstString("_Zinvalid"));
-  ASSERT_NE(mangled.GetDemangledInfo(), nullptr);
-  EXPECT_FALSE(mangled.GetDemangledInfo()->hasBasename());
+  ASSERT_NE(mangled.ComputeDemangledInfo(), std::nullopt);
+  EXPECT_FALSE(mangled.ComputeDemangledInfo()->hasBasename());
+}
+
+TEST(MangledTest, DemangledNameInfoCache_Get) {
+  DemangledNameInfoCache cache;
+
+  Mangled func("_Z4funcv");
+  Mangled foo("_Z3foov");
+
+  std::optional<DemangledNameInfo> func_info = cache.Get(func);
+  ASSERT_NE(func_info, std::nullopt);
+  ASSERT_TRUE(func_info->hasBasename());
+
+  // Repeated lookups (of both the same and a copied object) hand out the same
+  // info.
+  EXPECT_TRUE(NameInfoEquals(*cache.Get(func), *func_info));
+  EXPECT_TRUE(NameInfoEquals(*cache.Get(Mangled("_Z4funcv")), *func_info));
+
+  // Different names get their own info.
+  std::optional<DemangledNameInfo> foo_info = cache.Get(foo);
+  ASSERT_NE(foo_info, std::nullopt);
+  EXPECT_FALSE(NameInfoEquals(*foo_info, *func_info));
+
+  // Names that have no info at all (not mangled, or a scheme without info
+  // support) are reported as such, no matter how often they are looked up.
+  Mangled not_mangled("not_mangled");
+  EXPECT_EQ(cache.Get(not_mangled), std::nullopt);
+  EXPECT_EQ(cache.Get(not_mangled), std::nullopt);
+
+  cache.Clear();
+
+  EXPECT_TRUE(NameInfoEquals(*cache.Get(func), *func_info));
+}
+
+TEST(MangledTest, DemangledNameInfoCache_MaxEntries) {
+  DemangledNameInfoCache cache(/*max_entries=*/4);
+
+  std::optional<DemangledNameInfo> expected = cache.Get(Mangled("_Z4funcv"));
+  ASSERT_NE(expected, std::nullopt);
+
+  // Overflow the cache a few times over. It drops entries to stay within its
+  // limit, but never hands out anything but the correct info.
+  for (unsigned i = 0; i < 4 * cache.GetMaxEntries(); ++i) {
+    std::string basename = "foo" + std::to_string(i);
+    cache.Get(Mangled("_Z" + std::to_string(basename.size()) + basename + "v"));
+    EXPECT_TRUE(NameInfoEquals(*cache.Get(Mangled("_Z4funcv")), *expected));
+    EXPECT_LE(cache.GetNumEntries(), cache.GetMaxEntries());
+  }
+
+  // Only a single entry is dropped to make room, so a full cache stays full.
+  EXPECT_EQ(cache.GetNumEntries(), cache.GetMaxEntries());
+
+  // Further lookups keep the cache at its limit.
+  cache.Get(Mangled("_Z4funcv"));
+  EXPECT_EQ(cache.GetNumEntries(), cache.GetMaxEntries());
+
+  cache.Clear();
+  EXPECT_EQ(cache.GetNumEntries(), 0u);
+}
+
+TEST(MangledTest, DemangledNameInfoCache_SetMaxEntries) {
+  DemangledNameInfoCache cache(/*max_entries=*/4);
+
+  std::optional<DemangledNameInfo> expected = cache.Get(Mangled("_Z4funcv"));
+  ASSERT_NE(expected, std::nullopt);
+
+  cache.SetMaxEntries(8);
+  EXPECT_EQ(cache.GetMaxEntries(), 8u);
+  EXPECT_TRUE(NameInfoEquals(*cache.Get(Mangled("_Z4funcv")), *expected));
+
+  for (unsigned i = 0; i < 8; ++i) {
+    std::string basename = "foo" + std::to_string(i);
+    cache.Get(Mangled("_Z" + std::to_string(basename.size()) + basename + "v"));
+  }
+  EXPECT_EQ(cache.GetNumEntries(), 8u);
+
+  // Growing the limit keeps all entries.
+  cache.SetMaxEntries(16);
+  EXPECT_EQ(cache.GetNumEntries(), 8u);
+
+  // Shrinking the limit below the number of entries clears the cache.
+  cache.SetMaxEntries(2);
+  EXPECT_EQ(cache.GetNumEntries(), 0u);
+  EXPECT_TRUE(NameInfoEquals(*cache.Get(Mangled("_Z4funcv")), *expected));
+
+  // A limit of 0 disables the cache, but lookups still compute the info.
+  cache.SetMaxEntries(0);
+  EXPECT_EQ(cache.GetMaxEntries(), 0u);
+  EXPECT_EQ(cache.GetNumEntries(), 0u);
+  EXPECT_TRUE(NameInfoEquals(*cache.Get(Mangled("_Z4funcv")), *expected));
+  EXPECT_EQ(cache.Get(Mangled("not_mangled")), std::nullopt);
+  EXPECT_EQ(cache.GetNumEntries(), 0u);
 }
 
 struct DemanglingPartsTestCase {



More information about the lldb-commits mailing list