[llvm-branch-commits] [lld] [lld-macho] Build the export trie by edges, not bytes (NFC) (PR #234857)
Jez Ng via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Oct 22 23:49:22 PDT 2026
https://github.com/int3 created https://github.com/llvm/llvm-project/pull/234857
Emit singleton suffixes directly and scan common prefixes in one pass,
avoiding repeated byte-wise partitioning. Preserve radix traversal,
node allocation order, and terminal encoding. This speeds up builds that
emit large export tries.
Wall time with [LinkerBench](https://github.com/int3/macho-linker-bench),
#233399 → this PR, Release, assertions off. Median ± 95% CI over at least 20
interleaved runs per side.
M4 Mac mini (10 cores, 16 GB):
| Link | Exports | Default threads | 1 thread |
| --- | ---: | ---: | ---: |
| rust-analyzer (debug) | 248,607 | 650.3 ± 1.2 → 625.4 ± 1.9 ms (−3.8%) | 951.7 ± 0.9 → 870.3 ± 1.7 ms (−8.6%) |
| SourceKit-LSP | 105,359 | 604.2 ± 2.3 → 605.4 ± 1.6 ms | 774.5 ± 1.3 → 767.5 ± 0.6 ms (−0.9%) |
| Firefox for iOS | 82,489 | 552.6 ± 0.9 → 552.0 ± 1.6 ms | 651.8 ± 1.3 → 646.5 ± 1.3 ms (−0.8%) |
| libLLVM | 55,996 | 365.3 ± 1.0 → 365.7 ± 1.0 ms | |
rust-analyzer (release), Telegram and Firefox for macOS are unchanged (1.00×).
Ryzen 7 7700 (Linux), default threads, all 15 LinkerBench workloads:
rust-analyzer (debug) goes from 1.241 ± 0.011 to 1.158 ± 0.006 s (−6.7%);
the other 14 workloads are within noise of 1.00×.
Assisted by Opus 5.5
>From 603b2ae7280c71933444f70744840a2326a7b556 Mon Sep 17 00:00:00 2001
From: Jez Ng <jezreel at gmail.com>
Date: Mon, 19 Oct 2026 11:05:22 +0200
Subject: [PATCH 1/2] [lld-macho] Build the export trie by edges, not bytes
(NFC)
Emit singleton suffixes directly and scan common prefixes in one pass,
avoiding repeated byte-wise partitioning. Preserve radix traversal,
node allocation order, and terminal encoding.
On a Ryzen 7 7700, a rust-analyzer debug link with 248,607 exports
changes from 2079 to 1948 ms at one thread and 1269 to 1185 ms at
eight threads (median wall times over four interleaved pairs).
---
lld/MachO/ExportTrie.cpp | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/lld/MachO/ExportTrie.cpp b/lld/MachO/ExportTrie.cpp
index 4a9e14c3d920d..55235219fb3b1 100644
--- a/lld/MachO/ExportTrie.cpp
+++ b/lld/MachO/ExportTrie.cpp
@@ -226,6 +226,19 @@ void TrieBuilder::sortAndBuild(MutableArrayRef<const Symbol *> vec,
if (vec.empty())
return;
+ // A singleton needs just one edge for its remaining suffix.
+ if (vec.size() == 1) {
+ const Symbol *sym = vec.front();
+ StringRef suffix = sym->getName().drop_front(lastPos);
+ if (!suffix.empty()) {
+ TrieNode *newNode = makeNode();
+ node->edges.emplace_back(suffix, newNode);
+ node = newNode;
+ }
+ node->info = ExportInfo(*sym, imageBase);
+ return;
+ }
+
// Partition items so that items in [0, i) are less than the pivot,
// [i, j) are the same as the pivot, and [j, vec.size()) are greater than
// the pivot.
@@ -264,6 +277,20 @@ void TrieBuilder::sortAndBuild(MutableArrayRef<const Symbol *> vec,
// sortAndBuild(vec.slice(i, j - i), node, lastPos, pos + 1);
vec = vec.slice(i, j - i);
++pos;
+ // Skip the rest of a common prefix without partitioning at every byte.
+ if (vec.size() > 1) {
+ StringRef firstName = vec.front()->getName();
+ size_t commonEnd = firstName.size();
+ for (const Symbol *sym : vec.drop_front()) {
+ StringRef name = sym->getName();
+ commonEnd = std::min(commonEnd, name.size());
+ size_t commonPos = pos;
+ while (commonPos < commonEnd && firstName[commonPos] == name[commonPos])
+ ++commonPos;
+ commonEnd = commonPos;
+ }
+ pos = commonEnd;
+ }
goto tailcall;
}
}
>From 470f33ec44ca83a72cb3378bdeaaf56bbc2dc467 Mon Sep 17 00:00:00 2001
From: Jez Ng <me at jezng.com>
Date: Mon, 19 Oct 2026 04:02:38 -0700
Subject: [PATCH 2/2] Improve comments in ExportTrie.cpp
Refactor comments for clarity and optimization explanation.
---
lld/MachO/ExportTrie.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lld/MachO/ExportTrie.cpp b/lld/MachO/ExportTrie.cpp
index 55235219fb3b1..2caf0f7ede6bd 100644
--- a/lld/MachO/ExportTrie.cpp
+++ b/lld/MachO/ExportTrie.cpp
@@ -226,7 +226,7 @@ void TrieBuilder::sortAndBuild(MutableArrayRef<const Symbol *> vec,
if (vec.empty())
return;
- // A singleton needs just one edge for its remaining suffix.
+ // Create singletons in one go instead of doing a tail call per character.
if (vec.size() == 1) {
const Symbol *sym = vec.front();
StringRef suffix = sym->getName().drop_front(lastPos);
@@ -277,7 +277,8 @@ void TrieBuilder::sortAndBuild(MutableArrayRef<const Symbol *> vec,
// sortAndBuild(vec.slice(i, j - i), node, lastPos, pos + 1);
vec = vec.slice(i, j - i);
++pos;
- // Skip the rest of a common prefix without partitioning at every byte.
+ // Skip the rest of a common prefix instead of doing a tail call per
+ // character.
if (vec.size() > 1) {
StringRef firstName = vec.front()->getName();
size_t commonEnd = firstName.size();
More information about the llvm-branch-commits
mailing list