[llvm-branch-commits] [llvm] release/23.x: [Windows] Export Registry<T> members and accessors for plugins again (#221296) (PR #222643)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Sep 10 06:04:05 PDT 2026
https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/222643
Backport bc6c76a4d4f8b4f6d5e9b663736703f91fcbb6ed
Requested by: @rvandermeulen
>From 66ca4639de74312660f74f1a6ee64cf0c9b4589d Mon Sep 17 00:00:00 2001
From: Ryan VanderMeulen <ryanvm at gmail.com>
Date: Thu, 10 Sep 2026 03:14:23 -0400
Subject: [PATCH] [Windows] Export Registry<T> members and accessors for
plugins again (#221296)
Since #185141 reworked `llvm::Registry`, the registered-node list lives
behind the explicitly specialized
`llvm::detail::getRegistryLinkListInstance<R>()` accessor defined by
`LLVM_DEFINE_REGISTRY`, and the `Head`/`Tail` static data members that
used to be referenced across objects are gone (#163391 had special-cased
those).
`extract_symbols.py` handles neither. The accessor is a `??$` function
template specialization, which the script drops on the assumption that
it can be instantiated in the importing translation unit; here it
cannot, it is defined once by `LLVM_DEFINE_REGISTRY`. And `Registry<T>`
members are only exported when some object references the `Registry`
template, which nothing does anymore because every use goes through the
accessor. On a static clang.exe built with
`LLVM_EXPORT_SYMBOLS_FOR_PLUGINS`, the export table went from 11
`Registry<clang::PluginASTAction>` symbols on 22.1.8 to zero on 23.1.0,
and linking a plugin fails:
```
lld-link: error: undefined symbol: __declspec(dllimport) public: static void __cdecl llvm::Registry<class clang::PluginASTAction>::add_node(class llvm::Registry<class clang::PluginASTAction>::node *)
>>> referenced by host_Registration.obj:(_GLOBAL__sub_I_Registration.cpp)
```
Keep the accessor specializations, and treat a reference to an accessor
as a reference to the `Registry` template so its explicitly instantiated
members are exported as they were before. Verified on the 23.1.0 static
libraries: `Registry<PluginASTAction>::add_node`, the node/iterator
members and the accessor come back for every registry (PluginASTAction,
PragmaHandler, GCStrategy, GCMetadataPrinter, ...), 141 symbols in total
in the Itanium run over the clang libraries. The `Head`/`Tail` rule from
#163391 no longer matches anything and is dropped.
Related: #163367 / #163391 (the previous Registry export fix), #87865.
---
**AI tool use disclosure** (per the [LLVM AI Tool Use
Policy](https://llvm.org/docs/AIToolPolicy.html)): this change was
developed with Claude Code assisting in the analysis and drafting. The
root cause was established against real 23.1.0 binaries and libraries,
and the fix was verified by running extract_symbols.py over the 23.1.0
clang static libraries (the Registry members and accessors come back)
and against MSVC-mangled test objects; Firefox's Windows clang-plugin
and mozsearch builds link and run against a clang.exe built with it.
`llvm/test/tools/extract_symbols/` adds lit coverage for both manglings.
The author reviewed the change and is accountable for and able to answer
questions about it. Commits carry an `Assisted-by:` trailer.
(cherry picked from commit bc6c76a4d4f8b4f6d5e9b663736703f91fcbb6ed)
---
.../extract_symbols/Inputs/lib-itanium.s | 10 +++++++++
.../tools/extract_symbols/Inputs/lib-msvc.s | 12 +++++++++++
.../extract_symbols/Inputs/user-itanium.s | 8 +++++++
.../tools/extract_symbols/Inputs/user-msvc.s | 10 +++++++++
llvm/test/tools/extract_symbols/lit.local.cfg | 21 +++++++++++++++++++
.../extract_symbols/registry-itanium.test | 13 ++++++++++++
.../extract_symbols/registry-microsoft.test | 19 +++++++++++++++++
llvm/utils/extract_symbols.py | 20 +++++++++++-------
8 files changed, 105 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/tools/extract_symbols/Inputs/lib-itanium.s
create mode 100644 llvm/test/tools/extract_symbols/Inputs/lib-msvc.s
create mode 100644 llvm/test/tools/extract_symbols/Inputs/user-itanium.s
create mode 100644 llvm/test/tools/extract_symbols/Inputs/user-msvc.s
create mode 100644 llvm/test/tools/extract_symbols/lit.local.cfg
create mode 100644 llvm/test/tools/extract_symbols/registry-itanium.test
create mode 100644 llvm/test/tools/extract_symbols/registry-microsoft.test
diff --git a/llvm/test/tools/extract_symbols/Inputs/lib-itanium.s b/llvm/test/tools/extract_symbols/Inputs/lib-itanium.s
new file mode 100644
index 0000000000000..bedd90e523de1
--- /dev/null
+++ b/llvm/test/tools/extract_symbols/Inputs/lib-itanium.s
@@ -0,0 +1,10 @@
+ .text
+ .globl _ZN4llvm6detail27getRegistryLinkListInstanceINS_8RegistryIN5clang15PluginASTActionEJEEEEERNS0_23RegistryLinkListStorageIT_EEv
+_ZN4llvm6detail27getRegistryLinkListInstanceINS_8RegistryIN5clang15PluginASTActionEJEEEEERNS0_23RegistryLinkListStorageIT_EEv:
+ retq
+ .globl _ZN4llvm8RegistryIN5clang15PluginASTActionEJEE8add_nodeEPNS3_4nodeE
+_ZN4llvm8RegistryIN5clang15PluginASTActionEJEE8add_nodeEPNS3_4nodeE:
+ retq
+ .globl _ZN4llvm8RegistryIN5clang15PluginASTActionEJEE5beginEv
+_ZN4llvm8RegistryIN5clang15PluginASTActionEJEE5beginEv:
+ retq
diff --git a/llvm/test/tools/extract_symbols/Inputs/lib-msvc.s b/llvm/test/tools/extract_symbols/Inputs/lib-msvc.s
new file mode 100644
index 0000000000000..11fd9fa5525fe
--- /dev/null
+++ b/llvm/test/tools/extract_symbols/Inputs/lib-msvc.s
@@ -0,0 +1,12 @@
+# Explicit specialization defined by LLVM_DEFINE_REGISTRY, plus the explicitly instantiated Registry<T> member
+# that only a plugin references (LLVM_INSTANTIATE_REGISTRY's `template class Registry<...>`).
+ .text
+ .globl "??$getRegistryLinkListInstance at V?$Registry at VPluginASTAction@clang@@$$V at llvm@@@detail at llvm@@YAAEAU?$RegistryLinkListStorage at V?$Registry at VPluginASTAction@clang@@$$V at llvm@@@01 at XZ"
+"??$getRegistryLinkListInstance at V?$Registry at VPluginASTAction@clang@@$$V at llvm@@@detail at llvm@@YAAEAU?$RegistryLinkListStorage at V?$Registry at VPluginASTAction@clang@@$$V at llvm@@@01 at XZ":
+ retq
+ .globl "?add_node@?$Registry at VPluginASTAction@clang@@$$V at llvm@@SAXPEAVnode at 12@@Z"
+"?add_node@?$Registry at VPluginASTAction@clang@@$$V at llvm@@SAXPEAVnode at 12@@Z":
+ retq
+ .globl "?begin@?$Registry at VPluginASTAction@clang@@$$V at llvm@@SA?AViterator at 12@XZ"
+"?begin@?$Registry at VPluginASTAction@clang@@$$V at llvm@@SA?AViterator at 12@XZ":
+ retq
diff --git a/llvm/test/tools/extract_symbols/Inputs/user-itanium.s b/llvm/test/tools/extract_symbols/Inputs/user-itanium.s
new file mode 100644
index 0000000000000..b1cffcdd5e08d
--- /dev/null
+++ b/llvm/test/tools/extract_symbols/Inputs/user-itanium.s
@@ -0,0 +1,8 @@
+ .text
+ .globl _Z10HasPluginsv
+_Z10HasPluginsv:
+ callq _ZN4llvm6detail27getRegistryLinkListInstanceINS_8RegistryIN5clang15PluginASTActionEJEEEEERNS0_23RegistryLinkListStorageIT_EEv
+ retq
+ .weak _ZN4llvm8RegistryIN5clang15PluginASTActionEJEE5beginEv
+_ZN4llvm8RegistryIN5clang15PluginASTActionEJEE5beginEv:
+ retq
diff --git a/llvm/test/tools/extract_symbols/Inputs/user-msvc.s b/llvm/test/tools/extract_symbols/Inputs/user-msvc.s
new file mode 100644
index 0000000000000..e5cdc14f87161
--- /dev/null
+++ b/llvm/test/tools/extract_symbols/Inputs/user-msvc.s
@@ -0,0 +1,10 @@
+# A TU that iterates the registry: references the accessor (declared-only via LLVM_DECLARE_REGISTRY) and
+# carries its own inline copy of begin(), but never touches add_node.
+ .text
+ .globl "?HasPlugins@@YA_NXZ"
+"?HasPlugins@@YA_NXZ":
+ callq "??$getRegistryLinkListInstance at V?$Registry at VPluginASTAction@clang@@$$V at llvm@@@detail at llvm@@YAAEAU?$RegistryLinkListStorage at V?$Registry at VPluginASTAction@clang@@$$V at llvm@@@01 at XZ"
+ retq
+ .globl "?begin@?$Registry at VPluginASTAction@clang@@$$V at llvm@@SA?AViterator at 12@XZ"
+"?begin@?$Registry at VPluginASTAction@clang@@$$V at llvm@@SA?AViterator at 12@XZ":
+ retq
diff --git a/llvm/test/tools/extract_symbols/lit.local.cfg b/llvm/test/tools/extract_symbols/lit.local.cfg
new file mode 100644
index 0000000000000..70cdd4f079418
--- /dev/null
+++ b/llvm/test/tools/extract_symbols/lit.local.cfg
@@ -0,0 +1,21 @@
+import os
+
+import lit.util
+
+config.suffixes = [".test"]
+
+if not "X86" in config.root.targets:
+ config.unsupported = True
+
+config.substitutions.append(
+ (
+ "%extract_symbols",
+ "'%s' %s --nm=%s --readobj=%s"
+ % (
+ config.python_executable,
+ os.path.join(config.llvm_src_root, "utils", "extract_symbols.py"),
+ lit.util.which("llvm-nm", config.llvm_tools_dir),
+ lit.util.which("llvm-readobj", config.llvm_tools_dir),
+ ),
+ )
+)
diff --git a/llvm/test/tools/extract_symbols/registry-itanium.test b/llvm/test/tools/extract_symbols/registry-itanium.test
new file mode 100644
index 0000000000000..f94e4e4df7947
--- /dev/null
+++ b/llvm/test/tools/extract_symbols/registry-itanium.test
@@ -0,0 +1,13 @@
+## Itanium counterpart of registry-microsoft.test: a reference to the
+## getRegistryLinkListInstance<R>() accessor counts as a reference to Registry,
+## so its explicitly instantiated members that are defined once are exported.
+## Symbols are printed in input order, so the checks are ordered.
+
+# RUN: rm -rf %t && mkdir -p %t
+# RUN: llvm-mc -triple=x86_64-linux-gnu -filetype=obj %S/Inputs/lib-itanium.s -o %t/lib.o
+# RUN: llvm-mc -triple=x86_64-linux-gnu -filetype=obj %S/Inputs/user-itanium.s -o %t/user.o
+# RUN: %extract_symbols --mangling=itanium %t/lib.o %t/user.o \
+# RUN: | FileCheck %s --implicit-check-not=Registry
+
+# CHECK: _ZN4llvm6detail27getRegistryLinkListInstanceINS_8RegistryIN5clang15PluginASTActionEJEEEEERNS0_23RegistryLinkListStorageIT_EEv
+# CHECK-NEXT: _ZN4llvm8RegistryIN5clang15PluginASTActionEJEE8add_nodeEPNS3_4nodeE
diff --git a/llvm/test/tools/extract_symbols/registry-microsoft.test b/llvm/test/tools/extract_symbols/registry-microsoft.test
new file mode 100644
index 0000000000000..41ff71c8441f5
--- /dev/null
+++ b/llvm/test/tools/extract_symbols/registry-microsoft.test
@@ -0,0 +1,19 @@
+## Registry<T> is only ever used through the getRegistryLinkListInstance<R>()
+## accessor that LLVM_DEFINE_REGISTRY defines once, so the accessor (a ??$
+## specialization) and the explicitly instantiated Registry<T> members that only
+## a plugin references must both be exported, or a plugin cannot link against a
+## static tool built with LLVM_EXPORT_SYMBOLS_FOR_PLUGINS.
+##
+## Symbols are printed in input order, so the checks are ordered.
+
+# RUN: rm -rf %t && mkdir -p %t
+# RUN: llvm-mc -triple=x86_64-pc-windows-msvc -filetype=obj %S/Inputs/lib-msvc.s -o %t/lib.obj
+# RUN: llvm-mc -triple=x86_64-pc-windows-msvc -filetype=obj %S/Inputs/user-msvc.s -o %t/user.obj
+# RUN: %extract_symbols --mangling=microsoft %t/lib.obj %t/user.obj \
+# RUN: | FileCheck %s --implicit-check-not=Registry
+
+## The accessor specialization and add_node are defined once and referenced
+## only through the accessor, so both are exported. begin() is defined in both
+## objects (the user TU instantiated the inline member itself), so it is not.
+# CHECK: ??$getRegistryLinkListInstance at V?$Registry at VPluginASTAction@clang@@$$V at llvm@@@detail at llvm@@YAAEAU?$RegistryLinkListStorage at V?$Registry at VPluginASTAction@clang@@$$V at llvm@@@01 at XZ
+# CHECK-NEXT: ?add_node@?$Registry at VPluginASTAction@clang@@$$V at llvm@@SAXPEAVnode at 12@@Z
diff --git a/llvm/utils/extract_symbols.py b/llvm/utils/extract_symbols.py
index 40a43d3655552..b51531dbca111 100755
--- a/llvm/utils/extract_symbols.py
+++ b/llvm/utils/extract_symbols.py
@@ -106,6 +106,13 @@ def should_keep_microsoft_symbol(symbol, calling_convention_decoration):
# instantiated locally. Pattern: ??$getAs@<template_arg>@Type at clang@@...
if symbol.startswith("??$getAs@") and "@Type at clang@@" in symbol:
return symbol
+ # Keep the Registry<T> storage accessors: explicit specializations that
+ # LLVM_DEFINE_REGISTRY defines once, and that plugins call to register.
+ if (
+ symbol.startswith("??$getRegistryLinkListInstance@")
+ and "@detail at llvm@@" in symbol
+ ):
+ return symbol
return None
# Delete lambda object constructors and operator() functions. These start
# with ??R<lambda_ or ??0<lambda_ and can be discarded because lambdas are
@@ -120,14 +127,6 @@ def should_keep_microsoft_symbol(symbol, calling_convention_decoration):
# Skip X86GenMnemonicTables functions, they are not exposed from llvm/include/.
elif re.match(r"\?is[A-Z0-9]*@X86 at llvm", symbol):
return None
- # Keep Registry<T>::Head and Registry<T>::Tail static members for plugin support.
- # Pattern matches: ?Head@?$Registry@<template_args>@llvm@@ or ?Tail@?$Registry at ...
- elif (
- "?$Registry@" in symbol
- and "@llvm@@" in symbol
- and (symbol.startswith("?Head@") or symbol.startswith("?Tail@"))
- ):
- return symbol
# Skip symbols added by the compiler with -fprofile-generate.
elif symbol.startswith("__prof"):
return None
@@ -515,6 +514,11 @@ def parse_tool_path(parser, tool, val):
template = get_template_name(sym, args.mangling)
if template:
template_instantiation_refs.add(template)
+ # Registry<T> is only ever used through its storage accessor.
+ if template.endswith("getRegistryLinkListInstance"):
+ template_instantiation_refs.add(
+ "Registry" if args.mangling == "microsoft" else "8Registry"
+ )
# Print symbols which both:
# * Appear in exactly one input, as symbols defined in multiple
More information about the llvm-branch-commits
mailing list