[lld] [lld:MachO] Allow independent override of weak symbols aliased via .set (PR #167825)
Joshua Haberman via llvm-commits
llvm-commits at lists.llvm.org
Fri Nov 21 12:07:20 PST 2025
https://github.com/haberman updated https://github.com/llvm/llvm-project/pull/167825
>From 44ad7773ab66294050a9c40a9ea26bcad23495d9 Mon Sep 17 00:00:00 2001
From: Joshua Haberman <jhaberman at gmail.com>
Date: Wed, 12 Nov 2025 22:42:16 -0800
Subject: [PATCH 1/2] [lld:MachO] Allow independent override of weak symbols
aliased via .set
Currently, if multiple external weak symbols are defined at the same
address in an object file (e.g., by using the .set assembler directive
to alias them to a single weak variable), ld64.lld treats them as a single
unit. When any one of these symbols is overridden by a strong definition,
all of the original weak symbols resolve to the strong definition.
This patch changes the behavior in `transplantSymbolsAtOffset`. When a
weak symbol is being replaced by a strong one, only non-external (local)
symbols at the same offset are moved to the new symbol's section. Other
*external* symbols are no longer transplanted.
This allows each external weak symbol to be overridden independently.
This behavior is consistent with ld-classic, but diverges from ld-prime.
A new test case, weak-alias-override.s, is added.
Fixes #167262
---
lld/MachO/SymbolTable.cpp | 31 ++++-----
lld/test/MachO/weak-alias-override.s | 97 ++++++++++++++++++++++++++++
2 files changed, 113 insertions(+), 15 deletions(-)
create mode 100644 lld/test/MachO/weak-alias-override.s
diff --git a/lld/MachO/SymbolTable.cpp b/lld/MachO/SymbolTable.cpp
index baddddcb76fbf..5e1d7a5573256 100644
--- a/lld/MachO/SymbolTable.cpp
+++ b/lld/MachO/SymbolTable.cpp
@@ -78,22 +78,23 @@ static void transplantSymbolsAtOffset(InputSection *fromIsec,
auto insertIt = llvm::upper_bound(toIsec->symbols, toOff, symSucceedsOff);
llvm::erase_if(fromIsec->symbols, [&](Symbol *s) {
auto *d = cast<Defined>(s);
- if (d->value != fromOff)
+ if (d == skip)
+ return true;
+ if (d->value != fromOff || d->isExternal())
return false;
- if (d != skip) {
- // This repeated insertion will be quadratic unless insertIt is the end
- // iterator. However, that is typically the case for files that have
- // .subsections_via_symbols set.
- insertIt = toIsec->symbols.insert(insertIt, d);
- d->originalIsec = toIsec;
- d->value = toOff;
- // We don't want to have more than one unwindEntry at a given address, so
- // drop the redundant ones. We We can safely drop the unwindEntries of
- // the symbols in fromIsec since we will be adding another unwindEntry as
- // we finish parsing toIsec's file. (We can assume that toIsec has its
- // own unwindEntry because of the ODR.)
- d->originalUnwindEntry = nullptr;
- }
+
+ // This repeated insertion will be quadratic unless insertIt is the end
+ // iterator. However, that is typically the case for files that have
+ // .subsections_via_symbols set.
+ insertIt = toIsec->symbols.insert(insertIt, d);
+ d->originalIsec = toIsec;
+ d->value = toOff;
+ // We don't want to have more than one unwindEntry at a given address, so
+ // drop the redundant ones. We We can safely drop the unwindEntries of
+ // the symbols in fromIsec since we will be adding another unwindEntry as
+ // we finish parsing toIsec's file. (We can assume that toIsec has its
+ // own unwindEntry because of the ODR.)
+ d->originalUnwindEntry = nullptr;
return true;
});
}
diff --git a/lld/test/MachO/weak-alias-override.s b/lld/test/MachO/weak-alias-override.s
new file mode 100644
index 0000000000000..f56bfa5c34d95
--- /dev/null
+++ b/lld/test/MachO/weak-alias-override.s
@@ -0,0 +1,97 @@
+# REQUIRES: x86
+# RUN: rm -rf %t; split-file %s %t
+# RUN: mkdir -p %t/bin
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/weak.o %t/weak.s
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/strong_a.o %t/strong_a.s
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/strong_b.o %t/strong_b.s
+
+# --- Test Case 1: No overrides
+# RUN: %lld %t/weak.o -o %t/bin/alone -e _s
+# RUN: llvm-nm -am %t/bin/alone | FileCheck --check-prefix=NM_ALONE %s
+
+# NM_ALONE: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
+# NM_ALONE: [[P_ADDR]] (__TEXT,__const) weak external _weak_a
+# NM_ALONE: [[P_ADDR]] (__TEXT,__const) weak external _weak_b
+
+# --- Test Case 2: Override weak_a
+# RUN: %lld %t/weak.o %t/strong_a.o -o %t/bin/with_a -e _s
+# RUN: llvm-nm -am %t/bin/with_a | FileCheck --check-prefix=NM_WITH_A %s
+# RUN: llvm-nm -am %t/bin/with_a | FileCheck --check-prefix=NM_WITH_A_BAD %s
+
+# NM_WITH_A: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_A: [[A_ADDR:[0-9a-f]+]] (__TEXT,__const) external _strong_a
+# NM_WITH_A: [[A_ADDR]] (__TEXT,__const) external _weak_a
+# NM_WITH_A: [[P_ADDR]] (__TEXT,__const) weak external _weak_b
+
+# --- Addresses of _placeholder_int and _strong_a must not match.
+# NM_WITH_A_BAD: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_A_BAD-NOT: [[P_ADDR]] (__TEXT,__const) external _strong_a
+
+# --- Test Case 3: Override weak_b
+# RUN: %lld %t/weak.o %t/strong_b.o -o %t/bin/with_b -e _s
+# RUN: llvm-nm -am %t/bin/with_b | FileCheck --check-prefix=NM_WITH_B %s
+# RUN: llvm-nm -am %t/bin/with_b | FileCheck --check-prefix=NM_WITH_B_BAD %s
+
+# NM_WITH_B: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_B: [[B_ADDR:[0-9a-f]+]] (__TEXT,__const) external _strong_b
+# NM_WITH_B: [[P_ADDR]] (__TEXT,__const) weak external _weak_a
+# NM_WITH_B: [[B_ADDR]] (__TEXT,__const) external _weak_b
+
+# --- Addresses of _placeholder_int and _strong_a must not match.
+# NM_WITH_B_BAD: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_B_BAD-NOT: [[P_ADDR]] (__TEXT,__const) external _strong_b
+
+# --- Test Case 4: Override weak_a and weak_b
+# RUN: %lld %t/weak.o %t/strong_a.o %t/strong_b.o -o %t/bin/with_ab -e _s
+# RUN: llvm-nm -am %t/bin/with_ab | FileCheck --check-prefix=NM_WITH_AB %s
+# RUN: llvm-nm -am %t/bin/with_ab | FileCheck --check-prefix=NM_WITH_AB_BAD %s
+
+# NM_WITH_AB: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_AB: [[A_ADDR:[0-9a-f]+]] (__TEXT,__const) external _strong_a
+# NM_WITH_AB: [[B_ADDR:[0-9a-f]+]] (__TEXT,__const) external _strong_b
+# NM_WITH_AB: [[A_ADDR]] (__TEXT,__const) external _weak_a
+# NM_WITH_AB: [[B_ADDR]] (__TEXT,__const) external _weak_b
+
+# --- Addresses of _placeholder_int, _strong_a, and _strong_b must all be distinct
+# NM_WITH_AB_BAD: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_AB_BAD-NOT: [[P_ADDR]] (__TEXT,__const) external _strong_a
+# NM_WITH_AB_BAD-NOT: [[P_ADDR]] (__TEXT,__const) external _strong_b
+
+#--- weak.s
+.section __TEXT,__const
+.globl _placeholder_int
+.weak_definition _placeholder_int
+_placeholder_int:
+ .long 0
+
+.globl _weak_a
+.set _weak_a, _placeholder_int
+.weak_definition _weak_a
+
+.globl _weak_b
+.set _weak_b, _placeholder_int
+.weak_definition _weak_b
+
+.globl _s
+_s:
+ .quad _weak_a
+ .quad _weak_b
+
+#--- strong_a.s
+.section __TEXT,__const
+.globl _strong_a
+_strong_a:
+ .long 1
+
+.globl _weak_a
+_weak_a = _strong_a
+
+#--- strong_b.s
+.section __TEXT,__const
+.globl _strong_b
+_strong_b:
+ .long 2
+
+.globl _weak_b
+_weak_b = _strong_b
>From ba350cd352f50a23e2ece8b5ee852fd733428864 Mon Sep 17 00:00:00 2001
From: Joshua Haberman <jhaberman at gmail.com>
Date: Fri, 21 Nov 2025 12:06:03 -0800
Subject: [PATCH 2/2] Addressed review comments.
---
lld/MachO/SymbolTable.cpp | 12 ++++----
lld/test/MachO/weak-alias-override.s | 46 ++++++++++++++--------------
2 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/lld/MachO/SymbolTable.cpp b/lld/MachO/SymbolTable.cpp
index 5e1d7a5573256..a7db5a3ac96e3 100644
--- a/lld/MachO/SymbolTable.cpp
+++ b/lld/MachO/SymbolTable.cpp
@@ -61,8 +61,8 @@ struct DuplicateSymbolDiag {
SmallVector<DuplicateSymbolDiag> dupSymDiags;
} // namespace
-// Move symbols at \p fromOff in \p fromIsec into \p toIsec, unless that symbol
-// is \p skip.
+// Move local symbols at \p fromOff in \p fromIsec into \p toIsec, unless that
+// symbol is \p skip, in which case we just remove it.
static void transplantSymbolsAtOffset(InputSection *fromIsec,
InputSection *toIsec, Defined *skip,
uint64_t fromOff, uint64_t toOff) {
@@ -90,10 +90,10 @@ static void transplantSymbolsAtOffset(InputSection *fromIsec,
d->originalIsec = toIsec;
d->value = toOff;
// We don't want to have more than one unwindEntry at a given address, so
- // drop the redundant ones. We We can safely drop the unwindEntries of
- // the symbols in fromIsec since we will be adding another unwindEntry as
- // we finish parsing toIsec's file. (We can assume that toIsec has its
- // own unwindEntry because of the ODR.)
+ // drop the redundant ones. We can safely drop the unwindEntries of the
+ // symbols in fromIsec since we will be adding another unwindEntry as we
+ // finish parsing toIsec's file. (We can assume that toIsec has its own
+ // unwindEntry because of the ODR.)
d->originalUnwindEntry = nullptr;
return true;
});
diff --git a/lld/test/MachO/weak-alias-override.s b/lld/test/MachO/weak-alias-override.s
index f56bfa5c34d95..224ddc4ec38d6 100644
--- a/lld/test/MachO/weak-alias-override.s
+++ b/lld/test/MachO/weak-alias-override.s
@@ -10,53 +10,53 @@
# RUN: %lld %t/weak.o -o %t/bin/alone -e _s
# RUN: llvm-nm -am %t/bin/alone | FileCheck --check-prefix=NM_ALONE %s
-# NM_ALONE: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
-# NM_ALONE: [[P_ADDR]] (__TEXT,__const) weak external _weak_a
-# NM_ALONE: [[P_ADDR]] (__TEXT,__const) weak external _weak_b
+# NM_ALONE: [[#%x, P_ADDR:]] (__TEXT,__const) weak external _placeholder_int
+# NM_ALONE: [[#P_ADDR]] (__TEXT,__const) weak external _weak_a
+# NM_ALONE: [[#P_ADDR]] (__TEXT,__const) weak external _weak_b
# --- Test Case 2: Override weak_a
# RUN: %lld %t/weak.o %t/strong_a.o -o %t/bin/with_a -e _s
# RUN: llvm-nm -am %t/bin/with_a | FileCheck --check-prefix=NM_WITH_A %s
# RUN: llvm-nm -am %t/bin/with_a | FileCheck --check-prefix=NM_WITH_A_BAD %s
-# NM_WITH_A: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
-# NM_WITH_A: [[A_ADDR:[0-9a-f]+]] (__TEXT,__const) external _strong_a
-# NM_WITH_A: [[A_ADDR]] (__TEXT,__const) external _weak_a
-# NM_WITH_A: [[P_ADDR]] (__TEXT,__const) weak external _weak_b
+# NM_WITH_A: [[#%x, P_ADDR:]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_A: [[#%x, A_ADDR:]] (__TEXT,__const) external _strong_a
+# NM_WITH_A: [[#A_ADDR]] (__TEXT,__const) external _weak_a
+# NM_WITH_A: [[#P_ADDR]] (__TEXT,__const) weak external _weak_b
# --- Addresses of _placeholder_int and _strong_a must not match.
-# NM_WITH_A_BAD: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
-# NM_WITH_A_BAD-NOT: [[P_ADDR]] (__TEXT,__const) external _strong_a
+# NM_WITH_A_BAD: [[#%x, P_ADDR:]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_A_BAD-NOT: [[#P_ADDR]] (__TEXT,__const) external _strong_a
# --- Test Case 3: Override weak_b
# RUN: %lld %t/weak.o %t/strong_b.o -o %t/bin/with_b -e _s
# RUN: llvm-nm -am %t/bin/with_b | FileCheck --check-prefix=NM_WITH_B %s
# RUN: llvm-nm -am %t/bin/with_b | FileCheck --check-prefix=NM_WITH_B_BAD %s
-# NM_WITH_B: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
-# NM_WITH_B: [[B_ADDR:[0-9a-f]+]] (__TEXT,__const) external _strong_b
-# NM_WITH_B: [[P_ADDR]] (__TEXT,__const) weak external _weak_a
-# NM_WITH_B: [[B_ADDR]] (__TEXT,__const) external _weak_b
+# NM_WITH_B: [[#%x, P_ADDR:]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_B: [[#%x, B_ADDR:]] (__TEXT,__const) external _strong_b
+# NM_WITH_B: [[#P_ADDR]] (__TEXT,__const) weak external _weak_a
+# NM_WITH_B: [[#B_ADDR]] (__TEXT,__const) external _weak_b
# --- Addresses of _placeholder_int and _strong_a must not match.
-# NM_WITH_B_BAD: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
-# NM_WITH_B_BAD-NOT: [[P_ADDR]] (__TEXT,__const) external _strong_b
+# NM_WITH_B_BAD: [[#%x, P_ADDR:]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_B_BAD-NOT: [[#P_ADDR]] (__TEXT,__const) external _strong_b
# --- Test Case 4: Override weak_a and weak_b
# RUN: %lld %t/weak.o %t/strong_a.o %t/strong_b.o -o %t/bin/with_ab -e _s
# RUN: llvm-nm -am %t/bin/with_ab | FileCheck --check-prefix=NM_WITH_AB %s
# RUN: llvm-nm -am %t/bin/with_ab | FileCheck --check-prefix=NM_WITH_AB_BAD %s
-# NM_WITH_AB: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
-# NM_WITH_AB: [[A_ADDR:[0-9a-f]+]] (__TEXT,__const) external _strong_a
-# NM_WITH_AB: [[B_ADDR:[0-9a-f]+]] (__TEXT,__const) external _strong_b
-# NM_WITH_AB: [[A_ADDR]] (__TEXT,__const) external _weak_a
-# NM_WITH_AB: [[B_ADDR]] (__TEXT,__const) external _weak_b
+# NM_WITH_AB: [[#%x, P_ADDR:]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_AB: [[#%x, A_ADDR:]] (__TEXT,__const) external _strong_a
+# NM_WITH_AB: [[#%x, B_ADDR:]] (__TEXT,__const) external _strong_b
+# NM_WITH_AB: [[#A_ADDR]] (__TEXT,__const) external _weak_a
+# NM_WITH_AB: [[#B_ADDR]] (__TEXT,__const) external _weak_b
# --- Addresses of _placeholder_int, _strong_a, and _strong_b must all be distinct
-# NM_WITH_AB_BAD: [[P_ADDR:[0-9a-f]+]] (__TEXT,__const) weak external _placeholder_int
-# NM_WITH_AB_BAD-NOT: [[P_ADDR]] (__TEXT,__const) external _strong_a
-# NM_WITH_AB_BAD-NOT: [[P_ADDR]] (__TEXT,__const) external _strong_b
+# NM_WITH_AB_BAD: [[#%x, P_ADDR:]] (__TEXT,__const) weak external _placeholder_int
+# NM_WITH_AB_BAD-NOT: [[#P_ADDR]] (__TEXT,__const) external _strong_a
+# NM_WITH_AB_BAD-NOT: [[#P_ADDR]] (__TEXT,__const) external _strong_b
#--- weak.s
.section __TEXT,__const
More information about the llvm-commits
mailing list