[lld] [ELF] Parallelize version script and dynamic list matching (PR #223208)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 12 22:54:47 PDT 2026
https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/223208
scanVersionScript and handleDynamicList make one serial pass over
symVector per wildcard pattern, which is inefficient when there are
multiple wildcard patterns.
Parallelize this process: for each symbol, match against all wildcard
patterns and take the first match. This drops the TensorFlow link from
4.8s to 1.8s (--threads=8; its version script has 24 `*foo*` and most
time was in scanVersionScript).
>From d7c3c3c00ea73819850530faf0dbe3b1c13d18c5 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 12 Sep 2026 21:30:09 -0700
Subject: [PATCH] [ELF] Parallelize version script and dynamic list matching
scanVersionScript and handleDynamicList make one serial pass over
symVector per wildcard pattern, which is inefficient when there are
multiple wildcard patterns.
Parallelize this process: for each symbol, match against all wildcard
patterns and take the first match. This drops the TensorFlow link from
4.8s to 1.8s (--threads=8; its version script has 24 `*foo*` and most
time was in scanVersionScript).
---
lld/ELF/SymbolTable.cpp | 106 +++++++++++++++++-----------------------
lld/ELF/SymbolTable.h | 4 --
2 files changed, 44 insertions(+), 66 deletions(-)
diff --git a/lld/ELF/SymbolTable.cpp b/lld/ELF/SymbolTable.cpp
index 810cb6df5afcd..142b2f1b92ddc 100644
--- a/lld/ELF/SymbolTable.cpp
+++ b/lld/ELF/SymbolTable.cpp
@@ -21,6 +21,7 @@
#include "lld/Common/Strings.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Demangle/Demangle.h"
+#include "llvm/Support/Parallel.h"
using namespace llvm;
using namespace llvm::object;
@@ -127,19 +128,7 @@ static std::string demangleForVersion(StringRef name) {
return s;
}
-// Initialize demangledSyms with a map from demangled symbols to symbol
-// objects. Used to handle "extern C++" directive in version scripts.
-//
-// The map will contain all demangled symbols. That can be very large,
-// and in LLD we generally want to avoid do anything for each symbol.
-// Then, why are we doing this? Here's why.
-//
-// Users can use "extern C++ {}" directive to match against demangled
-// C++ symbols. For example, you can write a pattern such as
-// "llvm::*::foo(int, ?)". Obviously, there's no way to handle this
-// other than trying to match a pattern against all demangled symbols.
-// So, if "extern C++" feature is used, we need to demangle all known
-// symbols.
+// Map from demangled name to symbols, for exact lookups in extern "C++" blocks.
StringMap<SmallVector<Symbol *, 0>> &SymbolTable::getDemangledSyms() {
if (!demangledSyms) {
demangledSyms.emplace();
@@ -159,45 +148,6 @@ SmallVector<Symbol *, 0> SymbolTable::findByVersion(SymbolVersion ver) {
return {};
}
-SmallVector<Symbol *, 0> SymbolTable::findAllByVersion(SymbolVersion ver,
- bool includeNonDefault) {
- SmallVector<Symbol *, 0> res;
- SingleStringMatcher m(ver.name);
- auto check = [&](const Symbol &sym) -> bool {
- if (!includeNonDefault)
- return !sym.hasVersionSuffix;
- return !sym.hasVersionSuffix ||
- !sym.getName().split('@').second.starts_with('@');
- };
-
- if (ver.isExternCpp) {
- for (auto &p : getDemangledSyms())
- if (m.match(p.first()))
- for (Symbol *sym : p.second)
- if (check(*sym))
- res.push_back(sym);
- return res;
- }
-
- for (Symbol *sym : symVector)
- if (canBeVersioned(*sym) && check(*sym) && m.match(sym->getName()))
- res.push_back(sym);
- return res;
-}
-
-void SymbolTable::handleDynamicList() {
- SmallVector<Symbol *, 0> syms;
- for (SymbolVersion &ver : ctx.arg.dynamicList) {
- if (ver.hasWildcard)
- syms = findAllByVersion(ver, /*includeNonDefault=*/true);
- else
- syms = findByVersion(ver);
-
- for (Symbol *sym : syms)
- sym->isExported = sym->inDynamicList = true;
- }
-}
-
// Set symbol versions to symbols. This function handles patterns containing no
// wildcard characters. Return false if no symbol definition matches ver.
bool SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId) {
@@ -236,10 +186,11 @@ bool SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId) {
namespace {
struct WildcardPattern {
- SymbolVersion ver;
+ SingleStringMatcher matcher;
+ bool isExternCpp;
uint16_t versionId;
WildcardPattern(const SymbolVersion &ver, uint16_t versionId)
- : ver(ver), versionId(versionId) {}
+ : matcher(ver.name), isExternCpp(ver.isExternCpp), versionId(versionId) {}
};
} // namespace
@@ -314,20 +265,51 @@ void SymbolTable::scanVersionScript() {
}
pats.append(asterisks);
+ auto findFirstMatch = [&](ArrayRef<WildcardPattern> pats,
+ StringRef name) -> const WildcardPattern * {
+ std::optional<std::string> demangled;
+ for (auto &pat : pats) {
+ if (pat.isExternCpp && !demangled)
+ demangled = demangleForVersion(name);
+ if (pat.matcher.match(pat.isExternCpp ? StringRef(*demangled) : name))
+ return &pat;
+ }
+ return nullptr;
+ };
+
// Exact matching takes precedence over wildcard matching, so a wildcard
// assigns a version only if none has been assigned.
- for (auto &pat : pats) {
- for (Symbol *sym : findAllByVersion(pat.ver, /*includeNonDefault=*/false)) {
- if (!sym->versionScriptAssigned) {
- sym->versionScriptAssigned = true;
- sym->versionId = pat.versionId;
- }
- }
+ if (!pats.empty()) {
+ parallelForEach(symVector, [&](Symbol *sym) {
+ if (sym->versionScriptAssigned || sym->hasVersionSuffix ||
+ !canBeVersioned(*sym))
+ return;
+ if (auto *pat = findFirstMatch(pats, sym->getName()))
+ sym->versionId = pat->versionId;
+ });
}
// Handle --dynamic-list. If a specified symbol is also matched by local: in a
// version script, the version script takes precedence.
- handleDynamicList();
+ SmallVector<Symbol *, 0> syms;
+ pats.clear();
+ for (SymbolVersion &ver : ctx.arg.dynamicList) {
+ if (ver.hasWildcard) {
+ pats.emplace_back(ver, 0);
+ } else {
+ for (Symbol *sym : findByVersion(ver))
+ sym->isExported = sym->inDynamicList = true;
+ }
+ }
+ if (!pats.empty()) {
+ parallelForEach(symVector, [&](Symbol *sym) {
+ if (!canBeVersioned(*sym))
+ return;
+ StringRef name = sym->getName();
+ if (findFirstMatch(pats, name))
+ sym->isExported = sym->inDynamicList = true;
+ });
+ }
}
Symbol *SymbolTable::addUnusedUndefined(StringRef name, uint8_t binding) {
diff --git a/lld/ELF/SymbolTable.h b/lld/ELF/SymbolTable.h
index 058684bf84af2..28e3810ab63bd 100644
--- a/lld/ELF/SymbolTable.h
+++ b/lld/ELF/SymbolTable.h
@@ -56,8 +56,6 @@ class SymbolTable {
Symbol *find(StringRef name);
- void handleDynamicList();
-
Symbol *addUnusedUndefined(StringRef name,
uint8_t binding = llvm::ELF::STB_GLOBAL);
@@ -83,8 +81,6 @@ class SymbolTable {
private:
SmallVector<Symbol *, 0> findByVersion(SymbolVersion ver);
- SmallVector<Symbol *, 0> findAllByVersion(SymbolVersion ver,
- bool includeNonDefault);
llvm::StringMap<SmallVector<Symbol *, 0>> &getDemangledSyms();
bool assignExactVersion(SymbolVersion ver, uint16_t versionId);
More information about the llvm-commits
mailing list