[lld] [lld][MachO] Keep coincident alt entries with their atom (PR #224466)

Kyungwoo Lee via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 17 16:46:10 PDT 2026


https://github.com/kyulee-com updated https://github.com/llvm/llvm-project/pull/224466

>From 260056b781812902e51a56a71e81067ac0fa0c57 Mon Sep 17 00:00:00 2001
From: Kyungwoo Lee <kyulee at meta.com>
Date: Thu, 17 Sep 2026 14:53:35 -0700
Subject: [PATCH 1/2] [lld][MachO] Keep coincident alt entries with their atom

Mach-O can list a local N_ALT_ENTRY symbol before an ordinary external symbol at the same address. LLD previously created the alt entry against the preceding subsection, then truncated that subsection at the same address when the ordinary symbol created a new atom. This left the alt entry one past the end of the preceding atom instead of aliasing the atom started by the ordinary symbol.

Atom transformations exposed the misassociation: order-file reordering could move the alt entry with the preceding atom so references reached the wrong code, while marking that atom as containing an alt entry suppressed otherwise valid ICF.

Examine each complete equal-address symbol group. A regular symbol makes that address an atom boundary for every coincident symbol, while alt-only groups retain their interior-entry behavior.

Preserve symbol order so the ordinary symbol retains the body size and the coincident alt entry remains a zero-sized alias, matching Apple ld-prime size attribution. Add regression coverage for order-file reordering and ICF.
---
 lld/MachO/InputFiles.cpp              | 24 +++++++-
 lld/test/MachO/coincident-alt-entry.s | 83 +++++++++++++++++++++++++++
 2 files changed, 104 insertions(+), 3 deletions(-)
 create mode 100644 lld/test/MachO/coincident-alt-entry.s

diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 8faac4b0614a55..7e41e171e3be4c 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -909,9 +909,24 @@ void ObjFile::parseSymbols(ArrayRef<typename LP::section> sectionHeaders,
         return !(nList[lhs].n_desc & N_WEAK_DEF) && (nList[rhs].n_desc & N_WEAK_DEF);
       return nList[lhs].n_value < nList[rhs].n_value;
     });
