[Lldb-commits] [lldb] [lldb] Move demangling into a minimal D language plugin (PR #232929)

Adrian Prantl via lldb-commits lldb-commits at lists.llvm.org
Fri Oct 16 13:46:55 PDT 2026


https://github.com/adrian-prantl created https://github.com/llvm/llvm-project/pull/232929

Currently Mangled demangles D names itself, since there is no D
language plugin to hand them to.

This patch adds a minimal one, like the Rust plugin, that recognizes D
source files, registers the D mangling scheme, and demangles its names
with the code that moves out of Mangled, which leaves Mangled without
a demangler of its own.

Assisted-by: Claude

Depends on https://github.com/llvm/llvm-project/pull/232927

>From 43781d80b67f6f89d9ba3ed86102ff943a7c5db5 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Thu, 15 Oct 2026 11:13:25 -0700
Subject: [PATCH 1/6] [lldb] Move parsing of mangled names into C++ language
 plugin

Currently Symtab::InitNameIndexes() parses Itanium and MSVC names
itself, so a language plugin can't add its functions to the basename
and method indexes without patching Symtab.

This patch moves that parsing, along with
Mangled::GetRichManglingInfo(), into CPlusPlusLanguage using a new
Language::MangledNameParser interface. A parser reports a function's
basename, its context, and what the mangling says about that
context. Symtab still decides which index the name goes into, and
still infers which C++ contexts are classes from their constructors
and destructors.

Assisted-by: Claude
---
 lldb/include/lldb/Core/Mangled.h              |  54 +++----
 lldb/include/lldb/Symbol/Symtab.h             |   6 +-
 lldb/include/lldb/Target/Language.h           |  39 +++++
 lldb/source/Core/Mangled.cpp                  |  59 --------
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  |  89 +++++++++++-
 .../Language/CPlusPlus/CPlusPlusLanguage.h    |   5 +
 lldb/source/Symbol/Symtab.cpp                 | 135 ++++++++----------
 lldb/source/Target/Language.cpp               |  16 +++
 lldb/unittests/Core/MangledTest.cpp           |   3 +-
 9 files changed, 237 insertions(+), 169 deletions(-)

diff --git a/lldb/include/lldb/Core/Mangled.h b/lldb/include/lldb/Core/Mangled.h
index 622c0f82f49f77..a9c78649cfa927 100644
--- a/lldb/include/lldb/Core/Mangled.h
+++ b/lldb/include/lldb/Core/Mangled.h
@@ -48,6 +48,33 @@ class Mangled {
     eManglingSchemeSwift,
   };
 
+  /// The number of mangling schemes.
+  static constexpr size_t kNumManglingSchemes = eManglingSchemeSwift + 1;
+
+  /// What a mangled function name says about the function's declaration.
+  enum class FunctionNameKind {
+    /// No enclosing context.
+    TopLevel,
+    /// A function in a namespace or module.
+    InNamespace,
+    /// A method.
+    Method,
+    /// A constructor or destructor, so its context is a type.
+    ConstructorOrDestructor,
+    /// A function or method in a context that the mangling doesn't classify.
+    Unknown,
+  };
+
+  /// The parts of a mangled function name that its symbol is indexed by.
+  struct FunctionNameParts {
+    /// If empty, the symbol is only indexed by its mangled name.
+    ConstString basename;
+    /// The enclosing context. Only needed for ConstructorOrDestructor and
+    /// Unknown.
+    ConstString decl_context;
+    FunctionNameKind kind = FunctionNameKind::Unknown;
+  };
+
   /// Default constructor.
   ///
   /// Initialize with both mangled and demangled names empty.
@@ -195,33 +222,6 @@ class Mangled {
   ///     if there is no mangled or demangled counterpart.
   lldb::LanguageType GuessLanguage() const;
 
-  /// Function signature for filtering mangled names.
-  using SkipMangledNameFn = bool(llvm::StringRef, ManglingScheme);
-
-  /// Get rich mangling information. This is optimized for batch processing
-  /// while populating a name index. To get the pure demangled name string for
-  /// a single entity, use GetDemangledName() instead.
-  ///
-  /// For names that match the Itanium mangling scheme, this uses LLVM's
-  /// ItaniumPartialDemangler. All other names fall back to LLDB's builtin
-  /// parser currently.
-  ///
-  /// This function is thread-safe when used with different \a context
-  /// instances in different threads.
-  ///
-  /// \param[in] context
-  ///     The context for this function. A single instance can be stack-
-  ///     allocated in the caller's frame and used for multiple calls.
-  ///
-  /// \param[in] skip_mangled_name
-  ///     A filtering function for skipping entities based on name and mangling
-  ///     scheme. This can be null if unused.
-  ///
-  /// \return
-  ///     True on success, false otherwise.
-  bool GetRichManglingInfo(RichManglingContext &context,
-                           SkipMangledNameFn *skip_mangled_name);
-
   /// Try to identify the mangling scheme used.
   /// \param[in] name
   ///     The name we are attempting to identify the mangling scheme for.
diff --git a/lldb/include/lldb/Symbol/Symtab.h b/lldb/include/lldb/Symbol/Symtab.h
index 224de9e775859f..ea5e2d8044402b 100644
--- a/lldb/include/lldb/Symbol/Symtab.h
+++ b/lldb/include/lldb/Symbol/Symtab.h
@@ -346,9 +346,9 @@ class Symtab {
                                         SymbolContextList &sc_list);
 
   void RegisterMangledNameEntry(
-      uint32_t value, std::set<const char *> &class_contexts,
-      std::vector<std::pair<NameToIndexMap::Entry, const char *>> &backlog,
-      RichManglingContext &rmc);
+      uint32_t value, const Mangled::FunctionNameParts &parts,
+      std::set<const char *> &class_contexts,
+      std::vector<std::pair<NameToIndexMap::Entry, const char *>> &backlog);
 
   void RegisterBacklogEntry(const NameToIndexMap::Entry &entry,
                             const char *decl_context,
diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h
index c08994cc32e9fd..fa2c2159bd46d1 100644
--- a/lldb/include/lldb/Target/Language.h
+++ b/lldb/include/lldb/Target/Language.h
@@ -12,11 +12,13 @@
 
 #include <functional>
 #include <memory>
+#include <optional>
 #include <set>
 #include <vector>
 
 #include "lldb/Core/FormatEntity.h"
 #include "lldb/Core/Highlighter.h"
+#include "lldb/Core/Mangled.h"
 #include "lldb/Core/PluginInterface.h"
 #include "lldb/DataFormatters/DumpValueObjectOptions.h"
 #include "lldb/DataFormatters/FormatClasses.h"
@@ -167,6 +169,10 @@ class Language : public PluginInterface {
   static Language *FindPlugin(lldb::LanguageType language,
                               llvm::StringRef file_path);
 
+  /// Returns the Language that handles names mangled with \p scheme, or a
+  /// nullptr if there is none.
+  static Language *FindPluginForManglingScheme(Mangled::ManglingScheme scheme);
+
   static llvm::Expected<lldb::LanguageType>
   GetExceptionLanguageForLanguage(llvm::StringRef lang_name);
   // return false from callback to stop iterating
@@ -324,6 +330,39 @@ class Language : public PluginInterface {
     return false;
   }
 
+  /// Returns true if this language handles the names mangled with \p scheme.
+  virtual bool HandlesManglingScheme(Mangled::ManglingScheme scheme) const {
+    return false;
+  }
+
+  /// Parses mangled function names for the symbol table's name indexes.
+  ///
+  /// A symbol table uses one parser for all of its symbols, so a parser can
+  /// reuse state, such as a demangler, between names.
+  class MangledNameParser {
+  public:
+    virtual ~MangledNameParser() = default;
+
+    /// Parse \p mangled, a function name mangled with the scheme that this
+    /// parser was created for.
+    ///
+    /// \return
+    ///   The parts to index the symbol by. If their basename is empty, the
+    ///   symbol is only indexed by its mangled name. std::nullopt if this
+    ///   isn't a function name the parser understands, so that the symbol is
+    ///   indexed by its demangled name instead.
+    virtual std::optional<Mangled::FunctionNameParts>
+    Parse(const Mangled &mangled) = 0;
+  };
+
+  /// Returns a parser for the names mangled with \p scheme, a scheme that this
+  /// language handles, or a nullptr if the symbol table should index them by
+  /// their demangled names.
+  virtual std::unique_ptr<MangledNameParser>
+  CreateMangledNameParser(Mangled::ManglingScheme scheme) const {
+    return nullptr;
+  }
+
   /// An individual data formatter may apply to several types and cross language
   /// boundaries. Each of those languages may want to customize the display of
   /// values of said types by appending proper prefix/suffix information in
diff --git a/lldb/source/Core/Mangled.cpp b/lldb/source/Core/Mangled.cpp
index 06aa1a97309c1b..ce9e54cb73f9fc 100644
--- a/lldb/source/Core/Mangled.cpp
+++ b/lldb/source/Core/Mangled.cpp
@@ -10,7 +10,6 @@
 
 #include "lldb/Core/DataFileCache.h"
 #include "lldb/Core/DemangledNameInfo.h"
-#include "lldb/Core/RichManglingContext.h"
 #include "lldb/Target/Language.h"
 #include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/DataEncoder.h"
@@ -220,64 +219,6 @@ static char *GetDLangDemangledStr(llvm::StringRef M) {
   return demangled_cstr;
 }
 
-// Explicit demangling for scheduled requests during batch processing. This
-// makes use of ItaniumPartialDemangler's rich demangle info
-bool Mangled::GetRichManglingInfo(RichManglingContext &context,
-                                  SkipMangledNameFn *skip_mangled_name) {
-  // Others are not meant to arrive here. ObjC names or C's main() for example
-  // have their names stored in m_demangled, while m_mangled is empty.
-  assert(m_mangled);
-
-  // Check whether or not we are interested in this name at all.
-  ManglingScheme scheme = GetManglingScheme(m_mangled.GetStringRef());
-  if (skip_mangled_name && skip_mangled_name(m_mangled.GetStringRef(), scheme))
-    return false;
-
-  switch (scheme) {
-  case eManglingSchemeNone:
-    // The current mangled_name_filter would allow llvm_unreachable here.
-    return false;
-
-  case eManglingSchemeItanium:
-    // We want the rich mangling info here, so we don't care whether or not
-    // there is a demangled string in the pool already.
-    return context.FromItaniumName(m_mangled);
-
-  case eManglingSchemeMSVC: {
-    // We have no rich mangling for MSVC-mangled names yet, so first try to
-    // demangle it if necessary.
-    if (!m_demangled && !m_mangled.GetMangledCounterpart(m_demangled)) {
-      if (char *d = GetMSVCDemangledStr(m_mangled)) {
-        // Without the rich mangling info we have to demangle the full name.
-        // Copy it to string pool and connect the counterparts to accelerate
-        // later access in GetDemangledName().
-        m_demangled.SetStringWithMangledCounterpart(llvm::StringRef(d),
-                                                    m_mangled);
-        ::free(d);
-      } else {
-        m_demangled.SetCString("");
-      }
-    }
-
-    if (m_demangled.IsEmpty()) {
-      // Cannot demangle it, so don't try parsing.
-      return false;
-    } else {
-      // Demangled successfully, we can try and parse it with
-      // CPlusPlusLanguage::CxxMethodName.
-      return context.FromCxxMethodName(m_demangled);
-    }
-  }
-
-  case eManglingSchemeRustV0:
-  case eManglingSchemeD:
-  case eManglingSchemeSwift:
-    // Rich demangling scheme is not supported
-    return false;
-  }
-  llvm_unreachable("Fully covered switch above!");
-}
-
 // 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
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index ea8e94c116a111..653e1faf687ea8 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -25,6 +25,7 @@
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleList.h"
 #include "lldb/Core/PluginManager.h"
+#include "lldb/Core/RichManglingContext.h"
 #include "lldb/Core/UniqueCStringMap.h"
 #include "lldb/DataFormatters/CXXFunctionPointer.h"
 #include "lldb/DataFormatters/DataVisualization.h"
@@ -108,8 +109,92 @@ CPlusPlusLanguage::GetFunctionNameInfo(ConstString name) const {
 bool CPlusPlusLanguage::SymbolNameFitsToLanguage(const Mangled &mangled) const {
   auto mangling_scheme =
       Mangled::GetManglingScheme(mangled.GetMangledName().GetStringRef());
-  return mangling_scheme == Mangled::eManglingSchemeItanium ||
-         mangling_scheme == Mangled::eManglingSchemeMSVC;
+  return HandlesManglingScheme(mangling_scheme);
+}
+
+bool CPlusPlusLanguage::HandlesManglingScheme(
+    Mangled::ManglingScheme scheme) const {
+  return scheme == Mangled::eManglingSchemeItanium ||
+         scheme == Mangled::eManglingSchemeMSVC;
+}
+
+/// Returns true for the Itanium names that aren't worth indexing as functions.
+static bool SkipItaniumName(llvm::StringRef mangled) {
+  if (mangled.size() < 3 || !mangled.starts_with("_Z"))
+    return true;
+
+  // Avoid the following types of symbols in the index.
+  switch (mangled[2]) {
+  case 'G': // guard variables
+  case 'T': // virtual tables, VTT structures, typeinfo structures + names
+  case 'Z': // named local entities (if we eventually handle
+            // eSymbolTypeData, we will want this back)
+    return true;
+
+  default:
+    return false;
+  }
+}
+
+namespace {
+/// Parses Itanium or MSVC mangled function names. Their mangling doesn't tell
+/// whether a declaration context is a class or a namespace.
+class CPlusPlusMangledNameParser : public Language::MangledNameParser {
+public:
+  explicit CPlusPlusMangledNameParser(Mangled::ManglingScheme scheme)
+      : m_scheme(scheme) {}
+
+  std::optional<Mangled::FunctionNameParts>
+  Parse(const Mangled &mangled) override {
+    if (!ParseRichManglingInfo(mangled))
+      return std::nullopt;
+
+    Mangled::FunctionNameParts parts;
+    llvm::StringRef base_name = m_rmc.ParseFunctionBaseName();
+    if (base_name.empty())
+      return parts;
+
+    // Parsing the context reuses the buffer that base_name points into.
+    parts.basename = ConstString(base_name);
+    llvm::StringRef decl_context = m_rmc.ParseFunctionDeclContextName();
+    if (decl_context.empty()) {
+      parts.kind = Mangled::FunctionNameKind::TopLevel;
+      return parts;
+    }
+
+    parts.decl_context = ConstString(decl_context);
+    parts.kind = m_rmc.IsCtorOrDtor()
+                     ? Mangled::FunctionNameKind::ConstructorOrDestructor
+                     : Mangled::FunctionNameKind::Unknown;
+    return parts;
+  }
+
+private:
+  bool ParseRichManglingInfo(const Mangled &mangled) {
+    ConstString mangled_name = mangled.GetMangledName();
+    if (m_scheme == Mangled::eManglingSchemeItanium)
+      return !SkipItaniumName(mangled_name) &&
+             m_rmc.FromItaniumName(mangled_name);
+
+    // There is no rich mangling info for MSVC names, so parse the demangled
+    // name with CPlusPlusLanguage::CxxMethodName instead.
+    ConstString demangled = mangled.GetDemangledName();
+    return !demangled.IsEmpty() && m_rmc.FromCxxMethodName(demangled);
+  }
+
+  Mangled::ManglingScheme m_scheme;
+  /// Instantiation of the demangler is expensive, so better use a single one
+  /// for all entries during batch processing.
+  RichManglingContext m_rmc;
+};
+} // namespace
+
+std::unique_ptr<Language::MangledNameParser>
+CPlusPlusLanguage::CreateMangledNameParser(
+    Mangled::ManglingScheme scheme) const {
+  if (!HandlesManglingScheme(scheme))
+    return nullptr;
+  return std::make_unique<CPlusPlusMangledNameParser>(scheme);
 }
 
 ConstString CPlusPlusLanguage::GetDemangledFunctionNameWithoutArguments(
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
index cf0cd95bcbfaf3..09603c4824ad53 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
@@ -89,6 +89,11 @@ class CPlusPlusLanguage : public Language {
 
   bool SymbolNameFitsToLanguage(const Mangled &mangled) const override;
 
+  bool HandlesManglingScheme(Mangled::ManglingScheme scheme) const override;
+
+  std::unique_ptr<MangledNameParser>
+  CreateMangledNameParser(Mangled::ManglingScheme scheme) const override;
+
   bool DemangledNameContainsPath(llvm::StringRef path,
                                  ConstString demangled) const override;
 
diff --git a/lldb/source/Symbol/Symtab.cpp b/lldb/source/Symbol/Symtab.cpp
index efea0339d2ef66..c14980f050ce63 100644
--- a/lldb/source/Symbol/Symtab.cpp
+++ b/lldb/source/Symbol/Symtab.cpp
@@ -6,12 +6,13 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <array>
+#include <bitset>
 #include <map>
 #include <set>
 
 #include "lldb/Core/DataFileCache.h"
 #include "lldb/Core/Module.h"
-#include "lldb/Core/RichManglingContext.h"
 #include "lldb/Core/Section.h"
 #include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Symbol/Symbol.h"
@@ -238,43 +239,6 @@ const Symbol *Symtab::SymbolAtIndex(size_t idx) const {
   return nullptr;
 }
 
-static bool lldb_skip_name(llvm::StringRef mangled,
-                           Mangled::ManglingScheme scheme) {
-  switch (scheme) {
-  case Mangled::eManglingSchemeItanium: {
-    if (mangled.size() < 3 || !mangled.starts_with("_Z"))
-      return true;
-
-    // Avoid the following types of symbols in the index.
-    switch (mangled[2]) {
-    case 'G': // guard variables
-    case 'T': // virtual tables, VTT structures, typeinfo structures + names
-    case 'Z': // named local entities (if we eventually handle
-              // eSymbolTypeData, we will want this back)
-      return true;
-
-    default:
-      break;
-    }
-
-    // Include this name in the index.
-    return false;
-  }
-
-  // No filters for this scheme yet. Include all names in indexing.
-  case Mangled::eManglingSchemeMSVC:
-  case Mangled::eManglingSchemeRustV0:
-  case Mangled::eManglingSchemeD:
-  case Mangled::eManglingSchemeSwift:
-    return false;
-
-  // Don't try and demangle things we can't categorize.
-  case Mangled::eManglingSchemeNone:
-    return true;
-  }
-  llvm_unreachable("unknown scheme!");
-}
-
 void Symtab::InitNameIndexes() {
   // Protected function, no need to lock mutex...
   if (m_name_indexes_computed)
@@ -308,9 +272,21 @@ void Symtab::InitNameIndexes() {
   std::vector<std::pair<NameToIndexMap::Entry, const char *>> backlog;
   backlog.reserve(num_symbols / 2);
 
-  // Instantiation of the demangler is expensive, so better use a single one
-  // for all entries during batch processing.
-  RichManglingContext rmc;
+  // A single parser per mangling scheme handles all of its names, so that it
+  // can reuse its demangler. Parsers are only created for the schemes that
+  // occur, and a nullptr means that no language parses the scheme.
+  std::array<std::unique_ptr<Language::MangledNameParser>,
+             Mangled::kNumManglingSchemes>
+      parsers;
+  std::bitset<Mangled::kNumManglingSchemes> created_parsers;
+  auto get_parser = [&](Mangled::ManglingScheme scheme) {
+    if (!created_parsers.test(scheme)) {
+      created_parsers.set(scheme);
+      if (Language *lang = Language::FindPluginForManglingScheme(scheme))
+        parsers[scheme] = lang->CreateMangledNameParser(scheme);
+    }
+    return parsers[scheme].get();
+  };
   for (size_t value = 0; value < num_symbols; ++value) {
     Symbol *symbol = &m_symbols[value];
 
@@ -339,9 +315,14 @@ void Symtab::InitNameIndexes() {
 
       const SymbolType type = symbol->GetType();
       if (type == eSymbolTypeCode || type == eSymbolTypeResolver) {
-        if (mangled.GetRichManglingInfo(rmc, lldb_skip_name)) {
-          RegisterMangledNameEntry(value, class_contexts, backlog, rmc);
-          continue;
+        Mangled::ManglingScheme scheme =
+            Mangled::GetManglingScheme(name.GetStringRef());
+        if (Language::MangledNameParser *parser = get_parser(scheme)) {
+          if (std::optional<Mangled::FunctionNameParts> parts =
+                  parser->Parse(mangled)) {
+            RegisterMangledNameEntry(value, *parts, class_contexts, backlog);
+            continue;
+          }
         }
       }
     }
@@ -390,56 +371,56 @@ void Symtab::InitNameIndexes() {
 }
 
 void Symtab::RegisterMangledNameEntry(
-    uint32_t value, std::set<const char *> &class_contexts,
-    std::vector<std::pair<NameToIndexMap::Entry, const char *>> &backlog,
-    RichManglingContext &rmc) {
+    uint32_t value, const Mangled::FunctionNameParts &parts,
+    std::set<const char *> &class_contexts,
+    std::vector<std::pair<NameToIndexMap::Entry, const char *>> &backlog) {
   // Only register functions that have a base name.
-  llvm::StringRef base_name = rmc.ParseFunctionBaseName();
-  if (base_name.empty())
+  if (!parts.basename)
     return;
 
   // The base name will be our entry's name.
-  NameToIndexMap::Entry entry(ConstString(base_name), value);
-  llvm::StringRef decl_context = rmc.ParseFunctionDeclContextName();
+  NameToIndexMap::Entry entry(parts.basename, value);
 
-  // Register functions with no context.
-  if (decl_context.empty()) {
+  switch (parts.kind) {
+  case Mangled::FunctionNameKind::TopLevel:
     // This has to be a basename
-    auto &basename_to_index =
-        GetNameToSymbolIndexMap(lldb::eFunctionNameTypeBase);
-    basename_to_index.Append(entry);
+    GetNameToSymbolIndexMap(lldb::eFunctionNameTypeBase).Append(entry);
     // If there is no context (no namespaces or class scopes that come before
     // the function name) then this also could be a fullname.
-    auto &name_to_index = GetNameToSymbolIndexMap(lldb::eFunctionNameTypeNone);
-    name_to_index.Append(entry);
+    GetNameToSymbolIndexMap(lldb::eFunctionNameTypeNone).Append(entry);
     return;
-  }
 
-  // Make sure we have a pool-string pointer and see if we already know the
-  // context name.
-  const char *decl_context_ccstr = ConstString(decl_context).GetCString();
-  auto it = class_contexts.find(decl_context_ccstr);
+  case Mangled::FunctionNameKind::InNamespace:
+    GetNameToSymbolIndexMap(lldb::eFunctionNameTypeBase).Append(entry);
+    return;
+
+  case Mangled::FunctionNameKind::Method:
+    GetNameToSymbolIndexMap(lldb::eFunctionNameTypeMethod).Append(entry);
+    return;
 
-  auto &method_to_index =
-      GetNameToSymbolIndexMap(lldb::eFunctionNameTypeMethod);
   // Register constructors and destructors. They are methods and create
   // declaration contexts.
-  if (rmc.IsCtorOrDtor()) {
-    method_to_index.Append(entry);
-    if (it == class_contexts.end())
-      class_contexts.insert(it, decl_context_ccstr);
+  case Mangled::FunctionNameKind::ConstructorOrDestructor:
+    GetNameToSymbolIndexMap(lldb::eFunctionNameTypeMethod).Append(entry);
+    class_contexts.insert(parts.decl_context.GetCString());
     return;
-  }
 
-  // Register regular methods with a known declaration context.
-  if (it != class_contexts.end()) {
-    method_to_index.Append(entry);
+  case Mangled::FunctionNameKind::Unknown: {
+    const char *decl_context_ccstr = parts.decl_context.GetCString();
+
+    // Register regular methods with a known declaration context.
+    if (class_contexts.count(decl_context_ccstr)) {
+      GetNameToSymbolIndexMap(lldb::eFunctionNameTypeMethod).Append(entry);
+      return;
+    }
+
+    // Regular methods in unknown declaration contexts are put to the backlog.
+    // We will revisit them once we processed all remaining symbols.
+    backlog.push_back(std::make_pair(entry, decl_context_ccstr));
     return;
   }
-
-  // Regular methods in unknown declaration contexts are put to the backlog. We
-  // will revisit them once we processed all remaining symbols.
-  backlog.push_back(std::make_pair(entry, decl_context_ccstr));
+  }
+  llvm_unreachable("unknown function name kind");
 }
 
 void Symtab::RegisterBacklogEntry(
diff --git a/lldb/source/Target/Language.cpp b/lldb/source/Target/Language.cpp
index a4d6031b4b27d5..e7d057a4ead2dc 100644
--- a/lldb/source/Target/Language.cpp
+++ b/lldb/source/Target/Language.cpp
@@ -124,6 +124,22 @@ Language *Language::FindPlugin(LanguageType language,
   return result;
 }
 
+Language *
+Language::FindPluginForManglingScheme(Mangled::ManglingScheme scheme) {
+  if (scheme == Mangled::eManglingSchemeNone)
+    return nullptr;
+
+  Language *result = nullptr;
+  ForEach([&result, scheme](Language *language) {
+    if (language->HandlesManglingScheme(scheme)) {
+      result = language;
+      return IterationAction::Stop;
+    }
+    return IterationAction::Continue;
+  });
+  return result;
+}
+
 void Language::ForEach(
     llvm::function_ref<IterationAction(Language *)> callback) {
   // If we want to iterate over all languages, we first have to complete the
diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp
index 8a7c46d91d944f..44e070259f648d 100644
--- a/lldb/unittests/Core/MangledTest.cpp
+++ b/lldb/unittests/Core/MangledTest.cpp
@@ -153,7 +153,8 @@ TEST(MangledTest, BoolConversionOperator) {
 }
 
 TEST(MangledTest, NameIndexes_FindFunctionSymbols) {
-  SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, SymbolFileSymtab>
+  SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, SymbolFileSymtab,
+                CPlusPlusLanguage>
       subsystems;
 
   auto ExpectedFile = TestFile::fromYaml(R"(

>From 0eb76ff4889983c1ba4bf8b76945b176a452f1ff Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Thu, 15 Oct 2026 13:45:59 -0700
Subject: [PATCH 2/6] [lldb] Move Itanium and MSVC demangling into C++ language
 plugin

Currently Mangled demangles Itanium and MSVC names itself, and other
language plugins have no way to demangle the names of their own
schemes.

This patch adds Language::DemangleSymbol() and moves the Itanium and
MSVC demangling code, unchanged, into CPlusPlusLanguage. Since Mangled
now looks up the C++ plugin for every name it demangles,
FindPluginForManglingScheme() caches the plugins it finds.

Assisted-by: Claude
---
 lldb/include/lldb/Target/Language.h           | 23 +++++
 lldb/source/Core/Mangled.cpp                  | 80 ++++-------------
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  | 85 +++++++++++++++++++
 .../Language/CPlusPlus/CPlusPlusLanguage.h    |  4 +
 lldb/source/Target/Language.cpp               | 13 +++
 lldb/unittests/Core/MangledTest.cpp           | 10 +++
 lldb/unittests/Symbol/CMakeLists.txt          |  1 +
 lldb/unittests/Symbol/CallEdgeTest.cpp        |  3 +-
 8 files changed, 155 insertions(+), 64 deletions(-)

diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h
index fa2c2159bd46d1..00103040cb9c66 100644
--- a/lldb/include/lldb/Target/Language.h
+++ b/lldb/include/lldb/Target/Language.h
@@ -14,6 +14,7 @@
 #include <memory>
 #include <optional>
 #include <set>
+#include <string>
 #include <vector>
 
 #include "lldb/Core/FormatEntity.h"
@@ -335,6 +336,28 @@ class Language : public PluginInterface {
     return false;
   }
 
+  /// How to demangle a name in DemangleSymbol().
+  struct DemangleOptions {
+    /// Whether to compute the \c DemangledNameInfo too.
+    bool want_info = false;
+  };
+
+  /// A demangled name and, if requested, where its parts begin and end.
+  struct DemangledName {
+    /// Empty if the name couldn't be demangled.
+    std::string name;
+    /// Only computed if DemangleOptions::want_info is true.
+    std::optional<DemangledNameInfo> info;
+  };
+
+  /// Demangle \p mangled, a name mangled with \p scheme, a scheme that this
+  /// language handles.
+  virtual DemangledName DemangleSymbol(ConstString mangled,
+                                       Mangled::ManglingScheme scheme,
+                                       const DemangleOptions &options) const {
+    return {};
+  }
+
   /// Parses mangled function names for the symbol table's name indexes.
   ///
   /// A symbol table uses one parser for all of its symbols, so a parser can
diff --git a/lldb/source/Core/Mangled.cpp b/lldb/source/Core/Mangled.cpp
index ce9e54cb73f9fc..65311e6b346d92 100644
--- a/lldb/source/Core/Mangled.cpp
+++ b/lldb/source/Core/Mangled.cpp
@@ -136,61 +136,6 @@ void Mangled::SetValue(ConstString name) {
 }
 
 // Local helpers for different demangling implementations.
-static char *GetMSVCDemangledStr(llvm::StringRef M) {
-  char *demangled_cstr = llvm::microsoftDemangle(
-      M, nullptr, nullptr,
-      llvm::MSDemangleFlags(
-          llvm::MSDF_NoAccessSpecifier | llvm::MSDF_NoCallingConvention |
-          llvm::MSDF_NoMemberType | llvm::MSDF_NoVariableType));
-
-  if (Log *log = GetLog(LLDBLog::Demangle)) {
-    if (demangled_cstr && demangled_cstr[0])
-      LLDB_LOGF(log, "demangled msvc: %s -> \"%s\"", M.data(), demangled_cstr);
-    else
-      LLDB_LOGF(log, "demangled msvc: %s -> error", M.data());
-  }
-
-  return demangled_cstr;
-}
-
-static std::pair<char *, DemangledNameInfo>
-GetItaniumDemangledStr(const char *M) {
-  char *demangled_cstr = nullptr;
-
-  DemangledNameInfo info;
-  llvm::ItaniumPartialDemangler ipd;
-  bool err = ipd.partialDemangle(M);
-  if (!err) {
-    // Default buffer and size (OutputBuffer will realloc in case it's too
-    // small).
-    size_t demangled_size = 80;
-    demangled_cstr = static_cast<char *>(std::malloc(80));
-
-    TrackingOutputBuffer OB(demangled_cstr, demangled_size);
-    demangled_cstr = ipd.finishDemangle(&OB);
-    info = std::move(OB.NameInfo);
-
-    assert(demangled_cstr &&
-           "finishDemangle must always succeed if partialDemangle did");
-    assert(demangled_cstr[OB.getCurrentPosition() - 1] == '\0' &&
-           "Expected demangled_size to return length including trailing null");
-  }
-
-  if (Log *log = GetLog(LLDBLog::Demangle)) {
-    if (demangled_cstr)
-      LLDB_LOGF(log, "demangled itanium: %s -> \"%s\"", M, demangled_cstr);
-    else
-      LLDB_LOGF(log, "demangled itanium: %s -> error: failed to demangle", M);
-
-    if (!info.hasBasename())
-      LLDB_LOGF(log,
-                "demangled itanium: %s -> error: failed to retrieve name info",
-                M);
-  }
-
-  return {demangled_cstr, std::move(info)};
-}
-
 static char *GetRustV0DemangledStr(llvm::StringRef M) {
   char *demangled_cstr = llvm::rustDemangle(M);
 
@@ -236,13 +181,18 @@ ConstString Mangled::GetDemangledName() const {
 
   // We didn't already mangle this name, demangle it and if all goes well
   // add it to our map.
+  ManglingScheme scheme = GetManglingScheme(m_mangled.GetStringRef());
   char *demangled_name = nullptr;
-  switch (GetManglingScheme(m_mangled.GetStringRef())) {
+  switch (scheme) {
   case eManglingSchemeMSVC:
-    demangled_name = GetMSVCDemangledStr(m_mangled);
-    break;
   case eManglingSchemeItanium:
-    demangled_name = GetItaniumDemangledStr(m_mangled.GetCString()).first;
+    // The C++ language plugin demangles C++ names.
+    if (Language *lang = Language::FindPluginForManglingScheme(scheme)) {
+      Language::DemangledName demangled =
+          lang->DemangleSymbol(m_mangled, scheme, Language::DemangleOptions());
+      if (!demangled.name.empty())
+        m_demangled.SetStringWithMangledCounterpart(demangled.name, m_mangled);
+    }
     break;
   case eManglingSchemeRustV0:
     demangled_name = GetRustV0DemangledStr(m_mangled);
@@ -279,10 +229,14 @@ std::optional<DemangledNameInfo> Mangled::ComputeDemangledInfo() const {
       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);
+  Language *lang =
+      Language::FindPluginForManglingScheme(eManglingSchemeItanium);
+  if (!lang)
+    return std::nullopt;
+
+  Language::DemangleOptions options;
+  options.want_info = true;
+  return lang->DemangleSymbol(m_mangled, eManglingSchemeItanium, options).info;
 }
 
 ConstString Mangled::GetDisplayDemangledName() const {
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 653e1faf687ea8..283c8356354a9a 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -9,6 +9,7 @@
 #include "CPlusPlusLanguage.h"
 
 #include <cctype>
+#include <cstdlib>
 #include <cstring>
 
 #include <functional>
@@ -17,6 +18,7 @@
 #include <set>
 
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Demangle/Demangle.h"
 #include "llvm/Demangle/ItaniumDemangle.h"
 #include "llvm/Support/ErrorExtras.h"
 
@@ -118,6 +120,89 @@ bool CPlusPlusLanguage::HandlesManglingScheme(
          scheme == Mangled::eManglingSchemeMSVC;
 }
 
+static char *GetMSVCDemangledStr(llvm::StringRef M) {
+  char *demangled_cstr = llvm::microsoftDemangle(
+      M, nullptr, nullptr,
+      llvm::MSDemangleFlags(
+          llvm::MSDF_NoAccessSpecifier | llvm::MSDF_NoCallingConvention |
+          llvm::MSDF_NoMemberType | llvm::MSDF_NoVariableType));
+
+  if (Log *log = GetLog(LLDBLog::Demangle)) {
+    if (demangled_cstr && demangled_cstr[0])
+      LLDB_LOGF(log, "demangled msvc: %s -> \"%s\"", M.data(), demangled_cstr);
+    else
+      LLDB_LOGF(log, "demangled msvc: %s -> error", M.data());
+  }
+
+  return demangled_cstr;
+}
+
+static std::pair<char *, DemangledNameInfo>
+GetItaniumDemangledStr(const char *M) {
+  char *demangled_cstr = nullptr;
+
+  DemangledNameInfo info;
+  llvm::ItaniumPartialDemangler ipd;
+  bool err = ipd.partialDemangle(M);
+  if (!err) {
+    // Default buffer and size (OutputBuffer will realloc in case it's too
+    // small).
+    size_t demangled_size = 80;
+    demangled_cstr = static_cast<char *>(std::malloc(80));
+
+    TrackingOutputBuffer OB(demangled_cstr, demangled_size);
+    demangled_cstr = ipd.finishDemangle(&OB);
+    info = std::move(OB.NameInfo);
+
+    assert(demangled_cstr &&
+           "finishDemangle must always succeed if partialDemangle did");
+    assert(demangled_cstr[OB.getCurrentPosition() - 1] == '\0' &&
+           "Expected demangled_size to return length including trailing null");
+  }
+
+  if (Log *log = GetLog(LLDBLog::Demangle)) {
+    if (demangled_cstr)
+      LLDB_LOGF(log, "demangled itanium: %s -> \"%s\"", M, demangled_cstr);
+    else
+      LLDB_LOGF(log, "demangled itanium: %s -> error: failed to demangle", M);
+
+    if (!info.hasBasename())
+      LLDB_LOGF(log,
+                "demangled itanium: %s -> error: failed to retrieve name info",
+                M);
+  }
+
+  return {demangled_cstr, std::move(info)};
+}
+
+Language::DemangledName
+CPlusPlusLanguage::DemangleSymbol(ConstString mangled,
+                                  Mangled::ManglingScheme scheme,
+                                  const DemangleOptions &options) const {
+  DemangledName demangled;
+  switch (scheme) {
+  case Mangled::eManglingSchemeItanium: {
+    auto [demangled_cstr, info] = GetItaniumDemangledStr(mangled.GetCString());
+    if (demangled_cstr) {
+      demangled.name = demangled_cstr;
+      std::free(demangled_cstr);
+    }
+    if (options.want_info)
+      demangled.info = std::move(info);
+    break;
+  }
+  case Mangled::eManglingSchemeMSVC:
+    if (char *demangled_cstr = GetMSVCDemangledStr(mangled.GetStringRef())) {
+      demangled.name = demangled_cstr;
+      std::free(demangled_cstr);
+    }
+    break;
+  default:
+    break;
+  }
+  return demangled;
+}
+
 /// Returns true for the Itanium names that aren't worth indexing as functions.
 static bool SkipItaniumName(llvm::StringRef mangled) {
   if (mangled.size() < 3 || !mangled.starts_with("_Z"))
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
index 09603c4824ad53..7abd311dbf8d89 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
@@ -91,6 +91,10 @@ class CPlusPlusLanguage : public Language {
 
   bool HandlesManglingScheme(Mangled::ManglingScheme scheme) const override;
 
+  DemangledName DemangleSymbol(ConstString mangled,
+                               Mangled::ManglingScheme scheme,
+                               const DemangleOptions &options) const override;
+
   std::unique_ptr<MangledNameParser>
   CreateMangledNameParser(Mangled::ManglingScheme scheme) const override;
 
diff --git a/lldb/source/Target/Language.cpp b/lldb/source/Target/Language.cpp
index e7d057a4ead2dc..feb0fa9d19814d 100644
--- a/lldb/source/Target/Language.cpp
+++ b/lldb/source/Target/Language.cpp
@@ -6,6 +6,8 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <array>
+#include <atomic>
 #include <functional>
 #include <map>
 #include <mutex>
@@ -129,6 +131,15 @@ Language::FindPluginForManglingScheme(Mangled::ManglingScheme scheme) {
   if (scheme == Mangled::eManglingSchemeNone)
     return nullptr;
 
+  // Mangled calls this for every name it demangles with a plugin, so avoid
+  // taking the lock in ForEach() each time. Language instances are never
+  // destroyed, so the plugin can be cached once it's found. Misses aren't
+  // cached, since the plugin may not be registered yet.
+  static std::array<std::atomic<Language *>, Mangled::kNumManglingSchemes>
+      g_plugins;
+  if (Language *language = g_plugins[scheme].load(std::memory_order_acquire))
+    return language;
+
   Language *result = nullptr;
   ForEach([&result, scheme](Language *language) {
     if (language->HandlesManglingScheme(scheme)) {
@@ -137,6 +148,8 @@ Language::FindPluginForManglingScheme(Mangled::ManglingScheme scheme) {
     }
     return IterationAction::Continue;
   });
+  if (result)
+    g_plugins[scheme].store(result, std::memory_order_release);
   return result;
 }
 
diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp
index 44e070259f648d..3d6d236c476749 100644
--- a/lldb/unittests/Core/MangledTest.cpp
+++ b/lldb/unittests/Core/MangledTest.cpp
@@ -54,6 +54,7 @@ struct TrackingOutputBufferDeleter {
 };
 
 TEST(MangledTest, ResultForValidName) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   ConstString MangledName("_ZN1a1b1cIiiiEEvm");
   Mangled TheMangled(MangledName);
   ConstString TheDemangled = TheMangled.GetDemangledName();
@@ -63,6 +64,7 @@ TEST(MangledTest, ResultForValidName) {
 }
 
 TEST(MangledTest, ResultForBlockInvocation) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   ConstString MangledName("___Z1fU13block_pointerFviE_block_invoke");
   Mangled TheMangled(MangledName);
   ConstString TheDemangled = TheMangled.GetDemangledName();
@@ -73,6 +75,7 @@ TEST(MangledTest, ResultForBlockInvocation) {
 }
 
 TEST(MangledTest, EmptyForInvalidName) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   ConstString MangledName("_ZN1a1b1cmxktpEEvm");
   Mangled TheMangled(MangledName);
   ConstString TheDemangled = TheMangled.GetDemangledName();
@@ -355,6 +358,7 @@ static bool NameInfoEquals(const DemangledNameInfo &lhs,
 }
 
 TEST(MangledTest, DemangledNameInfo_SetMangled) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   Mangled mangled;
   EXPECT_EQ(mangled.ComputeDemangledInfo(), std::nullopt);
 
@@ -378,6 +382,7 @@ TEST(MangledTest, DemangledNameInfo_SetMangled) {
 }
 
 TEST(MangledTest, DemangledNameInfo_SetDemangled) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   Mangled mangled("_Z3foov");
   ASSERT_TRUE(mangled);
 
@@ -393,6 +398,7 @@ TEST(MangledTest, DemangledNameInfo_SetDemangled) {
 }
 
 TEST(MangledTest, DemangledNameInfo_Clear) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   Mangled mangled("_Z3foov");
   ASSERT_TRUE(mangled);
   EXPECT_NE(mangled.ComputeDemangledInfo(), std::nullopt);
@@ -403,6 +409,7 @@ TEST(MangledTest, DemangledNameInfo_Clear) {
 }
 
 TEST(MangledTest, DemangledNameInfo_SetValue) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   Mangled mangled("_Z4funcv");
   ASSERT_TRUE(mangled);
 
@@ -441,6 +448,7 @@ static Mangled MakeUniqueFunctionName(unsigned i) {
 }
 
 TEST(MangledTest, DemangledNameInfoCache_Get) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   DemangledNameInfoCache cache;
 
   Mangled func("_Z4funcv");
@@ -471,6 +479,7 @@ TEST(MangledTest, DemangledNameInfoCache_Get) {
 }
 
 TEST(MangledTest, DemangledNameInfoCache_MaxEntries) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   DemangledNameInfoCache cache;
   cache.SetMaxEntries(4);
 
@@ -497,6 +506,7 @@ TEST(MangledTest, DemangledNameInfoCache_MaxEntries) {
 }
 
 TEST(MangledTest, DemangledNameInfoCache_SetMaxEntries) {
+  SubsystemRAII<CPlusPlusLanguage> subsystems;
   DemangledNameInfoCache cache;
   cache.SetMaxEntries(4);
 
diff --git a/lldb/unittests/Symbol/CMakeLists.txt b/lldb/unittests/Symbol/CMakeLists.txt
index a8501d5d16aa72..5682fe193d8a03 100644
--- a/lldb/unittests/Symbol/CMakeLists.txt
+++ b/lldb/unittests/Symbol/CMakeLists.txt
@@ -23,6 +23,7 @@ add_lldb_unittest(SymbolTests
     lldbHost
     lldbSymbol
     lldbUtilityHelpers
+    lldbPluginCPlusPlusLanguage
     lldbPluginObjectFileELF
     lldbPluginObjectFileMachO
     lldbPluginSymbolFileDWARF
diff --git a/lldb/unittests/Symbol/CallEdgeTest.cpp b/lldb/unittests/Symbol/CallEdgeTest.cpp
index 3a7b188e8b0acd..e7a1b70a5063e2 100644
--- a/lldb/unittests/Symbol/CallEdgeTest.cpp
+++ b/lldb/unittests/Symbol/CallEdgeTest.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h"
 #include "Plugins/ObjectFile/Mach-O/ObjectFileMachO.h"
 #include "Plugins/SymbolFile/DWARF/SymbolFileDWARF.h"
 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
@@ -30,7 +31,7 @@ using namespace lldb_private::plugin::dwarf;
 namespace {
 class CallEdgeTest : public testing::Test {
   SubsystemRAII<FileSystem, HostInfo, ObjectFileMachO, SymbolFileDWARF,
-                TypeSystemClang>
+                TypeSystemClang, CPlusPlusLanguage>
       subsystems;
 
 public:

>From c5ee76db4422b58ca3212d12485cfdc8397da913 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Thu, 15 Oct 2026 13:48:00 -0700
Subject: [PATCH 3/6] [lldb] Let language plugins register their mangling
 schemes

Currently Mangled recognizes the names of each mangling scheme by
hardcoded prefixes, so a language plugin can't bring a scheme that
LLDB doesn't already know.

This patch adds a registry of mangling schemes to Mangled. Language
plugins register the prefixes of their schemes when they are
initialized, and Mangled hands the names of those schemes to the
plugin that registered them. The registry also records each scheme's
language, which replaces Language::HandlesManglingScheme().

CPlusPlusLanguage now registers the Itanium and MSVC schemes.

Assisted-by: Claude
---
 lldb/include/lldb/Core/Mangled.h              |  41 +++-
 lldb/include/lldb/Target/Language.h           |  15 +-
 lldb/source/Core/Mangled.cpp                  | 201 +++++++++++++++---
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  |  41 ++--
 .../Language/CPlusPlus/CPlusPlusLanguage.h    |   2 -
 lldb/source/Target/Language.cpp               |  31 ++-
 lldb/unittests/Core/MangledTest.cpp           |  40 ++++
 lldb/unittests/Symbol/TestTypeSystemClang.cpp |   3 +-
 8 files changed, 292 insertions(+), 82 deletions(-)

diff --git a/lldb/include/lldb/Core/Mangled.h b/lldb/include/lldb/Core/Mangled.h
index a9c78649cfa927..9b32040b70ec0d 100644
--- a/lldb/include/lldb/Core/Mangled.h
+++ b/lldb/include/lldb/Core/Mangled.h
@@ -14,9 +14,11 @@
 #include "lldb/lldb-enumerations.h"
 #include "lldb/lldb-forward.h"
 #include "lldb/lldb-types.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringRef.h"
 
 #include <cstddef>
+#include <cstdint>
 #include <optional>
 
 namespace lldb_private {
@@ -39,17 +41,20 @@ class Mangled {
     ePreferDemangledWithoutArguments
   };
 
-  enum ManglingScheme {
+  /// The underlying type is fixed so that the schemes that language plugins
+  /// register, which have no enumerators, are valid values too.
+  enum ManglingScheme : uint8_t {
     eManglingSchemeNone = 0,
-    eManglingSchemeMSVC,
-    eManglingSchemeItanium,
     eManglingSchemeRustV0,
     eManglingSchemeD,
     eManglingSchemeSwift,
+    /// The first of the schemes that language plugins register with
+    /// RegisterManglingScheme().
+    eFirstRegisteredManglingScheme,
   };
 
-  /// The number of mangling schemes.
-  static constexpr size_t kNumManglingSchemes = eManglingSchemeSwift + 1;
+  /// The number of mangling schemes, including all that can be registered.
+  static constexpr size_t kNumManglingSchemes = 16;
 
   /// What a mangled function name says about the function's declaration.
   enum class FunctionNameKind {
@@ -233,6 +238,32 @@ class Mangled {
 
   static bool IsMangledName(llvm::StringRef name);
 
+  /// Returns true if \p name, which starts with one of the prefixes of a
+  /// mangling scheme, is a name of that scheme.
+  using ManglingSchemeMatcher = bool (*)(llvm::StringRef name);
+
+  /// Register a mangling scheme whose names start with one of \p prefixes,
+  /// for the language plugin of \p language to demangle and parse. If the
+  /// prefixes alone don't tell the names of the scheme apart, \p matches
+  /// checks the rest of each name. Language plugins call this when they are
+  /// initialized. Registering the same scheme again returns the same scheme.
+  ///
+  /// \return
+  ///     The new scheme, or eManglingSchemeNone if there is no room for
+  ///     another one.
+  static ManglingScheme
+  RegisterManglingScheme(llvm::ArrayRef<llvm::StringRef> prefixes,
+                         lldb::LanguageType language,
+                         ManglingSchemeMatcher matches = nullptr);
+
+  /// Stop recognizing the names of \p scheme, a scheme returned by
+  /// RegisterManglingScheme().
+  static void UnregisterManglingScheme(ManglingScheme scheme);
+
+  /// Returns the language that registered \p scheme, or eLanguageTypeUnknown
+  /// if no language did.
+  static lldb::LanguageType GetLanguageForManglingScheme(ManglingScheme scheme);
+
   /// Decode a serialized version of this object from data.
   ///
   /// \param data
diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h
index 00103040cb9c66..bd0eb7490346e7 100644
--- a/lldb/include/lldb/Target/Language.h
+++ b/lldb/include/lldb/Target/Language.h
@@ -170,8 +170,8 @@ class Language : public PluginInterface {
   static Language *FindPlugin(lldb::LanguageType language,
                               llvm::StringRef file_path);
 
-  /// Returns the Language that handles names mangled with \p scheme, or a
-  /// nullptr if there is none.
+  /// Returns the Language that registered \p scheme with
+  /// Mangled::RegisterManglingScheme(), or a nullptr if none did.
   static Language *FindPluginForManglingScheme(Mangled::ManglingScheme scheme);
 
   static llvm::Expected<lldb::LanguageType>
@@ -331,11 +331,6 @@ class Language : public PluginInterface {
     return false;
   }
 
-  /// Returns true if this language handles the names mangled with \p scheme.
-  virtual bool HandlesManglingScheme(Mangled::ManglingScheme scheme) const {
-    return false;
-  }
-
   /// How to demangle a name in DemangleSymbol().
   struct DemangleOptions {
     /// Whether to compute the \c DemangledNameInfo too.
@@ -351,7 +346,7 @@ class Language : public PluginInterface {
   };
 
   /// Demangle \p mangled, a name mangled with \p scheme, a scheme that this
-  /// language handles.
+  /// language registered with Mangled::RegisterManglingScheme().
   virtual DemangledName DemangleSymbol(ConstString mangled,
                                        Mangled::ManglingScheme scheme,
                                        const DemangleOptions &options) const {
@@ -379,8 +374,8 @@ class Language : public PluginInterface {
   };
 
   /// Returns a parser for the names mangled with \p scheme, a scheme that this
-  /// language handles, or a nullptr if the symbol table should index them by
-  /// their demangled names.
+  /// language registered, or a nullptr if the symbol table should index them
+  /// by their demangled names.
   virtual std::unique_ptr<MangledNameParser>
   CreateMangledNameParser(Mangled::ManglingScheme scheme) const {
     return nullptr;
diff --git a/lldb/source/Core/Mangled.cpp b/lldb/source/Core/Mangled.cpp
index 65311e6b346d92..307b12e671c1e6 100644
--- a/lldb/source/Core/Mangled.cpp
+++ b/lldb/source/Core/Mangled.cpp
@@ -19,16 +19,20 @@
 #include "lldb/Utility/Stream.h"
 #include "lldb/lldb-enumerations.h"
 
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Demangle/Demangle.h"
 #include "llvm/Support/Compiler.h"
 
+#include <array>
+#include <atomic>
 #include <mutex>
 #include <optional>
 #include <string>
 #include <string_view>
 #include <utility>
+#include <vector>
 
 #include <cstdlib>
 #include <cstring>
@@ -36,6 +40,152 @@ using namespace lldb_private;
 
 #pragma mark Mangled
 
+namespace {
+/// A mangling scheme that a language plugin registered.
+struct RegisteredManglingScheme {
+  std::vector<std::string> prefixes;
+  /// If set, a name that starts with one of the prefixes must also match.
+  Mangled::ManglingSchemeMatcher matches = nullptr;
+  lldb::LanguageType language = lldb::eLanguageTypeUnknown;
+  /// Schemes are never removed, so that GetManglingScheme() can read them
+  /// without a lock. Unregistered schemes are only marked inactive. No other
+  /// data is published through this flag, so it needs no memory ordering.
+  std::atomic<bool> active = false;
+};
+
+/// The mangling schemes that language plugins registered.
+class ManglingSchemeRegistry {
+public:
+  Mangled::ManglingScheme Register(llvm::ArrayRef<llvm::StringRef> prefixes,
+                                   lldb::LanguageType language,
+                                   Mangled::ManglingSchemeMatcher matches);
+  void Unregister(Mangled::ManglingScheme scheme);
+  lldb::LanguageType GetLanguage(Mangled::ManglingScheme scheme) const;
+  Mangled::ManglingScheme Find(llvm::StringRef name) const;
+
+private:
+  /// Serializes registrations.
+  std::mutex m_mutex;
+  std::array<RegisteredManglingScheme,
+             Mangled::kNumManglingSchemes -
+                 Mangled::eFirstRegisteredManglingScheme>
+      m_schemes;
+  /// The number of initialized entries in m_schemes. Incrementing it publishes
+  /// a new entry to readers.
+  std::atomic<size_t> m_num_schemes = 0;
+};
+} // namespace
+
+static ManglingSchemeRegistry &GetManglingSchemeRegistry() {
+  // NOTE: INTENTIONAL LEAK due to global destructor chain.
+  static ManglingSchemeRegistry *g_registry = new ManglingSchemeRegistry();
+  return *g_registry;
+}
+
+static Mangled::ManglingScheme SchemeForIndex(size_t index) {
+  return static_cast<Mangled::ManglingScheme>(
+      Mangled::eFirstRegisteredManglingScheme + index);
+}
+
+/// Returns true if a name could start with both one of \p prefixes and a
+/// prefix of one of the active \p schemes.
+[[maybe_unused]] static bool
+OverlapsActiveScheme(llvm::ArrayRef<llvm::StringRef> prefixes,
+                     llvm::ArrayRef<RegisteredManglingScheme> schemes) {
+  for (const RegisteredManglingScheme &scheme : schemes) {
+    if (!scheme.active.load(std::memory_order_relaxed))
+      continue;
+    for (llvm::StringRef prefix : prefixes)
+      for (llvm::StringRef other : scheme.prefixes)
+        if (prefix.starts_with(other) || other.starts_with(prefix))
+          return true;
+  }
+  return false;
+}
+
+Mangled::ManglingScheme
+ManglingSchemeRegistry::Register(llvm::ArrayRef<llvm::StringRef> prefixes,
+                                 lldb::LanguageType language,
+                                 Mangled::ManglingSchemeMatcher matches) {
+  std::lock_guard<std::mutex> guard(m_mutex);
+  const size_t num_schemes = m_num_schemes.load(std::memory_order_relaxed);
+  size_t index = 0;
+  for (; index < num_schemes; ++index) {
+    const RegisteredManglingScheme &scheme = m_schemes[index];
+    if (scheme.language == language && scheme.matches == matches &&
+        llvm::equal(scheme.prefixes, prefixes))
+      break;
+  }
+
+  if (index == num_schemes) {
+    assert(index < m_schemes.size() && "too many mangling schemes");
+    if (index >= m_schemes.size())
+      return Mangled::eManglingSchemeNone;
+    assert(!OverlapsActiveScheme(
+               prefixes, llvm::ArrayRef(m_schemes.data(), num_schemes)) &&
+           "mangling scheme prefixes overlap");
+    m_schemes[index].prefixes.assign(prefixes.begin(), prefixes.end());
+    m_schemes[index].matches = matches;
+    m_schemes[index].language = language;
+    m_num_schemes.store(num_schemes + 1, std::memory_order_release);
+  }
+  m_schemes[index].active.store(true, std::memory_order_relaxed);
+  return SchemeForIndex(index);
+}
+
+void ManglingSchemeRegistry::Unregister(Mangled::ManglingScheme scheme) {
+  if (scheme < Mangled::eFirstRegisteredManglingScheme)
+    return;
+  size_t index = scheme - Mangled::eFirstRegisteredManglingScheme;
+  if (index < m_num_schemes.load(std::memory_order_acquire))
+    m_schemes[index].active.store(false, std::memory_order_relaxed);
+}
+
+lldb::LanguageType
+ManglingSchemeRegistry::GetLanguage(Mangled::ManglingScheme scheme) const {
+  if (scheme < Mangled::eFirstRegisteredManglingScheme)
+    return lldb::eLanguageTypeUnknown;
+  size_t index = scheme - Mangled::eFirstRegisteredManglingScheme;
+  if (index >= m_num_schemes.load(std::memory_order_acquire))
+    return lldb::eLanguageTypeUnknown;
+  return m_schemes[index].language;
+}
+
+Mangled::ManglingScheme
+ManglingSchemeRegistry::Find(llvm::StringRef name) const {
+  const size_t num_schemes = m_num_schemes.load(std::memory_order_acquire);
+  for (size_t index = 0; index < num_schemes; ++index) {
+    const RegisteredManglingScheme &scheme = m_schemes[index];
+    if (!scheme.active.load(std::memory_order_relaxed))
+      continue;
+    for (llvm::StringRef prefix : scheme.prefixes) {
+      if (!name.starts_with(prefix))
+        continue;
+      // Prefixes don't overlap, so no other scheme can match the name.
+      if (scheme.matches && !scheme.matches(name))
+        return Mangled::eManglingSchemeNone;
+      return SchemeForIndex(index);
+    }
+  }
+  return Mangled::eManglingSchemeNone;
+}
+
+Mangled::ManglingScheme
+Mangled::RegisterManglingScheme(llvm::ArrayRef<llvm::StringRef> prefixes,
+                                lldb::LanguageType language,
+                                ManglingSchemeMatcher matches) {
+  return GetManglingSchemeRegistry().Register(prefixes, language, matches);
+}
+
+void Mangled::UnregisterManglingScheme(ManglingScheme scheme) {
+  GetManglingSchemeRegistry().Unregister(scheme);
+}
+
+lldb::LanguageType
+Mangled::GetLanguageForManglingScheme(ManglingScheme scheme) {
+  return GetManglingSchemeRegistry().GetLanguage(scheme);
+}
+
 bool Mangled::IsMangledName(llvm::StringRef name) {
   return Mangled::GetManglingScheme(name) != Mangled::eManglingSchemeNone;
 }
@@ -44,9 +194,6 @@ Mangled::ManglingScheme Mangled::GetManglingScheme(llvm::StringRef name) {
   if (name.empty())
     return Mangled::eManglingSchemeNone;
 
-  if (name.starts_with("?"))
-    return Mangled::eManglingSchemeMSVC;
-
   if (name.starts_with("_R"))
     return Mangled::eManglingSchemeRustV0;
 
@@ -60,13 +207,6 @@ Mangled::ManglingScheme Mangled::GetManglingScheme(llvm::StringRef name) {
       return Mangled::eManglingSchemeD;
   }
 
-  if (name.starts_with("_Z"))
-    return Mangled::eManglingSchemeItanium;
-
-  // ___Z is a clang extension of block invocations
-  if (name.starts_with("___Z"))
-    return Mangled::eManglingSchemeItanium;
-
   // Swift's older style of mangling used "_T" as a mangling prefix. This can
   // lead to false positives with other symbols that just so happen to start
   // with "_T". To minimize the chance of that happening, we only return true
@@ -87,7 +227,7 @@ Mangled::ManglingScheme Mangled::GetManglingScheme(llvm::StringRef name) {
       name.starts_with("@__swiftmacro_"))
     return Mangled::eManglingSchemeSwift;
 
-  return Mangled::eManglingSchemeNone;
+  return GetManglingSchemeRegistry().Find(name);
 }
 
 Mangled::Mangled(ConstString s) : m_mangled(), m_demangled() {
@@ -184,16 +324,6 @@ ConstString Mangled::GetDemangledName() const {
   ManglingScheme scheme = GetManglingScheme(m_mangled.GetStringRef());
   char *demangled_name = nullptr;
   switch (scheme) {
-  case eManglingSchemeMSVC:
-  case eManglingSchemeItanium:
-    // The C++ language plugin demangles C++ names.
-    if (Language *lang = Language::FindPluginForManglingScheme(scheme)) {
-      Language::DemangledName demangled =
-          lang->DemangleSymbol(m_mangled, scheme, Language::DemangleOptions());
-      if (!demangled.name.empty())
-        m_demangled.SetStringWithMangledCounterpart(demangled.name, m_mangled);
-    }
-    break;
   case eManglingSchemeRustV0:
     demangled_name = GetRustV0DemangledStr(m_mangled);
     break;
@@ -207,6 +337,15 @@ ConstString Mangled::GetDemangledName() const {
   case eManglingSchemeNone:
     // Don't bother demangling anything that isn't mangled.
     break;
+  default:
+    // Language plugins demangle the schemes they registered.
+    if (Language *lang = Language::FindPluginForManglingScheme(scheme)) {
+      Language::DemangledName demangled =
+          lang->DemangleSymbol(m_mangled, scheme, Language::DemangleOptions());
+      if (!demangled.name.empty())
+        m_demangled.SetStringWithMangledCounterpart(demangled.name, m_mangled);
+    }
+    break;
   }
 
   if (demangled_name) {
@@ -224,19 +363,19 @@ ConstString Mangled::GetDemangledName() const {
 }
 
 std::optional<DemangledNameInfo> Mangled::ComputeDemangledInfo() const {
-  // Only supported for itanium.
-  if (!m_mangled ||
-      GetManglingScheme(m_mangled.GetStringRef()) != eManglingSchemeItanium)
+  if (!m_mangled)
     return std::nullopt;
 
-  Language *lang =
-      Language::FindPluginForManglingScheme(eManglingSchemeItanium);
+  // Only the language plugins compute the info, for the schemes they
+  // registered.
+  ManglingScheme scheme = GetManglingScheme(m_mangled.GetStringRef());
+  Language *lang = Language::FindPluginForManglingScheme(scheme);
   if (!lang)
     return std::nullopt;
 
   Language::DemangleOptions options;
   options.want_info = true;
-  return lang->DemangleSymbol(m_mangled, eManglingSchemeItanium, options).info;
+  return lang->DemangleSymbol(m_mangled, scheme, options).info;
 }
 
 ConstString Mangled::GetDisplayDemangledName() const {
@@ -302,6 +441,14 @@ void Mangled::DumpDebug(Stream *s) const {
 // of mangling names from a given language, likewise the compilation units
 // within those targets.
 lldb::LanguageType Mangled::GuessLanguage() const {
+  // The language that registered the mangling scheme knows the name best.
+  if (m_mangled) {
+    lldb::LanguageType language = GetLanguageForManglingScheme(
+        GetManglingScheme(m_mangled.GetStringRef()));
+    if (language != lldb::eLanguageTypeUnknown)
+      return language;
+  }
+
   lldb::LanguageType result = lldb::eLanguageTypeUnknown;
   // Ask each language plugin to check if the mangled name belongs to it.
   Language::ForEach([this, &result](Language *l) {
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 283c8356354a9a..03646da8ca5b1f 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -61,12 +61,28 @@ using namespace lldb_private::formatters;
 
 LLDB_PLUGIN_DEFINE(CPlusPlusLanguage)
 
+/// The mangling schemes that this plugin registered.
+static Mangled::ManglingScheme g_itanium_scheme = Mangled::eManglingSchemeNone;
+static Mangled::ManglingScheme g_msvc_scheme = Mangled::eManglingSchemeNone;
+
+static bool IsCPlusPlusManglingScheme(Mangled::ManglingScheme scheme) {
+  return scheme != Mangled::eManglingSchemeNone &&
+         (scheme == g_itanium_scheme || scheme == g_msvc_scheme);
+}
+
 void CPlusPlusLanguage::Initialize() {
   PluginManager::RegisterPlugin(GetPluginNameStatic(), "C++ Language",
                                 CreateInstance, &DebuggerInitialize);
+  // "___Z" is a clang extension for block invocations.
+  g_itanium_scheme =
+      Mangled::RegisterManglingScheme({"_Z", "___Z"}, eLanguageTypeC_plus_plus);
+  g_msvc_scheme =
+      Mangled::RegisterManglingScheme({"?"}, eLanguageTypeC_plus_plus);
 }
 
 void CPlusPlusLanguage::Terminate() {
+  Mangled::UnregisterManglingScheme(g_msvc_scheme);
+  Mangled::UnregisterManglingScheme(g_itanium_scheme);
   PluginManager::UnregisterPlugin(CreateInstance);
 }
 
@@ -109,15 +125,8 @@ CPlusPlusLanguage::GetFunctionNameInfo(ConstString name) const {
 }
 
 bool CPlusPlusLanguage::SymbolNameFitsToLanguage(const Mangled &mangled) const {
-  auto mangling_scheme =
-      Mangled::GetManglingScheme(mangled.GetMangledName().GetStringRef());
-  return HandlesManglingScheme(mangling_scheme);
-}
-
-bool CPlusPlusLanguage::HandlesManglingScheme(
-    Mangled::ManglingScheme scheme) const {
-  return scheme == Mangled::eManglingSchemeItanium ||
-         scheme == Mangled::eManglingSchemeMSVC;
+  return IsCPlusPlusManglingScheme(
+      Mangled::GetManglingScheme(mangled.GetMangledName().GetStringRef()));
 }
 
 static char *GetMSVCDemangledStr(llvm::StringRef M) {
@@ -180,8 +189,7 @@ CPlusPlusLanguage::DemangleSymbol(ConstString mangled,
                                   Mangled::ManglingScheme scheme,
                                   const DemangleOptions &options) const {
   DemangledName demangled;
-  switch (scheme) {
-  case Mangled::eManglingSchemeItanium: {
+  if (scheme == g_itanium_scheme) {
     auto [demangled_cstr, info] = GetItaniumDemangledStr(mangled.GetCString());
     if (demangled_cstr) {
       demangled.name = demangled_cstr;
@@ -189,16 +197,11 @@ CPlusPlusLanguage::DemangleSymbol(ConstString mangled,
     }
     if (options.want_info)
       demangled.info = std::move(info);
-    break;
-  }
-  case Mangled::eManglingSchemeMSVC:
+  } else if (scheme == g_msvc_scheme) {
     if (char *demangled_cstr = GetMSVCDemangledStr(mangled.GetStringRef())) {
       demangled.name = demangled_cstr;
       std::free(demangled_cstr);
     }
-    break;
-  default:
-    break;
   }
   return demangled;
 }
@@ -257,7 +260,7 @@ class CPlusPlusMangledNameParser : public Language::MangledNameParser {
 private:
   bool ParseRichManglingInfo(const Mangled &mangled) {
     ConstString mangled_name = mangled.GetMangledName();
-    if (m_scheme == Mangled::eManglingSchemeItanium)
+    if (m_scheme == g_itanium_scheme)
       return !SkipItaniumName(mangled_name) &&
              m_rmc.FromItaniumName(mangled_name);
 
@@ -277,7 +280,7 @@ class CPlusPlusMangledNameParser : public Language::MangledNameParser {
 std::unique_ptr<Language::MangledNameParser>
 CPlusPlusLanguage::CreateMangledNameParser(
     Mangled::ManglingScheme scheme) const {
-  if (!HandlesManglingScheme(scheme))
+  if (!IsCPlusPlusManglingScheme(scheme))
     return nullptr;
   return std::make_unique<CPlusPlusMangledNameParser>(scheme);
 }
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
index 7abd311dbf8d89..f69830ba72ca10 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
@@ -89,8 +89,6 @@ class CPlusPlusLanguage : public Language {
 
   bool SymbolNameFitsToLanguage(const Mangled &mangled) const override;
 
-  bool HandlesManglingScheme(Mangled::ManglingScheme scheme) const override;
-
   DemangledName DemangleSymbol(ConstString mangled,
                                Mangled::ManglingScheme scheme,
                                const DemangleOptions &options) const override;
diff --git a/lldb/source/Target/Language.cpp b/lldb/source/Target/Language.cpp
index feb0fa9d19814d..5663c0b046b476 100644
--- a/lldb/source/Target/Language.cpp
+++ b/lldb/source/Target/Language.cpp
@@ -128,29 +128,24 @@ Language *Language::FindPlugin(LanguageType language,
 
 Language *
 Language::FindPluginForManglingScheme(Mangled::ManglingScheme scheme) {
-  if (scheme == Mangled::eManglingSchemeNone)
-    return nullptr;
-
-  // Mangled calls this for every name it demangles with a plugin, so avoid
-  // taking the lock in ForEach() each time. Language instances are never
-  // destroyed, so the plugin can be cached once it's found. Misses aren't
-  // cached, since the plugin may not be registered yet.
+  // Mangled calls this for every name it demangles, so avoid the lock in
+  // FindPlugin(). Language instances are never destroyed, and the language of
+  // a scheme never changes, so the plugin can be cached once it's found.
   static std::array<std::atomic<Language *>, Mangled::kNumManglingSchemes>
       g_plugins;
+  if (scheme >= g_plugins.size())
+    return nullptr;
   if (Language *language = g_plugins[scheme].load(std::memory_order_acquire))
     return language;
 
-  Language *result = nullptr;
-  ForEach([&result, scheme](Language *language) {
-    if (language->HandlesManglingScheme(scheme)) {
-      result = language;
-      return IterationAction::Stop;
-    }
-    return IterationAction::Continue;
-  });
-  if (result)
-    g_plugins[scheme].store(result, std::memory_order_release);
-  return result;
+  lldb::LanguageType language_type =
+      Mangled::GetLanguageForManglingScheme(scheme);
+  if (language_type == eLanguageTypeUnknown)
+    return nullptr;
+  Language *language = FindPlugin(language_type);
+  if (language)
+    g_plugins[scheme].store(language, std::memory_order_release);
+  return language;
 }
 
 void Language::ForEach(
diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp
index 3d6d236c476749..0e25413573bb8d 100644
--- a/lldb/unittests/Core/MangledTest.cpp
+++ b/lldb/unittests/Core/MangledTest.cpp
@@ -117,6 +117,46 @@ TEST(MangledTest, SameForInvalidDLangPrefixedName) {
   EXPECT_STREQ("_DDD", the_demangled.GetCString());
 }
 
+TEST(MangledTest, RegisterManglingScheme) {
+  Mangled::ManglingScheme scheme = Mangled::RegisterManglingScheme(
+      {"$test1_", "$test2_"}, eLanguageTypeAda83);
+  ASSERT_NE(scheme, Mangled::eManglingSchemeNone);
+  EXPECT_EQ(Mangled::GetManglingScheme("$test1_foo"), scheme);
+  EXPECT_EQ(Mangled::GetManglingScheme("$test2_foo"), scheme);
+  EXPECT_EQ(Mangled::GetManglingScheme("$test3_foo"),
+            Mangled::eManglingSchemeNone);
+  EXPECT_EQ(Mangled::GetLanguageForManglingScheme(scheme), eLanguageTypeAda83);
+  EXPECT_EQ(Mangled("$test1_foo").GetMangledName(), ConstString("$test1_foo"));
+
+  // Registering the scheme again, as a plugin that is initialized again would,
+  // returns the same scheme.
+  EXPECT_EQ(Mangled::RegisterManglingScheme({"$test1_", "$test2_"},
+                                            eLanguageTypeAda83),
+            scheme);
+
+  Mangled::UnregisterManglingScheme(scheme);
+  EXPECT_EQ(Mangled::GetManglingScheme("$test1_foo"),
+            Mangled::eManglingSchemeNone);
+  EXPECT_EQ(Mangled("$test1_foo").GetDemangledName(),
+            ConstString("$test1_foo"));
+}
+
+TEST(MangledTest, RegisterManglingSchemeWithMatcher) {
+  // Only the names that continue with a digit after the prefix are mangled.
+  Mangled::ManglingScheme scheme = Mangled::RegisterManglingScheme(
+      {"$test4_"}, eLanguageTypeAda83, [](llvm::StringRef name) {
+        return name.consume_front("$test4_") && !name.empty() &&
+               llvm::isDigit(name.front());
+      });
+  ASSERT_NE(scheme, Mangled::eManglingSchemeNone);
+  EXPECT_EQ(Mangled::GetManglingScheme("$test4_1foo"), scheme);
+  EXPECT_EQ(Mangled::GetManglingScheme("$test4_foo"),
+            Mangled::eManglingSchemeNone);
+  EXPECT_EQ(Mangled::GetManglingScheme("$test4_"),
+            Mangled::eManglingSchemeNone);
+  Mangled::UnregisterManglingScheme(scheme);
+}
+
 TEST(MangledTest, RecognizeSwiftMangledNames) {
   llvm::StringRef valid_swift_mangled_names[] = {
       "_TtC4main7MyClass",   // Mangled objc class name
diff --git a/lldb/unittests/Symbol/TestTypeSystemClang.cpp b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
index 314f8e2c97e199..a282cef795f1c8 100644
--- a/lldb/unittests/Symbol/TestTypeSystemClang.cpp
+++ b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Plugins/ExpressionParser/Clang/ClangUtil.h"
+#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h"
 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
 #include "TestingSupport/SubsystemRAII.h"
 #include "TestingSupport/Symbol/ClangTestUtils.h"
@@ -1475,7 +1476,7 @@ struct AsmLabelTestCase {
 class TestTypeSystemClangAsmLabel
     : public testing::TestWithParam<AsmLabelTestCase> {
 public:
-  SubsystemRAII<FileSystem, HostInfo> subsystems;
+  SubsystemRAII<FileSystem, HostInfo, CPlusPlusLanguage> subsystems;
 
   void SetUp() override {
     m_holder =

>From d183fcbfbca3e1fd1ec4655c9500e48467ecb4b0 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Fri, 16 Oct 2026 11:11:49 -0700
Subject: [PATCH 4/6] [lldb] Don't repeat function lookups that only differ in
 language

Currently Module::LookupInfo::MakeLookupInfos() creates one lookup for
each language plugin when no language is given, and each lookup
searches every module. Even languages that don't parse function names,
like Fortran and Objective-C++, do this repetetive work.

This patch skips a lookup of full names that only differs from an
earlier one in its language. Only the DWARF index uses the language of
a lookup, and only to match names that aren't full names, so the
skipped lookup would find the same functions again.

Assisted-by: Claude
---
 lldb/include/lldb/Core/Module.h    |  5 +--
 lldb/source/Core/Module.cpp        | 12 +++++++
 lldb/unittests/Core/ModuleTest.cpp | 51 ++++++++++++++++++++++++++++++
 3 files changed, 66 insertions(+), 2 deletions(-)

diff --git a/lldb/include/lldb/Core/Module.h b/lldb/include/lldb/Core/Module.h
index e71a29e3e413b9..b4586e9a420e2d 100644
--- a/lldb/include/lldb/Core/Module.h
+++ b/lldb/include/lldb/Core/Module.h
@@ -954,8 +954,9 @@ class Module : public std::enable_shared_from_this<Module>,
     /// \param[in] lang_type
     ///     The language to create lookups for. If eLanguageTypeUnknown is
     ///     passed, creates one LookupInfo for each language plugin currently
-    ///     available in LLDB. If a specific language is provided, creates only
-    ///     a single LookupInfo for that language.
+    ///     available in LLDB, except for full-name lookups that would repeat
+    ///     the lookup of another language. If a specific language is provided,
+    ///     creates only a single LookupInfo for that language.
     ///
     /// \param[in] lookup_name_override
     ///     Manually override the name used for lookup. This parameter is
diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp
index 7eab74e57766e9..38e93833f209d0 100644
--- a/lldb/source/Core/Module.cpp
+++ b/lldb/source/Core/Module.cpp
@@ -705,6 +705,18 @@ std::vector<Module::LookupInfo> Module::LookupInfo::MakeLookupInfos(
   for (LanguageType lang_type : lang_types) {
     Module::LookupInfo info(name, lookup_name_override, name_type_mask,
                             lang_type);
+    // The language of a lookup only matters for matching names that aren't
+    // full names, so a lookup of full names that only differs from an earlier
+    // one in its language would find the same functions again.
+    if (info.m_name_type_mask & eFunctionNameTypeFull &&
+        llvm::any_of(infos, [&info](const LookupInfo &other) {
+          return other.m_name == info.m_name &&
+                 other.m_lookup_name == info.m_lookup_name &&
+                 other.m_name_type_mask == info.m_name_type_mask &&
+                 other.m_match_name_after_lookup ==
+                     info.m_match_name_after_lookup;
+        }))
+      continue;
     infos.push_back(info);
   }
   return infos;
diff --git a/lldb/unittests/Core/ModuleTest.cpp b/lldb/unittests/Core/ModuleTest.cpp
index e720d9897828e6..58c6fd11eb8a10 100644
--- a/lldb/unittests/Core/ModuleTest.cpp
+++ b/lldb/unittests/Core/ModuleTest.cpp
@@ -159,6 +159,57 @@ TEST(ModuleTest, MakeLookupInfosHonorsLookupNameOverride) {
   }
 }
 
+namespace {
+/// A language that, like most, doesn't parse function names.
+template <LanguageType Type> class MockLanguage : public Language {
+public:
+  LanguageType GetLanguageType() const override { return Type; }
+  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+  bool IsSourceFile(llvm::StringRef file_path) const override { return false; }
+
+  static llvm::StringRef GetPluginNameStatic() {
+    return Language::GetNameForLanguageType(Type);
+  }
+  static void Initialize() {
+    PluginManager::RegisterPlugin(GetPluginNameStatic(), "Mock Language",
+                                  CreateInstance);
+  }
+  static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); }
+  static Language *CreateInstance(LanguageType language) {
+    return language == Type ? new MockLanguage() : nullptr;
+  }
+};
+} // namespace
+
+// Languages that don't parse function names all look up the full name the
+// same way, so a lookup in all languages only needs one of them.
+TEST(ModuleTest, MakeLookupInfosSkipsRepeatedFullNameLookups) {
+  SubsystemRAII<FileSystem, HostInfo, MockLanguage<eLanguageTypePascal83>,
+                MockLanguage<eLanguageTypeModula2>>
+      subsystems;
+  // Language::ForEach() only finds the plugins that were registered when it
+  // first ran, or that were looked up since.
+  ASSERT_NE(Language::FindPlugin(eLanguageTypePascal83), nullptr);
+  ASSERT_NE(Language::FindPlugin(eLanguageTypeModula2), nullptr);
+
+  auto count_mock_lookups = [](const std::vector<Module::LookupInfo> &infos) {
+    return llvm::count_if(infos, [](const Module::LookupInfo &info) {
+      return info.GetLanguageType() == eLanguageTypePascal83 ||
+             info.GetLanguageType() == eLanguageTypeModula2;
+    });
+  };
+
+  ConstString name("foo");
+  EXPECT_LE(count_mock_lookups(Module::LookupInfo::MakeLookupInfos(
+                name, eFunctionNameTypeAuto, eLanguageTypeUnknown)),
+            1);
+
+  // Lookups that don't match full names use the language to match them.
+  EXPECT_EQ(count_mock_lookups(Module::LookupInfo::MakeLookupInfos(
+                name, eFunctionNameTypeBase, eLanguageTypeUnknown)),
+            2);
+}
+
 TEST(ModuleTest, ResolveSymbolContextForAddressExactMatch) {
   // Test that ResolveSymbolContextForAddress prefers exact symbol matches
   // over symbols that merely contain the address.

>From 8c93066eaa9f225d7ed6de8932243db49035f7a6 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Thu, 15 Oct 2026 14:02:59 -0700
Subject: [PATCH 5/6] [lldb] Add a Rust language plugin

Currently Mangled demangles Rust v0 names itself, since there is no
Rust language plugin to hand them to.

This patch adds a minimal one, like the Fortran plugin, that
recognizes Rust source files, registers the Rust v0 mangling scheme,
and demangles its names with the code that moves out of Mangled.

Assisted-by: Claude
---
 lldb/include/lldb/Core/Mangled.h              |  1 -
 lldb/source/Core/Mangled.cpp                  | 20 ------
 lldb/source/Plugins/Language/CMakeLists.txt   |  1 +
 .../Plugins/Language/Rust/CMakeLists.txt      | 11 +++
 .../Plugins/Language/Rust/RustLanguage.cpp    | 70 +++++++++++++++++++
 .../Plugins/Language/Rust/RustLanguage.h      | 44 ++++++++++++
 lldb/unittests/Core/CMakeLists.txt            |  1 +
 lldb/unittests/Core/MangledTest.cpp           |  3 +
 lldb/unittests/Language/CMakeLists.txt        |  1 +
 lldb/unittests/Language/Rust/CMakeLists.txt   |  6 ++
 .../Language/Rust/RustLanguageTest.cpp        | 35 ++++++++++
 11 files changed, 172 insertions(+), 21 deletions(-)
 create mode 100644 lldb/source/Plugins/Language/Rust/CMakeLists.txt
 create mode 100644 lldb/source/Plugins/Language/Rust/RustLanguage.cpp
 create mode 100644 lldb/source/Plugins/Language/Rust/RustLanguage.h
 create mode 100644 lldb/unittests/Language/Rust/CMakeLists.txt
 create mode 100644 lldb/unittests/Language/Rust/RustLanguageTest.cpp

diff --git a/lldb/include/lldb/Core/Mangled.h b/lldb/include/lldb/Core/Mangled.h
index 9b32040b70ec0d..f4dfdaeb4dd54b 100644
--- a/lldb/include/lldb/Core/Mangled.h
+++ b/lldb/include/lldb/Core/Mangled.h
@@ -45,7 +45,6 @@ class Mangled {
   /// register, which have no enumerators, are valid values too.
   enum ManglingScheme : uint8_t {
     eManglingSchemeNone = 0,
-    eManglingSchemeRustV0,
     eManglingSchemeD,
     eManglingSchemeSwift,
     /// The first of the schemes that language plugins register with
diff --git a/lldb/source/Core/Mangled.cpp b/lldb/source/Core/Mangled.cpp
index 307b12e671c1e6..f3cacc9c36cd02 100644
--- a/lldb/source/Core/Mangled.cpp
+++ b/lldb/source/Core/Mangled.cpp
@@ -194,9 +194,6 @@ Mangled::ManglingScheme Mangled::GetManglingScheme(llvm::StringRef name) {
   if (name.empty())
     return Mangled::eManglingSchemeNone;
 
-  if (name.starts_with("_R"))
-    return Mangled::eManglingSchemeRustV0;
-
   if (name.starts_with("_D")) {
     // A dlang mangled name begins with `_D`, followed by a numeric length. One
     // known exception is the symbol `_Dmain`.
@@ -276,20 +273,6 @@ void Mangled::SetValue(ConstString name) {
 }
 
 // Local helpers for different demangling implementations.
-static char *GetRustV0DemangledStr(llvm::StringRef M) {
-  char *demangled_cstr = llvm::rustDemangle(M);
-
-  if (Log *log = GetLog(LLDBLog::Demangle)) {
-    if (demangled_cstr && demangled_cstr[0])
-      LLDB_LOG(log, "demangled rustv0: {0} -> \"{1}\"", M, demangled_cstr);
-    else
-      LLDB_LOG(log, "demangled rustv0: {0} -> error: failed to demangle",
-               static_cast<std::string_view>(M));
-  }
-
-  return demangled_cstr;
-}
-
 static char *GetDLangDemangledStr(llvm::StringRef M) {
   char *demangled_cstr = llvm::dlangDemangle(M);
 
@@ -324,9 +307,6 @@ ConstString Mangled::GetDemangledName() const {
   ManglingScheme scheme = GetManglingScheme(m_mangled.GetStringRef());
   char *demangled_name = nullptr;
   switch (scheme) {
-  case eManglingSchemeRustV0:
-    demangled_name = GetRustV0DemangledStr(m_mangled);
-    break;
   case eManglingSchemeD:
     demangled_name = GetDLangDemangledStr(m_mangled);
     break;
diff --git a/lldb/source/Plugins/Language/CMakeLists.txt b/lldb/source/Plugins/Language/CMakeLists.txt
index 5377734af2f8dd..9b1c19384a1cd3 100644
--- a/lldb/source/Plugins/Language/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CMakeLists.txt
@@ -8,3 +8,4 @@ add_subdirectory(CPlusPlus)
 add_subdirectory(ObjC)
 add_subdirectory(ObjCPlusPlus)
 add_subdirectory(Fortran)
+add_subdirectory(Rust)
diff --git a/lldb/source/Plugins/Language/Rust/CMakeLists.txt b/lldb/source/Plugins/Language/Rust/CMakeLists.txt
new file mode 100644
index 00000000000000..9c09f8e50b4e0e
--- /dev/null
+++ b/lldb/source/Plugins/Language/Rust/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_lldb_library(lldbPluginRustLanguage PLUGIN
+  RustLanguage.cpp
+
+  LINK_COMPONENTS
+    Demangle
+    Support
+  LINK_LIBS
+    lldbCore
+    lldbTarget
+    lldbUtility
+)
diff --git a/lldb/source/Plugins/Language/Rust/RustLanguage.cpp b/lldb/source/Plugins/Language/Rust/RustLanguage.cpp
new file mode 100644
index 00000000000000..9e6c76e5c6b374
--- /dev/null
+++ b/lldb/source/Plugins/Language/Rust/RustLanguage.cpp
@@ -0,0 +1,70 @@
+//===-- RustLanguage.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 "RustLanguage.h"
+
+#include "lldb/Core/Mangled.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+
+#include "llvm/Demangle/Demangle.h"
+
+#include <cstdlib>
+
+using namespace lldb;
+using namespace lldb_private;
+
+LLDB_PLUGIN_DEFINE(RustLanguage)
+
+/// The mangling scheme that this plugin registered.
+static Mangled::ManglingScheme g_rust_v0_scheme = Mangled::eManglingSchemeNone;
+
+void RustLanguage::Initialize() {
+  PluginManager::RegisterPlugin(GetPluginNameStatic(), "Rust Language",
+                                CreateInstance);
+  g_rust_v0_scheme = Mangled::RegisterManglingScheme({"_R"}, eLanguageTypeRust);
+}
+
+void RustLanguage::Terminate() {
+  Mangled::UnregisterManglingScheme(g_rust_v0_scheme);
+  PluginManager::UnregisterPlugin(CreateInstance);
+}
+
+// Static Functions
+Language *RustLanguage::CreateInstance(lldb::LanguageType language) {
+  if (language == eLanguageTypeRust)
+    return new RustLanguage();
+  return nullptr;
+}
+
+bool RustLanguage::IsSourceFile(llvm::StringRef file_path) const {
+  return file_path.ends_with_insensitive(".rs");
+}
+
+Language::DemangledName
+RustLanguage::DemangleSymbol(ConstString mangled,
+                             Mangled::ManglingScheme scheme,
+                             const DemangleOptions &options) const {
+  llvm::StringRef M = mangled.GetStringRef();
+  char *demangled_cstr = llvm::rustDemangle(M);
+
+  if (Log *log = GetLog(LLDBLog::Demangle)) {
+    if (demangled_cstr && demangled_cstr[0])
+      LLDB_LOG(log, "demangled rustv0: {0} -> \"{1}\"", M, demangled_cstr);
+    else
+      LLDB_LOG(log, "demangled rustv0: {0} -> error: failed to demangle", M);
+  }
+
+  DemangledName demangled;
+  if (demangled_cstr) {
+    demangled.name = demangled_cstr;
+    std::free(demangled_cstr);
+  }
+  return demangled;
+}
diff --git a/lldb/source/Plugins/Language/Rust/RustLanguage.h b/lldb/source/Plugins/Language/Rust/RustLanguage.h
new file mode 100644
index 00000000000000..b0a7e00580bcc5
--- /dev/null
+++ b/lldb/source/Plugins/Language/Rust/RustLanguage.h
@@ -0,0 +1,44 @@
+//===-- RustLanguage.h ------------------------------------------*- C++ -*-===//
+//
+// 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_SOURCE_PLUGINS_LANGUAGE_RUST_RUSTLANGUAGE_H
+#define LLDB_SOURCE_PLUGINS_LANGUAGE_RUST_RUSTLANGUAGE_H
+
+#include "lldb/Target/Language.h"
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+
+class RustLanguage : public Language {
+public:
+  lldb::LanguageType GetLanguageType() const override {
+    return lldb::eLanguageTypeRust;
+  }
+
+  bool IsSourceFile(llvm::StringRef file_path) const override;
+
+  DemangledName DemangleSymbol(ConstString mangled,
+                               Mangled::ManglingScheme scheme,
+                               const DemangleOptions &options) const override;
+
+  // Static Functions
+  static void Initialize();
+
+  static void Terminate();
+
+  static Language *CreateInstance(lldb::LanguageType language);
+
+  static llvm::StringRef GetPluginNameStatic() { return "rust"; }
+
+  // PluginInterface protocol
+  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_LANGUAGE_RUST_RUSTLANGUAGE_H
diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt
index 041a37432288ad..242556ffe47b4b 100644
--- a/lldb/unittests/Core/CMakeLists.txt
+++ b/lldb/unittests/Core/CMakeLists.txt
@@ -32,6 +32,7 @@ add_lldb_unittest(LLDBCoreTests
     lldbPluginObjectFileMachO
     lldbPluginObjectFilePECOFF
     lldbPluginPlatformMacOSX
+    lldbPluginRustLanguage
     lldbPluginSymbolFileSymtab
     lldbSymbol
     lldbUtilityHelpers
diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp
index 0e25413573bb8d..2a266e3f0121c7 100644
--- a/lldb/unittests/Core/MangledTest.cpp
+++ b/lldb/unittests/Core/MangledTest.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h"
+#include "Plugins/Language/Rust/RustLanguage.h"
 #include "Plugins/ObjectFile/ELF/ObjectFileELF.h"
 #include "Plugins/SymbolFile/Symtab/SymbolFileSymtab.h"
 #include "TestingSupport/SubsystemRAII.h"
@@ -84,6 +85,7 @@ TEST(MangledTest, EmptyForInvalidName) {
 }
 
 TEST(MangledTest, ResultForValidRustV0Name) {
+  SubsystemRAII<RustLanguage> subsystems;
   ConstString mangled_name("_RNvC1a4main");
   Mangled the_mangled(mangled_name);
   ConstString the_demangled = the_mangled.GetDemangledName();
@@ -93,6 +95,7 @@ TEST(MangledTest, ResultForValidRustV0Name) {
 }
 
 TEST(MangledTest, EmptyForInvalidRustV0Name) {
+  SubsystemRAII<RustLanguage> subsystems;
   ConstString mangled_name("_RRR");
   Mangled the_mangled(mangled_name);
   ConstString the_demangled = the_mangled.GetDemangledName();
diff --git a/lldb/unittests/Language/CMakeLists.txt b/lldb/unittests/Language/CMakeLists.txt
index 26710ff8896e20..a9a6da73fac19a 100644
--- a/lldb/unittests/Language/CMakeLists.txt
+++ b/lldb/unittests/Language/CMakeLists.txt
@@ -2,3 +2,4 @@ add_subdirectory(CPlusPlus)
 add_subdirectory(CLanguages)
 add_subdirectory(ObjC)
 add_subdirectory(Fortran)
+add_subdirectory(Rust)
diff --git a/lldb/unittests/Language/Rust/CMakeLists.txt b/lldb/unittests/Language/Rust/CMakeLists.txt
new file mode 100644
index 00000000000000..b8ef82f078b606
--- /dev/null
+++ b/lldb/unittests/Language/Rust/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_lldb_unittest(LanguageRustLanguageTests
+  RustLanguageTest.cpp
+
+  LINK_LIBS
+    lldbPluginRustLanguage
+)
diff --git a/lldb/unittests/Language/Rust/RustLanguageTest.cpp b/lldb/unittests/Language/Rust/RustLanguageTest.cpp
new file mode 100644
index 00000000000000..4783a30ab691cf
--- /dev/null
+++ b/lldb/unittests/Language/Rust/RustLanguageTest.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "Plugins/Language/Rust/RustLanguage.h"
+#include "TestingSupport/SubsystemRAII.h"
+#include "lldb/Core/Mangled.h"
+#include "lldb/lldb-enumerations.h"
+
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+
+TEST(RustLanguage, LookupRustLanguageByLanguageType) {
+  SubsystemRAII<RustLanguage> langs;
+
+  Language *lang = Language::FindPlugin(lldb::eLanguageTypeRust);
+  ASSERT_NE(lang, nullptr);
+  EXPECT_EQ(lang->GetPluginName(), "rust");
+  EXPECT_TRUE(lang->IsSourceFile("main.rs"));
+  EXPECT_FALSE(lang->IsSourceFile("main.cpp"));
+}
+
+TEST(RustLanguage, RegistersRustV0ManglingScheme) {
+  SubsystemRAII<RustLanguage> langs;
+
+  EXPECT_EQ(Mangled::GetLanguageForManglingScheme(
+                Mangled::GetManglingScheme("_RNvC1a4main")),
+            lldb::eLanguageTypeRust);
+  EXPECT_EQ(Mangled("_RNvC1a4main").GetDemangledName(), ConstString("a::main"));
+}

>From 1ad45e93122072b46f78d6f042b5a19b2b57f314 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Thu, 15 Oct 2026 14:06:33 -0700
Subject: [PATCH 6/6] [lldb] Add a D language plugin

Currently Mangled demangles D names itself, since there is no D
language plugin to hand them to.

This patch adds a minimal one, like the Rust plugin, that recognizes D
source files, registers the D mangling scheme, and demangles its names
with the code that moves out of Mangled, which leaves Mangled without
a demangler of its own.

Assisted-by: Claude
---
 lldb/include/lldb/Core/Mangled.h              |  1 -
 lldb/source/Core/Mangled.cpp                  | 41 ----------
 lldb/source/Plugins/Language/CMakeLists.txt   |  1 +
 lldb/source/Plugins/Language/D/CMakeLists.txt | 11 +++
 lldb/source/Plugins/Language/D/DLanguage.cpp  | 81 +++++++++++++++++++
 lldb/source/Plugins/Language/D/DLanguage.h    | 44 ++++++++++
 lldb/unittests/Core/CMakeLists.txt            |  1 +
 lldb/unittests/Core/MangledTest.cpp           |  2 +
 lldb/unittests/Language/CMakeLists.txt        |  1 +
 lldb/unittests/Language/D/CMakeLists.txt      |  6 ++
 lldb/unittests/Language/D/DLanguageTest.cpp   | 43 ++++++++++
 11 files changed, 190 insertions(+), 42 deletions(-)
 create mode 100644 lldb/source/Plugins/Language/D/CMakeLists.txt
 create mode 100644 lldb/source/Plugins/Language/D/DLanguage.cpp
 create mode 100644 lldb/source/Plugins/Language/D/DLanguage.h
 create mode 100644 lldb/unittests/Language/D/CMakeLists.txt
 create mode 100644 lldb/unittests/Language/D/DLanguageTest.cpp

diff --git a/lldb/include/lldb/Core/Mangled.h b/lldb/include/lldb/Core/Mangled.h
index f4dfdaeb4dd54b..b29dd1930d77a5 100644
--- a/lldb/include/lldb/Core/Mangled.h
+++ b/lldb/include/lldb/Core/Mangled.h
@@ -45,7 +45,6 @@ class Mangled {
   /// register, which have no enumerators, are valid values too.
   enum ManglingScheme : uint8_t {
     eManglingSchemeNone = 0,
-    eManglingSchemeD,
     eManglingSchemeSwift,
     /// The first of the schemes that language plugins register with
     /// RegisterManglingScheme().
diff --git a/lldb/source/Core/Mangled.cpp b/lldb/source/Core/Mangled.cpp
index f3cacc9c36cd02..b5d3e7bb775033 100644
--- a/lldb/source/Core/Mangled.cpp
+++ b/lldb/source/Core/Mangled.cpp
@@ -13,16 +13,12 @@
 #include "lldb/Target/Language.h"
 #include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/DataEncoder.h"
-#include "lldb/Utility/LLDBLog.h"
-#include "lldb/Utility/Log.h"
 #include "lldb/Utility/RegularExpression.h"
 #include "lldb/Utility/Stream.h"
 #include "lldb/lldb-enumerations.h"
 
 #include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
-#include "llvm/Demangle/Demangle.h"
 #include "llvm/Support/Compiler.h"
 
 #include <array>
@@ -30,12 +26,9 @@
 #include <mutex>
 #include <optional>
 #include <string>
-#include <string_view>
 #include <utility>
 #include <vector>
 
-#include <cstdlib>
-#include <cstring>
 using namespace lldb_private;
 
 #pragma mark Mangled
@@ -194,16 +187,6 @@ Mangled::ManglingScheme Mangled::GetManglingScheme(llvm::StringRef name) {
   if (name.empty())
     return Mangled::eManglingSchemeNone;
 
-  if (name.starts_with("_D")) {
-    // A dlang mangled name begins with `_D`, followed by a numeric length. One
-    // known exception is the symbol `_Dmain`.
-    // See `SymbolName` and `LName` in
-    // https://dlang.org/spec/abi.html#name_mangling
-    llvm::StringRef buf = name.drop_front(2);
-    if (!buf.empty() && (llvm::isDigit(buf.front()) || name == "_Dmain"))
-      return Mangled::eManglingSchemeD;
-  }
-
   // Swift's older style of mangling used "_T" as a mangling prefix. This can
   // lead to false positives with other symbols that just so happen to start
   // with "_T". To minimize the chance of that happening, we only return true
@@ -272,21 +255,6 @@ void Mangled::SetValue(ConstString name) {
   }
 }
 
-// Local helpers for different demangling implementations.
-static char *GetDLangDemangledStr(llvm::StringRef M) {
-  char *demangled_cstr = llvm::dlangDemangle(M);
-
-  if (Log *log = GetLog(LLDBLog::Demangle)) {
-    if (demangled_cstr && demangled_cstr[0])
-      LLDB_LOG(log, "demangled dlang: {0} -> \"{1}\"", M, demangled_cstr);
-    else
-      LLDB_LOG(log, "demangled dlang: {0} -> error: failed to demangle",
-               static_cast<std::string_view>(M));
-  }
-
-  return demangled_cstr;
-}
-
 // 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
@@ -305,11 +273,7 @@ ConstString Mangled::GetDemangledName() const {
   // We didn't already mangle this name, demangle it and if all goes well
   // add it to our map.
   ManglingScheme scheme = GetManglingScheme(m_mangled.GetStringRef());
-  char *demangled_name = nullptr;
   switch (scheme) {
-  case eManglingSchemeD:
-    demangled_name = GetDLangDemangledStr(m_mangled);
-    break;
   case eManglingSchemeSwift:
     // Demangling a swift name requires the swift compiler. This is
     // explicitly unsupported on llvm.org.
@@ -328,11 +292,6 @@ ConstString Mangled::GetDemangledName() const {
     break;
   }
 
-  if (demangled_name) {
-    m_demangled.SetStringWithMangledCounterpart(demangled_name, m_mangled);
-    free(demangled_name);
-  }
-
   if (m_demangled.IsNull()) {
     // Set the demangled string to the empty string to indicate we tried to
     // parse it once and failed.
diff --git a/lldb/source/Plugins/Language/CMakeLists.txt b/lldb/source/Plugins/Language/CMakeLists.txt
index 9b1c19384a1cd3..d14564b4dbe6a2 100644
--- a/lldb/source/Plugins/Language/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CMakeLists.txt
@@ -9,3 +9,4 @@ add_subdirectory(ObjC)
 add_subdirectory(ObjCPlusPlus)
 add_subdirectory(Fortran)
 add_subdirectory(Rust)
+add_subdirectory(D)
diff --git a/lldb/source/Plugins/Language/D/CMakeLists.txt b/lldb/source/Plugins/Language/D/CMakeLists.txt
new file mode 100644
index 00000000000000..a4561e09f8de28
--- /dev/null
+++ b/lldb/source/Plugins/Language/D/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_lldb_library(lldbPluginDLanguage PLUGIN
+  DLanguage.cpp
+
+  LINK_COMPONENTS
+    Demangle
+    Support
+  LINK_LIBS
+    lldbCore
+    lldbTarget
+    lldbUtility
+)
diff --git a/lldb/source/Plugins/Language/D/DLanguage.cpp b/lldb/source/Plugins/Language/D/DLanguage.cpp
new file mode 100644
index 00000000000000..1f149fa6b19fe0
--- /dev/null
+++ b/lldb/source/Plugins/Language/D/DLanguage.cpp
@@ -0,0 +1,81 @@
+//===-- DLanguage.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 "DLanguage.h"
+
+#include "lldb/Core/Mangled.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Demangle/Demangle.h"
+
+#include <cstdlib>
+
+using namespace lldb;
+using namespace lldb_private;
+
+LLDB_PLUGIN_DEFINE(DLanguage)
+
+/// The mangling scheme that this plugin registered.
+static Mangled::ManglingScheme g_d_scheme = Mangled::eManglingSchemeNone;
+
+/// Returns true if \p name, which starts with "_D", is a D mangled name.
+static bool IsDMangledName(llvm::StringRef name) {
+  // A D mangled name begins with `_D`, followed by a numeric length. One known
+  // exception is the symbol `_Dmain`. See `SymbolName` and `LName` in
+  // https://dlang.org/spec/abi.html#name_mangling
+  llvm::StringRef rest = name.drop_front(2);
+  return (!rest.empty() && llvm::isDigit(rest.front())) || name == "_Dmain";
+}
+
+void DLanguage::Initialize() {
+  PluginManager::RegisterPlugin(GetPluginNameStatic(), "D Language",
+                                CreateInstance);
+  g_d_scheme =
+      Mangled::RegisterManglingScheme({"_D"}, eLanguageTypeD, IsDMangledName);
+}
+
+void DLanguage::Terminate() {
+  Mangled::UnregisterManglingScheme(g_d_scheme);
+  PluginManager::UnregisterPlugin(CreateInstance);
+}
+
+// Static Functions
+Language *DLanguage::CreateInstance(lldb::LanguageType language) {
+  if (language == eLanguageTypeD)
+    return new DLanguage();
+  return nullptr;
+}
+
+bool DLanguage::IsSourceFile(llvm::StringRef file_path) const {
+  return file_path.ends_with_insensitive(".d") ||
+         file_path.ends_with_insensitive(".di");
+}
+
+Language::DemangledName
+DLanguage::DemangleSymbol(ConstString mangled, Mangled::ManglingScheme scheme,
+                          const DemangleOptions &options) const {
+  llvm::StringRef M = mangled.GetStringRef();
+  char *demangled_cstr = llvm::dlangDemangle(M);
+
+  if (Log *log = GetLog(LLDBLog::Demangle)) {
+    if (demangled_cstr && demangled_cstr[0])
+      LLDB_LOG(log, "demangled dlang: {0} -> \"{1}\"", M, demangled_cstr);
+    else
+      LLDB_LOG(log, "demangled dlang: {0} -> error: failed to demangle", M);
+  }
+
+  DemangledName demangled;
+  if (demangled_cstr) {
+    demangled.name = demangled_cstr;
+    std::free(demangled_cstr);
+  }
+  return demangled;
+}
diff --git a/lldb/source/Plugins/Language/D/DLanguage.h b/lldb/source/Plugins/Language/D/DLanguage.h
new file mode 100644
index 00000000000000..ca38ac74a12a0c
--- /dev/null
+++ b/lldb/source/Plugins/Language/D/DLanguage.h
@@ -0,0 +1,44 @@
+//===-- DLanguage.h ---------------------------------------------*- C++ -*-===//
+//
+// 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_SOURCE_PLUGINS_LANGUAGE_D_DLANGUAGE_H
+#define LLDB_SOURCE_PLUGINS_LANGUAGE_D_DLANGUAGE_H
+
+#include "lldb/Target/Language.h"
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+
+class DLanguage : public Language {
+public:
+  lldb::LanguageType GetLanguageType() const override {
+    return lldb::eLanguageTypeD;
+  }
+
+  bool IsSourceFile(llvm::StringRef file_path) const override;
+
+  DemangledName DemangleSymbol(ConstString mangled,
+                               Mangled::ManglingScheme scheme,
+                               const DemangleOptions &options) const override;
+
+  // Static Functions
+  static void Initialize();
+
+  static void Terminate();
+
+  static Language *CreateInstance(lldb::LanguageType language);
+
+  static llvm::StringRef GetPluginNameStatic() { return "d"; }
+
+  // PluginInterface protocol
+  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_LANGUAGE_D_DLANGUAGE_H
diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt
index 242556ffe47b4b..0cec0d7f1baea3 100644
--- a/lldb/unittests/Core/CMakeLists.txt
+++ b/lldb/unittests/Core/CMakeLists.txt
@@ -28,6 +28,7 @@ add_lldb_unittest(LLDBCoreTests
     lldbCore
     lldbHost
     lldbPluginCPlusPlusLanguage
+    lldbPluginDLanguage
     lldbPluginObjectFileELF
     lldbPluginObjectFileMachO
     lldbPluginObjectFilePECOFF
diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp
index 2a266e3f0121c7..62a972d70d0eee 100644
--- a/lldb/unittests/Core/MangledTest.cpp
+++ b/lldb/unittests/Core/MangledTest.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h"
+#include "Plugins/Language/D/DLanguage.h"
 #include "Plugins/Language/Rust/RustLanguage.h"
 #include "Plugins/ObjectFile/ELF/ObjectFileELF.h"
 #include "Plugins/SymbolFile/Symtab/SymbolFileSymtab.h"
@@ -104,6 +105,7 @@ TEST(MangledTest, EmptyForInvalidRustV0Name) {
 }
 
 TEST(MangledTest, ResultForValidDLangName) {
+  SubsystemRAII<DLanguage> subsystems;
   ConstString mangled_name("_Dmain");
   Mangled the_mangled(mangled_name);
   ConstString the_demangled = the_mangled.GetDemangledName();
diff --git a/lldb/unittests/Language/CMakeLists.txt b/lldb/unittests/Language/CMakeLists.txt
index a9a6da73fac19a..cd2b20552d84a8 100644
--- a/lldb/unittests/Language/CMakeLists.txt
+++ b/lldb/unittests/Language/CMakeLists.txt
@@ -3,3 +3,4 @@ add_subdirectory(CLanguages)
 add_subdirectory(ObjC)
 add_subdirectory(Fortran)
 add_subdirectory(Rust)
+add_subdirectory(D)
diff --git a/lldb/unittests/Language/D/CMakeLists.txt b/lldb/unittests/Language/D/CMakeLists.txt
new file mode 100644
index 00000000000000..a70d94d19d51fb
--- /dev/null
+++ b/lldb/unittests/Language/D/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_lldb_unittest(LanguageDLanguageTests
+  DLanguageTest.cpp
+
+  LINK_LIBS
+    lldbPluginDLanguage
+)
diff --git a/lldb/unittests/Language/D/DLanguageTest.cpp b/lldb/unittests/Language/D/DLanguageTest.cpp
new file mode 100644
index 00000000000000..ec0fc5a7364784
--- /dev/null
+++ b/lldb/unittests/Language/D/DLanguageTest.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "Plugins/Language/D/DLanguage.h"
+#include "TestingSupport/SubsystemRAII.h"
+#include "lldb/Core/Mangled.h"
+#include "lldb/lldb-enumerations.h"
+
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+
+TEST(DLanguage, LookupDLanguageByLanguageType) {
+  SubsystemRAII<DLanguage> langs;
+
+  Language *lang = Language::FindPlugin(lldb::eLanguageTypeD);
+  ASSERT_NE(lang, nullptr);
+  EXPECT_EQ(lang->GetPluginName(), "d");
+  EXPECT_TRUE(lang->IsSourceFile("main.d"));
+  EXPECT_TRUE(lang->IsSourceFile("module.di"));
+  EXPECT_FALSE(lang->IsSourceFile("main.cpp"));
+}
+
+TEST(DLanguage, RegistersDManglingScheme) {
+  SubsystemRAII<DLanguage> langs;
+
+  EXPECT_EQ(Mangled::GetLanguageForManglingScheme(
+                Mangled::GetManglingScheme("_D3foo3barFZv")),
+            lldb::eLanguageTypeD);
+  EXPECT_EQ(Mangled::GetLanguageForManglingScheme(
+                Mangled::GetManglingScheme("_Dmain")),
+            lldb::eLanguageTypeD);
+  EXPECT_EQ(Mangled::GetManglingScheme("_DDD"), Mangled::eManglingSchemeNone);
+  EXPECT_EQ(Mangled::GetManglingScheme("_Dmainx"),
+            Mangled::eManglingSchemeNone);
+  EXPECT_EQ(Mangled::GetManglingScheme("_D"), Mangled::eManglingSchemeNone);
+  EXPECT_EQ(Mangled("_Dmain").GetDemangledName(), ConstString("D main"));
+}



More information about the lldb-commits mailing list