[llvm] [ThinLTO] Record prevailing GUIDs by linker symbol name (PR #226911)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 28 01:06:22 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lto
Author: Fabian Meumertzheim (fmeum)
<details>
<summary>Changes</summary>
`LTO::addThinLTO` updates `GlobalResolutions` using the IR name from a summary, but that map is keyed by mangled linker symbol names. On Mach-O, the IR name `_foo` can therefore match the resolution for `foo` (whose linker name is `_foo`). This assigns `_foo`'s GUID to `foo` and can incorrectly internalize `foo` even when another module references it, producing an undefined-symbol error.
Move the GUID assignment into the symbol loop, where `Sym.getName()` identifies the correct resolution and the GUID has already been obtained using `Sym.getIRName()`. Preserve the prevailing-definition check and the existing fallback for bitcode without a GUID table.
This fixes a regression introduced by 39dcb0ff91b312bb269334168d5e32be78b60417 (#<!-- -->201849). It surfaced when linking mimalloc 3.5.3 into a PGO/ThinLTO build of LLVM on macOS. The standalone reproducer below needs only Clang, LLD, and the macOS SDK.
Reproducer on macOS: set `LLVM_BIN` to the absolute path of the LLVM `bin` directory, then run:
```sh
set -eu
: "${LLVM_BIN:?Set LLVM_BIN to the directory containing clang and ld64.lld}"
repro_dir=$(mktemp -d)
cd "$repro_dir"
cat > defs.c <<'C'
volatile int v;
__attribute__((noinline)) int foo(void) { return v; }
__attribute__((noinline)) int _foo(void) { return v + 1; }
C
cat > main.c <<'C'
int foo(void);
int _foo(void);
int main(void) { return foo() + _foo() != 1; }
C
"$LLVM_BIN/clang" -O2 -flto=thin -fuse-ld=lld \
--ld-path="$LLVM_BIN/ld64.lld" \
-isysroot "$(xcrun --show-sdk-path)" \
-Wl,-mllvm,-import-instr-limit=0 defs.c main.c -o repro
./repro
```
With unpatched LLVM 23.1.2, the link fails:
```text
ld64.lld: error: undefined symbol: foo
>>> did you mean: _foo
```
With the patch, it links successfully and `./repro` exits with status 0. Disabling ThinLTO importing keeps the cross-module references visible in this small example.
The added two-module `llvm-lto2` regression checks that both `foo` and `_foo` remain externally defined after importing. Before the fix, `foo` incorrectly becomes `internal`.
Assisted-by: OpenAI Codex
---
Full diff: https://github.com/llvm/llvm-project/pull/226911.diff
2 Files Affected:
- (modified) llvm/lib/LTO/LTO.cpp (+1-5)
- (added) llvm/test/ThinLTO/X86/macho-guid-collision.ll (+41)
``````````diff
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 50232bf87d257..a8d4bed429593 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1199,11 +1199,6 @@ LTO::addThinLTO(BitcodeModule BM, ArrayRef<InputFile::Symbol> Syms,
auto IT = IRSpecifiedGUIDs.insert({VI.name(), VI.getGUID()});
(void)IT;
assert(IT.second);
- if (auto GRIt = GlobalResolutions->find(VI.name());
- GRIt != GlobalResolutions->end() &&
- Prevailing.count(VI.name())) {
- GRIt->second.setGUID(VI.getGUID());
- }
}))
return Err;
LLVM_DEBUG(dbgs() << "Module " << BMID << "\n");
@@ -1224,6 +1219,7 @@ LTO::addThinLTO(BitcodeModule BM, ArrayRef<InputFile::Symbol> Syms,
if (!Sym.getIRName().empty() &&
(R.Prevailing || R.FinalDefinitionInLinkageUnit)) {
if (R.Prevailing) {
+ (*GlobalResolutions)[Sym.getName()].setGUID(GUID);
ThinLTO.setPrevailingModuleForGUID(GUID, BMID);
// For linker redefined symbols (via --wrap or --defsym) we want to
// switch the linkage to `weak` to prevent IPOs from happening.
diff --git a/llvm/test/ThinLTO/X86/macho-guid-collision.ll b/llvm/test/ThinLTO/X86/macho-guid-collision.ll
new file mode 100644
index 0000000000000..ced1651fa42cd
--- /dev/null
+++ b/llvm/test/ThinLTO/X86/macho-guid-collision.ll
@@ -0,0 +1,41 @@
+; RUN: split-file %s %t
+; RUN: opt -module-summary %t/defs.ll -o %t/defs.bc
+; RUN: opt -module-summary %t/main.ll -o %t/main.bc
+; RUN: llvm-lto2 run %t/defs.bc %t/main.bc -o %t/out -save-temps \
+; RUN: -import-instr-limit=0 \
+; RUN: -r=%t/defs.bc,_foo,pl -r=%t/defs.bc,__foo,pl \
+; RUN: -r=%t/main.bc,_main,plx -r=%t/main.bc,_foo,l \
+; RUN: -r=%t/main.bc,__foo,l
+; RUN: llvm-dis %t/out.1.3.import.bc -o - | FileCheck %s
+
+;; Global resolutions use mangled symbol names, whereas the summary records IR
+;; names. Looking up the IR name _foo in the resolutions for Mach-O incorrectly
+;; assigns its GUID to foo and allows foo to be internalized.
+; CHECK: define dso_local i32 @foo()
+; CHECK: define dso_local i32 @_foo()
+
+;--- defs.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-macosx10.15.0"
+
+define i32 @foo() noinline {
+ ret i32 1
+}
+
+define i32 @_foo() noinline {
+ ret i32 2
+}
+
+;--- main.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-macosx10.15.0"
+
+declare i32 @foo()
+declare i32 @_foo()
+
+define i32 @main() {
+ %a = call i32 @foo()
+ %b = call i32 @_foo()
+ %sum = add i32 %a, %b
+ ret i32 %sum
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/226911
More information about the llvm-commits
mailing list