+    size_t sameAddressGroupEnd = 0;
+    bool sameAddressHasRegularSymbol = false;
     for (size_t j = 0; j < symbolIndices.size(); ++j) {
       const uint32_t symIndex = symbolIndices[j];
       const NList &sym = nList[symIndex];
+      // An ordinary symbol establishes an atom boundary for every symbol at
+      // its address, even when a local alt entry precedes it in the nlist.
+      if (j == sameAddressGroupEnd) {
+        sameAddressHasRegularSymbol = false;
+        while (sameAddressGroupEnd < symbolIndices.size()) {
+          const NList &sameAddressSym =
+              nList[symbolIndices[sameAddressGroupEnd]];
+          if (sameAddressSym.n_value != sym.n_value)
+            break;
+          sameAddressHasRegularSymbol |= !(sameAddressSym.n_desc & N_ALT_ENTRY);
+          ++sameAddressGroupEnd;
+        }
+      }
       StringRef name = getSymName(sym);
       Subsection &subsec = subsections.back();
       InputSection *isec = subsec.isec;
@@ -922,15 +937,18 @@ void ObjFile::parseSymbols(ArrayRef<typename LP::section> sectionHeaders,
           j + 1 < symbolIndices.size()
               ? nList[symbolIndices[j + 1]].n_value - sym.n_value
               : isec->data.size() - symbolOffset;
+      const bool isInteriorAltEntry =
+          (sym.n_desc & N_ALT_ENTRY) && !sameAddressHasRegularSymbol;
       // There are 4 cases where we do not need to create a new subsection:
       //   1. If the input file does not use subsections-via-symbols.
       //   2. Multiple symbols at the same address only induce one subsection.
       //      (The symbolOffset == 0 check covers both this case as well as
       //      the first loop iteration.)
-      //   3. Alternative entry points do not induce new subsections.
+      //   3. Alternative entry points without a coincident ordinary symbol do
+      //      not induce new subsections.
       //   4. If we have a literal section (e.g. __cstring and __literal4).
-      if (!subsectionsViaSymbols || symbolOffset == 0 ||
-          sym.n_desc & N_ALT_ENTRY || !isa<ConcatInputSection>(isec)) {
+      if (!subsectionsViaSymbols || symbolOffset == 0 || isInteriorAltEntry ||
+          !isa<ConcatInputSection>(isec)) {
         isec->hasAltEntry = symbolOffset != 0;
         symbols[symIndex] = createDefined(sym, name, isec, symbolOffset,
                                           symbolSize, forceHidden);
diff --git a/lld/test/MachO/coincident-alt-entry.s b/lld/test/MachO/coincident-alt-entry.s
new file mode 100644
index 00000000000000..4cd1ca88e707e2
--- /dev/null
+++ b/lld/test/MachO/coincident-alt-entry.s
@@ -0,0 +1,83 @@
+# REQUIRES: aarch64
+
+## A coincident alt entry belongs to the atom started by the ordinary symbol
+## at the same address, even when the local alt entry precedes the external
+## symbol in the Mach-O symbol table.
+
+# RUN: split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/input.s -o %t/input.o
+# RUN: %lld -arch arm64 -dylib -platform_version macos 13.0 13.0 \
+# RUN:   -order_file %t/order.txt -map %t/map %t/input.o -o %t/out
+# RUN: llvm-objdump --syms %t/out | FileCheck %s --check-prefix=SYMS
+# RUN: llvm-objdump -d --no-show-raw-insn %t/out | \
+# RUN:   FileCheck %s --check-prefix=DIS
+# RUN: FileCheck %s --check-prefix=MAP --input-file=%t/map
+# RUN: %lld -arch arm64 -dylib -platform_version macos 13.0 13.0 \
+# RUN:   --icf=all %t/input.o -o %t/icf-out
+# RUN: llvm-objdump --syms %t/icf-out | FileCheck %s --check-prefix=ICF
+
+# SYMS-DAG: [[TARGET:[0-9a-f]+]] l     F __TEXT,__text target.alt
+# SYMS-DAG: [[TARGET]] g     F __TEXT,__text _target
+
+# DIS-LABEL: <{{(_target|target[.]alt)}}>:
+# DIS:         mov
+# DIS:         ret
+# DIS-LABEL: <_main>:
+# DIS-NEXT:    b {{.*}} <{{(_target|target[.]alt)}}>
+# DIS-LABEL: <_previous>:
+# DIS-NEXT:    ret
+
+## Listing interior.alt before its atom's defining symbol must move the whole
+## atom. If the alt entry split the atom, these labels would appear reversed.
+# DIS-LABEL: <_interior_owner>:
+# DIS:         mov
+# DIS-LABEL: <interior.alt>:
+# DIS-NEXT:    ret
+
+## The ordinary symbol carries the body size; the coincident alt entry is a
+## zero-sized alias.
+# MAP:      0x[[TARGET_ADDR:[0-9A-F]+]]	0x00000000	{{.*}} target.alt
+# MAP-NEXT: 0x[[TARGET_ADDR]]	0x00000008	{{.*}} _target
+
+## The coincident alt entry must not mark _previous as containing an interior
+## alt entry, so _previous remains eligible to fold with _fold_candidate.
+# ICF-DAG: [[FOLD:[0-9a-f]+]] l     F __TEXT,__text _previous
+# ICF-DAG: [[FOLD]] l     F __TEXT,__text _fold_candidate
+
+#--- order.txt
+_target
+_main
+_previous
+interior.alt
+_interior_owner
+
+#--- input.s
+.subsections_via_symbols
+.text
+
+.p2align 2
+## Put the coincident symbols at a nonzero section offset. At offset zero,
+## symbolOffset == 0 would bypass the behavior under test.
+_previous:
+  ret
+
+.alt_entry target.alt
+target.alt:
+.globl _target
+_target:
+  mov w0, #42
+  ret
+
+.globl _main
+_main:
+  b target.alt
+
+.globl _interior_owner
+_interior_owner:
+  mov w0, #7
+.alt_entry interior.alt
+interior.alt:
+  ret
+
+_fold_candidate:
+  ret

>From 2560cf947a48eedb56ea7bb8117745a49f53ca61 Mon Sep 17 00:00:00 2001
From: Kyungwoo Lee <kyulee at meta.com>
Date: Thu, 17 Sep 2026 16:45:33 -0700
Subject: [PATCH 2/2] Address DataCorrupted's comment

---
 lld/MachO/InputFiles.cpp | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 7e41e171e3be4c..abf7cf4e01b62d 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -909,22 +909,21 @@ void ObjFile::parseSymbols(ArrayRef<typename LP::section> sectionHeaders,
         return !(nList[lhs].n_desc & N_WEAK_DEF) && (nList[rhs].n_desc & N_WEAK_DEF);
       return nList[lhs].n_value < nList[rhs].n_value;
     });
-    size_t sameAddressGroupEnd = 0;
-    bool sameAddressHasRegularSymbol = false;
+    size_t sameAddrGroupIdx = 0;
+    bool sameAddrHasRegularSymbol = false;
     for (size_t j = 0; j < symbolIndices.size(); ++j) {
       const uint32_t symIndex = symbolIndices[j];
       const NList &sym = nList[symIndex];
       // An ordinary symbol establishes an atom boundary for every symbol at
       // its address, even when a local alt entry precedes it in the nlist.
-      if (j == sameAddressGroupEnd) {
-        sameAddressHasRegularSymbol = false;
-        while (sameAddressGroupEnd < symbolIndices.size()) {
-          const NList &sameAddressSym =
-              nList[symbolIndices[sameAddressGroupEnd]];
-          if (sameAddressSym.n_value != sym.n_value)
+      if (j == sameAddrGroupIdx) {
+        sameAddrHasRegularSymbol = false;
+        while (sameAddrGroupIdx < symbolIndices.size()) {
+          const NList &sameAddrSym = nList[symbolIndices[sameAddrGroupIdx]];
+          if (sameAddrSym.n_value != sym.n_value)
             break;
-          sameAddressHasRegularSymbol |= !(sameAddressSym.n_desc & N_ALT_ENTRY);
-          ++sameAddressGroupEnd;
+          sameAddrHasRegularSymbol |= !(sameAddrSym.n_desc & N_ALT_ENTRY);
+          ++sameAddrGroupIdx;
         }
       }
       StringRef name = getSymName(sym);
@@ -938,7 +937,7 @@ void ObjFile::parseSymbols(ArrayRef<typename LP::section> sectionHeaders,
               ? nList[symbolIndices[j + 1]].n_value - sym.n_value
               : isec->data.size() - symbolOffset;
       const bool isInteriorAltEntry =
-          (sym.n_desc & N_ALT_ENTRY) && !sameAddressHasRegularSymbol;
+          (sym.n_desc & N_ALT_ENTRY) && !sameAddrHasRegularSymbol;
       // There are 4 cases where we do not need to create a new subsection:
       //   1. If the input file does not use subsections-via-symbols.
       //   2. Multiple symbols at the same address only induce one subsection.



More information about the llvm-commits mailing list