Source code

Revision control

Copy as Markdown

Other Tools

From 5128a6a02fd03c889921ffc3c5810923e6b31a8f Mon Sep 17 00:00:00 2001
From: Fabian Meumertzheim <fabian@meumertzhe.im>
Date: Mon, 19 Oct 2026 19:07:37 +0200
Subject: [PATCH] [ThinLTO] Record prevailing GUIDs by linker symbol name
(#226911)
`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
---
llvm/lib/LTO/LTO.cpp | 6 +--
llvm/test/ThinLTO/X86/macho-guid-collision.ll | 41 +++++++++++++++++++
2 files changed, 42 insertions(+), 5 deletions(-)
create mode 100644 llvm/test/ThinLTO/X86/macho-guid-collision.ll
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index c8c5b0880819..9a671d3b2c51 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1202,11 +1202,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");
@@ -1227,6 +1222,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 000000000000..ced1651fa42c
--- /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
+}
--
2.56.0.1.g190df23c76