Repository: llvm/llvm-project
Pull request #134342: [lld] Merge equivalent symbols found during ICF
Author: login: pranavk
Labels: name: lld
name: lld:ELF
Created: 2025-04-04T03:46:00Z | Merged: 2025-04-21T22:17:03Z

Fixes a correctness issue for AArch64 when ADRP and LDR instructions are outlined in separate sections and sections are fed to ICF for deduplication.

See test case (based on https://github.com/llvm/llvm-project/issues/129122) for details. All rodata.* sections are folded into a single section with ICF. This leads to all f2_* function sections getting folded into one (as their relocation target symbols g* belong to .rodata.g* sections that have already been folded into one). Since relocations still refer original g* symbols, we end up creating duplicate GOT entry for all such symbols. This PR addresses that by tracking such folded symbols and create one GOT entry for all such symbols.

Fixes https://github.com/llvm/llvm-project/issues/129122

Co-authored by: @jyknight 


Diff:
diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp
index 1cdcf6be9d8a9..849f6bdd445f9 100644
--- a/lld/ELF/ICF.cpp
+++ b/lld/ELF/ICF.cpp
@@ -80,6 +80,7 @@
 #include "SymbolTable.h"
 #include "Symbols.h"
 #include "SyntheticSections.h"
+#include "llvm/ADT/EquivalenceClasses.h"
 #include "llvm/BinaryFormat/ELF.h"
 #include "llvm/Object/ELF.h"
 #include "llvm/Support/Parallel.h"
@@ -333,6 +334,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
+                                      Relocs<RelTy> relocs) {
+  SmallVector<Symbol *> syms;
+  for (auto ri = relocs.begin(), re = relocs.end(); ri != re; ++ri) {
+    Symbol &sym = sec->file->getRelocTargetSym(*ri);
+    syms.push_back(&sym);
+  }
+  return syms;
+}
+
+template <class ELFT>
+static SmallVector<Symbol *> getRelocTargetSyms(const InputSection *sec) {
+  const RelsOrRelas<ELFT> rel = sec->template relsOrRelas<ELFT>();
+  if (rel.areRelocsCrel())
+    return getReloc(sec, rel.crels);
+  if (rel.areRelocsRel())
+    return getReloc(sec, rel.rels);
+
+  return getReloc(sec, rel.relas);
+}
+
 // Compare two lists of relocations. Returns true if all pairs of
 // relocations point to the same section in terms of ICF.
 template <class ELFT>
@@ -535,14 +558,28 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  EquivalenceClasses<Symbol *> symbolEquivalence;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF.
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
+            !replacedSyms[i]->isGlobal())
+          continue;
+        symbolEquivalence.unionSets(syms[i], replacedSyms[i]);
+      }
 
       // At this point we know sections merged are fully identical and hence
       // we want to remove duplicate implicit dependencies such as link order
@@ -561,11 +598,26 @@ template <class ELFT> void ICF<ELFT>::run() {
           d->folded = true;
         }
   };
-  for (Symbol *sym : ctx.symtab->getSymbols())
+  for (Symbol *sym : ctx.symtab->getSymbols()) {
     fold(sym);
+    auto it = symbolEquivalence.findLeader(sym);
+    if (it != symbolEquivalence.member_end() && *it != sym) {
+      print() << "redirecting '" << sym->getName() << "' in symtab to '"
+              << (*it)->getName() << "'";
+      ctx.symtab->redirect(sym, *it);
+    }
+  }
   parallelForEach(ctx.objectFiles, [&](ELFFileBase *file) {
     for (Symbol *sym : file->getLocalSymbols())
       fold(sym);
+    for (Symbol *&sym : file->getMutableGlobalSymbols()) {
+      auto it = symbolEquivalence.findLeader(sym);
+      if (it != symbolEquivalence.member_end() && *it != sym) {
+        print() << "redirecting '" << sym->getName() << "' to '"
+                << (*it)->getName() << "'";
+        sym = *it;
+      }
+    }
   });
 
   // InputSectionDescription::sections is populated by processSectionCommands().
diff --git a/lld/ELF/SymbolTable.cpp b/lld/ELF/SymbolTable.cpp
index b8a70d4e898fc..91e47e15b01a4 100644
--- a/lld/ELF/SymbolTable.cpp
+++ b/lld/ELF/SymbolTable.cpp
@@ -29,6 +29,12 @@ using namespace llvm::ELF;
 using namespace lld;
 using namespace lld::elf;
 
+void SymbolTable::redirect(Symbol *from, Symbol *to) {
+  int &fromIdx = symMap[CachedHashStringRef(from->getName())];
+  const int toIdx = symMap[CachedHashStringRef(to->getName())];
+  fromIdx = toIdx;
+}
+
 void SymbolTable::wrap(Symbol *sym, Symbol *real, Symbol *wrap) {
   // Redirect __real_foo to the original foo and foo to the original __wrap_foo.
   int &idx1 = symMap[CachedHashStringRef(sym->getName())];
diff --git a/lld/ELF/SymbolTable.h b/lld/ELF/SymbolTable.h
index d6443742f7baa..e3a39bac85f97 100644
--- a/lld/ELF/SymbolTable.h
+++ b/lld/ELF/SymbolTable.h
@@ -41,6 +41,7 @@ class SymbolTable {
   SymbolTable(Ctx &ctx) : ctx(ctx) {}
   ArrayRef<Symbol *> getSymbols() const { return symVector; }
 
+  void redirect(Symbol *from, Symbol *to);
   void wrap(Symbol *sym, Symbol *real, Symbol *wrap);
 
   Symbol *insert(StringRef name);
diff --git a/lld/test/ELF/aarch64-got-merging-icf.s b/lld/test/ELF/aarch64-got-merging-icf.s
new file mode 100644
index 0000000000000..01d7bdfd75185
--- /dev/null
+++ b/lld/test/ELF/aarch64-got-merging-icf.s
@@ -0,0 +1,95 @@
+## REQUIRES: aarch64
+## Check that symbols that ICF assumes to be the same get a single GOT entry
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: llvm-mc -filetype=obj -crel -triple=aarch64 %s -o %tcrel
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: ld.lld %tcrel -o %tcrel2 --icf=all
+
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+# RUN: llvm-objdump --section-headers %tcrel2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
+# RUN: ld.lld -shared %tcrel -o %tcrel3 --icf=all
+
+# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
+# RUN: llvm-objdump --section-headers %tcrel3 | FileCheck %s --check-prefix=DSO
+
+## All global g* symbols should merge into a single GOT entry while non-global
+## gets its own GOT entry.
+# EXE: {{.*}}.got 00000010{{.*}}
+
+## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
+# DSO: {{.*}}.got 00000028{{.*}}
+
+.addrsig
+
+callee:
+ret
+
+.macro f, index, isglobal
+
+# (Kept unique) first instruction of the GOT code sequence
+.section .text.f1_\index,"ax",@progbits
+f1_\index:
+adrp x0, :got:g\index
+mov x1, #\index
+b f2_\index
+
+# Folded, second instruction of the GOT code sequence
+.section .text.f2_\index,"ax",@progbits
+f2_\index:
+ldr x0, [x0, :got_lo12:g\index] 
+b callee
+
+# Folded
+.ifnb \isglobal
+.globl g\index
+.endif
+.section .rodata.g\index,"a",@progbits
+g_\index:
+.long 111
+.long 122
+
+g\index:
+.byte 123
+
+.section .text._start,"ax",@progbits
+bl f1_\index
+
+.endm
+
+## Another set of sections merging: g1 <- g2. Linker should be able to
+## resolve both g1 and g2 to g0 based on ICF on previous sections.
+
+.section .text.t1_0,"ax",@progbits
+t1_0:
+adrp x2, :got:g1
+mov x3, #1
+b t2_0
+
+.section .text.t2_0,"ax",@progbits
+t2_0:
+ldr x2, [x2, :got_lo12:g1]
+b callee
+
+.section .text.t1_1,"ax",@progbits
+t1_1:
+adrp x2, :got:g2
+mov x3, #2
+b t2_1
+
+.section .text.t2_1,"ax",@progbits
+t2_1:
+ldr x2, [x2, :got_lo12:g2]
+b callee
+
+.section .text._start,"ax",@progbits
+.globl _start
+_start:
+
+f 0 1
+f 1 1
+f 2 1
+f 3 1
+f 4
diff --git a/lld/test/ELF/icf-preemptible.s b/lld/test/ELF/icf-preemptible.s
index 4bd1eca438b19..9352493600695 100644
--- a/lld/test/ELF/icf-preemptible.s
+++ b/lld/test/ELF/icf-preemptible.s
@@ -17,6 +17,12 @@
 # EXE-NEXT: selected section {{.*}}:(.text.h1)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.h2)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.h3)
+# EXE-NEXT: redirecting 'f2' in symtab to 'f1'
+# EXE-NEXT: redirecting 'g2' in symtab to 'g1'
+# EXE-NEXT: redirecting 'g3' in symtab to 'g1'
+# EXE-NEXT: redirecting 'f2' to 'f1'
+# EXE-NEXT: redirecting 'g2' to 'g1'
+# EXE-NEXT: redirecting 'g3' to 'g1'
 # EXE-NOT:  {{.}}
 
 ## Definitions are preemptible in a DSO. Only leaf functions can be folded.


Reviews:
user: login: MaskRay
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-04T03:54:54Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T04:00:47Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T04:03:07Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T04:06:30Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T23:23:15Z
user: login: smithp35
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T10:39:08Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T22:05:51Z
user: login: MaskRay
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-12T21:10:35Z
user: login: MaskRay
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-12T21:11:42Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T21:25:57Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T23:17:36Z
user: login: MaskRay
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T07:19:36Z
user: login: jyknight
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T09:47:43Z
user: login: jyknight
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T10:23:47Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:29:52Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:39:01Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:58:09Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T23:00:42Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T23:01:38Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T23:09:34Z
user: login: jyknight
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-16T07:34:33Z
user: login: MaskRay
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T16:10:27Z
user: login: MaskRay
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T16:13:45Z
user: login: MaskRay
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T16:14:29Z
user: login: MaskRay
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T16:15:30Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T20:22:41Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T20:23:44Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T20:24:58Z
user: login: MaskRay
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-18T05:47:38Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T22:54:09Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T22:28:47Z
user: login: pranavk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T02:22:04Z

Review comments:
diff_hunk: @@ -0,0 +1,68 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
+# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
+
+## All .rodata.* sections should merge into a single GOT entry
+# EXE: {{.*}}.got 00000008{{.*}}
+
+## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
+# DSO: {{.*}}.got 00000020{{.*}}
+
+.addrsig
+
+callee:
+ret
+
+.section .rodata.dummy1,"a",@progbits
+sym1:
+.long 111
+.long 122
+.byte 123
+
+.section .rodata.dummy2,"a",@progbits
+sym2:
+.long 111
+.long 122
+sym3:
+.byte 123
+
+.macro f, index
+
+.section .text.f1_\index,"ax",@progbits
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: MaskRay
body: Retain my code comment in https://github.com/llvm/llvm-project/issues/129122#issuecomment-2739009435
created_at: 2025-04-04T03:53:14Z
author_association: MEMBER
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: MaskRay
body: I don't think we should scan relocations of folded sections. James' original idea of replacing the InputFile symbols is better.
created_at: 2025-04-04T03:54:42Z
author_association: MEMBER
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: pranavk
body: We are still replacing the input file symbols as suggested in the original comment by James. 

The reason I am scanning the relocation here is to make code thread-safe. Original diff by James is not thread-safe as `variableEq` is called in parallel for larger links. So annotating symbols as equivalent in that function is going to be problematic. 
created_at: 2025-04-04T04:00:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -561,11 +604,17 @@ template <class ELFT> void ICF<ELFT>::run() {
           d->folded = true;
         }
   };
-  for (Symbol *sym : ctx.symtab->getSymbols())
+  for (Symbol *sym : ctx.symtab->getSymbols()) {
     fold(sym);
+    if (Symbol *s = symbolMap.lookup(sym))
+      ctx.symtab->redirect(sym, s);
+  }
   parallelForEach(ctx.objectFiles, [&](ELFFileBase *file) {
     for (Symbol *sym : file->getLocalSymbols())
       fold(sym);
+    for (Symbol *&sym : file->getMutableGlobalSymbols())
+      if (Symbol *s = symbolMap.lookup(sym))
path: lld/ELF/ICF.cpp
user: login: pranavk
body: @MaskRay Input file symbols are replaced here.
In addition, we also replace/redirect symbols in symbol table few lines above.
created_at: 2025-04-04T04:03:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
+# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
+
+## All .rodata.* sections should merge into a single GOT entry
+# EXE: {{.*}}.got 00000008{{.*}}
+
+## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
+# DSO: {{.*}}.got 00000020{{.*}}
+
+.addrsig
+
+callee:
+ret
+
+.section .rodata.dummy1,"a",@progbits
+sym1:
+.long 111
+.long 122
+.byte 123
+
+.section .rodata.dummy2,"a",@progbits
+sym2:
+.long 111
+.long 122
+sym3:
+.byte 123
+
+.macro f, index
+
+.section .text.f1_\index,"ax",@progbits
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: pranavk
body: Done.
created_at: 2025-04-04T04:06:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: pranavk
body: Any thoughts on this?
created_at: 2025-04-09T23:23:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: smithp35
body: IIUC this is because of:
```
  forEachClass([&](size_t begin, size_t end) {
    segregate(begin, end, eqClassBase, true);
  });
```
Just thinking if there's a way of making James' `std::unordered_map<Symbol*, Symbol *> symbolEquivalence` instanced (sharded may be the better term) per partition. Then we wouldn't need to worry about concurrent updates.

Once the parallel part of segregate was finished the symbolEquivalence instances for each class could be merged. While I've not done the analysis I'm wondering if the existing split into equivalence classes might make the merging fairly trivial. For example if the symbols defined in each equivalence class are not referred to from outside the equivalence class.

That might get you something closer to the original.
created_at: 2025-04-11T10:39:07Z
author_association: COLLABORATOR
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: pranavk
body: That would complicate things vs the current version of this PR where things are kept simple and thread-safe, but I can try taking a look if there's a way to do this without increasing the complexity (we rejected the previous version of the PR due to complexity)

I am also not sure if that's what Fangrui is concerned about in the original comment. 
created_at: 2025-04-11T22:05:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: MaskRay
body: I was concerned about the extra relocation scanning (we should minimize REL/RELA/CREL dispatch code paths). However, it seems unavoidable after taking a closer took.

Around line 581, `symbolMap.insert(std::make_pair(replacedSyms[i], syms[i]));` inside `forEachClassRange` (parallel) looks like unsafe concurrent updates of the unordered map.
created_at: 2025-04-12T21:10:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,71 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
+# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
+
+## All .rodata.* sections should merge into a single GOT entry
+# EXE: {{.*}}.got 00000008{{.*}}
+
+## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
+# DSO: {{.*}}.got 00000020{{.*}}
+
+.addrsig
+
+callee:
+ret
+
+.section .rodata.dummy1,"a",@progbits
+sym1:
+.long 111
+.long 122
+.byte 123
+
+.section .rodata.dummy2,"a",@progbits
+sym2:
+.long 111
+.long 122
+sym3:
+.byte 123
+
+.macro f, index
+
+# (Kept unique) first instruction of the GOT code sequence
+.section .text.f1_\index,"ax",@progbits
+f1_\index:
+adrp x0, :got:g\index
+mov x1, #\index
+b f2_\index
+
+# Folded, second instruction of the GOT code sequence
+.section .text.f2_\index,"ax",@progbits
+f2_\index:
+ldr x0, [x0, :got_lo12:g\index] 
+b callee
+
+# Folded
+.globl g\index
+.section .rodata.g\index,"a",@progbits
+g_\index:
+.long 111
+.long 122
+
+g\index:
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: MaskRay
body: It seems that we need to duplicate the assembly to test the case when `g\index` symbols are local.
created_at: 2025-04-12T21:11:42Z
author_association: MEMBER
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: pranavk
body: > Around line 581, symbolMap.insert(std::make_pair(replacedSyms[i], syms[i])); inside forEachClassRange (parallel) looks like unsafe concurrent updates of the unordered map.

`forEachClassRange` is not parallel as far as I can see. `forEachClass` is parallel which is why I couldn't do this inside of `variablesEq`. So there should be no concurrent update to the map.
created_at: 2025-04-14T21:25:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
+# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
+
+## All .rodata.* sections should merge into a single GOT entry
+# EXE: {{.*}}.got 00000008{{.*}}
+
+## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
+# DSO: {{.*}}.got 00000020{{.*}}
+
+.addrsig
+
+callee:
+ret
+
+.section .rodata.dummy1,"a",@progbits
+sym1:
+.long 111
+.long 122
+.byte 123
+
+.section .rodata.dummy2,"a",@progbits
+sym2:
+.long 111
+.long 122
+sym3:
+.byte 123
+
+.macro f, index
+
+# (Kept unique) first instruction of the GOT code sequence
+.section .text.f1_\index,"ax",@progbits
+f1_\index:
+adrp x0, :got:g\index
+mov x1, #\index
+b f2_\index
+
+# Folded, second instruction of the GOT code sequence
+.section .text.f2_\index,"ax",@progbits
+f2_\index:
+ldr x0, [x0, :got_lo12:g\index] 
+b callee
+
+# Folded
+.globl g\index
+.section .rodata.g\index,"a",@progbits
+g_\index:
+.long 111
+.long 122
+
+g\index:
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: pranavk
body: I modified the test macro to include a local symbol which won't be merged with rest of the symbols. 
created_at: 2025-04-14T23:17:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,60 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
+# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
+
+## All .rodata.* sections should merge into a single GOT entry
+# EXE: {{.*}}.got 00000010{{.*}}
+
+## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
+# DSO: {{.*}}.got 00000020{{.*}}
+
+.addrsig
+
+callee:
+ret
+
+.macro f, index, isglobal
+
+# (Kept unique) first instruction of the GOT code sequence
+.section .text.f1_\index,"ax",@progbits
+f1_\index:
+adrp x0, :got:g\index
+mov x1, #\index
+b f2_\index
+
+# Folded, second instruction of the GOT code sequence
+.section .text.f2_\index,"ax",@progbits
+f2_\index:
+ldr x0, [x0, :got_lo12:g\index] 
+b callee
+
+# Folded
+.ifnb \isglobal
+.globl g\index
+.endif
+.section .rodata.g\index,"a",@progbits
+g_\index:
+.long 111
+.long 122
+
+g\index:
+.byte 123
+
+.section .text._start,"ax",@progbits
+bl f1_\index
+
+.endm
+
+.section .text._start,"ax",@progbits
+.globl _start
+_start:
+
+f 0 1
+f 1 1
+f 2 1
+f 3
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: MaskRay
body: missing \n at file end
created_at: 2025-04-15T07:14:11Z
author_association: MEMBER
diff_hunk: @@ -29,6 +29,13 @@ using namespace llvm::ELF;
 using namespace lld;
 using namespace lld::elf;
 
+void SymbolTable::redirect(Symbol *from, Symbol *to) {
+  int &fromIdx = symMap[CachedHashStringRef(from->getName())];
+  const int toIdx = symMap[CachedHashStringRef(to->getName())];
+
path: lld/ELF/SymbolTable.cpp
user: login: MaskRay
body: Nit: unneeded blank line

created_at: 2025-04-15T07:16:02Z
author_association: MEMBER
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: MaskRay
body: > > Around line 581, symbolMap.insert(std::make_pair(replacedSyms[i], syms[i])); inside forEachClassRange (parallel) looks like unsafe concurrent updates of the unordered map.
> 
> `forEachClassRange` is not parallel as far as I can see. `forEachClass` is parallel which is why I couldn't do this inside of `variablesEq`. So there should be no concurrent update to the map.

OK, I see. I am concerned that this introduces an implicit restriction on making the loop parallel, but I don't have good alternative suggestion yet.
created_at: 2025-04-15T07:18:38Z
author_association: MEMBER
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: jyknight
body: After this PR goes in can you do an NFC follow-up to rename the loops which are parallel to actually have a name that starts with "parallel"? (That this naming convention is only inconsistently used is why I made the thread -safety bug in the initial. I did not read the implementation to discover that forEachClass is parallel!).
created_at: 2025-04-15T09:47:43Z
author_association: MEMBER
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
+            !replacedSyms[i]->isGlobal())
+          continue;
+        auto [it, inserted] =
path: lld/ELF/ICF.cpp
user: login: jyknight
body: I think this code doesn't resolve transitively to a _single_ canonical symbol. (My original proposed code also had this bug)

For example:

- functions f1, f2, which have relocs pointing, respectively, to symbols s1, s2 (which are equivalent), ICF will merge f2 into f1 and this will mark s2 as being replaced by s1.
- functions g2, g3 which have relocs pointing at symbols s2, s3, ICF will merge g3 into g2, and mark s3 as being replaced by s2.

But, nothing follows the chain s3 -> s2 -> s1, so, in the end, we have an output file which still has references to both s1 and s2, despite that we are supposed to replace s2 with s1. So I think this can still cause a miscompile.

created_at: 2025-04-15T10:09:45Z
author_association: MEMBER
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
path: lld/ELF/ICF.cpp
user: login: jyknight
body: Since you're checking isGlobal only here, and not in variableEq, doesn't that mean that any non-trivial relocation (such as GOT) which references a local symbol will remain broken? Maybe ELF producers don't _need_ to create a GOT reloc pointing to a local symbol, but it's not invalid to do so.

We can't (at least not right now) get rid of the isGlobal check, because we cannot actually canonicalize local symbols across TUs, since code doing loops over `getLocalSymbols()` for each file in `ctx.objectFiles` expects to see no duplicates. (which was a bug in my original version).

So I think we need to add a check in variableEq, `if((!sa.isGlobal() || !sb.isGlobal()) && !ra->"isTrivialRelocType"()) return false;`. (I made up "isTrivialRelocType"; do we have a relevant predicate already that would determine if a relocation has semantics beyond just the offset?)
created_at: 2025-04-15T10:12:10Z
author_association: MEMBER
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
path: lld/ELF/ICF.cpp
user: login: pranavk
body: Yes, as I mentioned in my comment earlier, I plan to do that in a separate PR as it's going to change the behavior of two sections getting ICF-merged (which also changes a `icf-ineligible.s` test as you noticed earlier). 
created_at: 2025-04-15T17:29:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: pranavk
body: yes, that's the plan.
created_at: 2025-04-15T17:39:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
+            !replacedSyms[i]->isGlobal())
+          continue;
+        auto [it, inserted] =
path: lld/ELF/ICF.cpp
user: login: pranavk
body: Ah, I recall discussing this offline but forgot to take care of it in the implementation. Let me see what I can do here.
created_at: 2025-04-15T17:58:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -29,6 +29,13 @@ using namespace llvm::ELF;
 using namespace lld;
 using namespace lld::elf;
 
+void SymbolTable::redirect(Symbol *from, Symbol *to) {
+  int &fromIdx = symMap[CachedHashStringRef(from->getName())];
+  const int toIdx = symMap[CachedHashStringRef(to->getName())];
+
path: lld/ELF/SymbolTable.cpp
user: login: pranavk
body: done
created_at: 2025-04-15T23:00:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
+            !replacedSyms[i]->isGlobal())
+          continue;
+        auto [it, inserted] =
path: lld/ELF/ICF.cpp
user: login: pranavk
body: I modified the patch to use union-find that is going to be able to track this better and handle these cases. PTAL.
created_at: 2025-04-15T23:01:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: pranavk
body: > OK, I see. I am concerned that this introduces an implicit restriction on making the loop parallel, but I don't have good alternative suggestion yet.

That is right. I can think of something but it seems pretty hard to do atm.
created_at: 2025-04-15T23:09:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
path: lld/ELF/ICF.cpp
user: login: jyknight
body: OK with me to do in another PR, as long as we're agreed the issue hasn't fully been fixed until that's also done. 

The change doesn't need to affect icf-ineligible, though, since that test uses only trivial relocations, where we don't need to replace the symbols.
created_at: 2025-04-16T07:30:15Z
author_association: MEMBER
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
+            !replacedSyms[i]->isGlobal())
+          continue;
+        auto [it, inserted] =
path: lld/ELF/ICF.cpp
user: login: jyknight
body: Oh, nice! I didn't know we had that data structure. I couldn't really tell by inspection, but I assume the new test code you added would've failed before this change to use EquivalenceClasses?
created_at: 2025-04-16T07:31:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,85 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: MaskRay
body: Use  `llvm-mc -crel` to test SHT_CREL as well
created_at: 2025-04-16T16:10:27Z
author_association: MEMBER
diff_hunk: @@ -561,11 +598,25 @@ template <class ELFT> void ICF<ELFT>::run() {
           d->folded = true;
         }
   };
-  for (Symbol *sym : ctx.symtab->getSymbols())
+  for (Symbol *sym : ctx.symtab->getSymbols()) {
     fold(sym);
+    auto it = symbolEquivalence.findLeader(sym);
+    if (it != symbolEquivalence.member_end() && *it != sym) {
+      print() << "Redirecting " << sym->getName() << " to " << (*it)->getName();
path: lld/ELF/ICF.cpp
user: login: MaskRay
body: Use lower-case messages (convention; we follow https://llvm.org/docs/CodingStandards.html#error-and-warning-messages)

In new messages, we quote symbol names like `'sym'`
created_at: 2025-04-16T16:13:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,85 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
+# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
+
+## All .rodata.* sections should merge into a single GOT entry
+# EXE: {{.*}}.got 00000010{{.*}}
+
+## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
+# DSO: {{.*}}.got 00000028{{.*}}
+
+.addrsig
+
+callee:
+ret
+
+.macro f, index, isglobal
+
+# (Kept unique) first instruction of the GOT code sequence
+.section .text.f1_\index,"ax",@progbits
+f1_\index:
+adrp x0, :got:g\index
+mov x1, #\index
+b f2_\index
+
+# Folded, second instruction of the GOT code sequence
+.section .text.f2_\index,"ax",@progbits
+f2_\index:
+ldr x0, [x0, :got_lo12:g\index] 
+b callee
+
+# Folded
+.ifnb \isglobal
+.globl g\index
+.endif
+.section .rodata.g\index,"a",@progbits
+g_\index:
+.long 111
+.long 122
+
+g\index:
+.byte 123
+
+.section .text._start,"ax",@progbits
+bl f1_\index
+
+.endm
+
+# another set of sections merging: g1 <- g2
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: MaskRay
body: Nit: `## Another set of ...`

`##` to make the NON-RUN non-CHECK comment stand out. Should change the comments above as well. It's a convention among new tests.
created_at: 2025-04-16T16:14:29Z
author_association: MEMBER
diff_hunk: @@ -17,7 +17,9 @@
 # EXE-NEXT: selected section {{.*}}:(.text.h1)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.h2)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.h3)
-# EXE-NOT:  {{.}}
+# EXE-NEXT: Redirecting f2 to f1
+# EXE-NEXT: Redirecting g2 to g1
+# EXE-NEXT: Redirecting g3 to g1
path: lld/test/ELF/icf-preemptible.s
user: login: MaskRay
body: Could add `EXE-NOT: {{.}}` to ensure that there is no new line below (to catch bugs like printing `redirecting` multiple times)
created_at: 2025-04-16T16:15:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,85 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: pranavk
body: done (alongwith other comments). 
created_at: 2025-04-16T20:22:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,7 +17,9 @@
 # EXE-NEXT: selected section {{.*}}:(.text.h1)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.h2)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.h3)
-# EXE-NOT:  {{.}}
+# EXE-NEXT: Redirecting f2 to f1
+# EXE-NEXT: Redirecting g2 to g1
+# EXE-NEXT: Redirecting g3 to g1
path: lld/test/ELF/icf-preemptible.s
user: login: pranavk
body: I removed that earlier because we are redirecting these symbols twice -- once in the symtab and again in the ELFfile. I have changed the test and messages to better reflect that. I have also added `EXE-NOT: {{.}}` back in.
created_at: 2025-04-16T20:23:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
+            !replacedSyms[i]->isGlobal())
+          continue;
+        auto [it, inserted] =
path: lld/ELF/ICF.cpp
user: login: pranavk
body: yes, the new test i added would've failed because linker would create separate GOT entries for `g0` and `g1`. After the equivalence classes code, it only adds GOT entry for `g0`.
created_at: 2025-04-16T20:24:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -535,14 +558,28 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  EquivalenceClasses<Symbol *> symbolEquivalence;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
path: lld/ELF/ICF.cpp
user: login: MaskRay
body: In comments, full sentences end with a full stop.
created_at: 2025-04-18T05:45:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,94 @@
+// REQUIRES: aarch64
path: lld/test/ELF/aarch64-got-merging-icf.s
user: login: MaskRay
body: As you've switched to `#`, switch to `# REQUIRES: aarch64` as well.
While here, can you add a file-level comment about the purpose of the test? Use `## `
created_at: 2025-04-18T05:47:32Z
author_association: MEMBER
diff_hunk: @@ -535,14 +558,28 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  EquivalenceClasses<Symbol *> symbolEquivalence;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
path: lld/ELF/ICF.cpp
user: login: pranavk
body: done. thank you for reviewing.
created_at: 2025-04-18T22:54:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
path: lld/ELF/ICF.cpp
user: login: pranavk
body: #136625 to rename this function
created_at: 2025-04-21T22:28:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
path: lld/ELF/ICF.cpp
user: login: pranavk
body: I followed-up here in this PR: #136641
created_at: 2025-04-22T02:22:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: llvmbot
created_at: 2025-04-04T03:46:32Z
body: 
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lld-elf

@llvm/pr-subscribers-lld

Author: Pranav Kant (pranavk)

<details>
<summary>Changes</summary>

Fixes a correctness issue for AArch64 when ADRP and LDR instructions are outlined in separate sections and sections are fed to ICF for deduplication.

See test case (based on https://github.com/llvm/llvm-project/issues/129122) for details. All rodata.* sections are folded into a single section with ICF. This leads to all f2_* function sections getting folded into one (as their relocation target symbols g* belong to .rodata.g* sections that have already been folded into one). Since relocations still refer original g* symbols, we end up creating duplicate GOT entry for all such symbols. This PR addresses that by tracking such folded symbols and create one GOT entry for all such symbols.

Fixes https://github.com/llvm/llvm-project/issues/129122

---
Full diff: https://github.com/llvm/llvm-project/pull/134342.diff


5 Files Affected:

- (modified) lld/ELF/ICF.cpp (+50-1) 
- (modified) lld/ELF/SymbolTable.cpp (+7) 
- (modified) lld/ELF/SymbolTable.h (+1) 
- (added) lld/test/ELF/aarch64-got-merging-icf.s (+68) 
- (modified) lld/test/ELF/icf-preemptible.s (+3) 


``````````diff
diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp
index 1cdcf6be9d8a9..467487e10f310 100644
--- a/lld/ELF/ICF.cpp
+++ b/lld/ELF/ICF.cpp
@@ -333,6 +333,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
              : constantEq(a, ra.relas, b, rb.relas);
 }
 
+template <class RelTy>
+static SmallVector<Symbol *> getReloc(const InputSection *sec,
+                                      Relocs<RelTy> relocs) {
+  SmallVector<Symbol *> syms;
+  for (auto ri = relocs.begin(), re = relocs.end(); ri != re; ++ri) {
+    Symbol &sym = sec->file->getRelocTargetSym(*ri);
+    syms.push_back(&sym);
+  }
+  return syms;
+}
+
+template <class ELFT>
+static SmallVector<Symbol *> getRelocTargetSyms(const InputSection *sec) {
+  const RelsOrRelas<ELFT> rel = sec->template relsOrRelas<ELFT>();
+  if (rel.areRelocsCrel())
+    return getReloc(sec, rel.crels);
+  if (rel.areRelocsRel())
+    return getReloc(sec, rel.rels);
+
+  return getReloc(sec, rel.relas);
+}
+
 // Compare two lists of relocations. Returns true if all pairs of
 // relocations point to the same section in terms of ICF.
 template <class ELFT>
@@ -535,14 +557,35 @@ template <class ELFT> void ICF<ELFT>::run() {
   auto print = [&ctx = ctx]() -> ELFSyncStream {
     return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
   };
+
+  DenseMap<Symbol *, Symbol *> symbolMap;
   // Merge sections by the equivalence class.
+  // Merge symbols identified as equivalent during ICF
   forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
+    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
     for (size_t i = begin + 1; i < end; ++i) {
       print() << "  removing identical section " << sections[i];
       sections[begin]->replace(sections[i]);
+      SmallVector<Symbol *> replacedSyms =
+          getRelocTargetSyms<ELFT>(sections[i]);
+      assert(syms.size() == replacedSyms.size() &&
+             "Should have same number of syms!");
+      for (size_t i = 0; i < syms.size(); i++) {
+        if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
+            !replacedSyms[i]->isGlobal())
+          continue;
+        auto [it, inserted] =
+            symbolMap.insert(std::make_pair(replacedSyms[i], syms[i]));
+        print() << "  selected symbol: " << syms[i]->getName().data()
+                << "; replaced symbol: " << replacedSyms[i]->getName().data();
+        if (!inserted) {
+          print() << "    replacement already exists: "
+                  << it->getSecond()->getName().data();
+        }
+      }
 
       // At this point we know sections merged are fully identical and hence
       // we want to remove duplicate implicit dependencies such as link order
@@ -561,11 +604,17 @@ template <class ELFT> void ICF<ELFT>::run() {
           d->folded = true;
         }
   };
-  for (Symbol *sym : ctx.symtab->getSymbols())
+  for (Symbol *sym : ctx.symtab->getSymbols()) {
     fold(sym);
+    if (Symbol *s = symbolMap.lookup(sym))
+      ctx.symtab->redirect(sym, s);
+  }
   parallelForEach(ctx.objectFiles, [&](ELFFileBase *file) {
     for (Symbol *sym : file->getLocalSymbols())
       fold(sym);
+    for (Symbol *&sym : file->getMutableGlobalSymbols())
+      if (Symbol *s = symbolMap.lookup(sym))
+        sym = s;
   });
 
   // InputSectionDescription::sections is populated by processSectionCommands().
diff --git a/lld/ELF/SymbolTable.cpp b/lld/ELF/SymbolTable.cpp
index b8a70d4e898fc..2199f159692b0 100644
--- a/lld/ELF/SymbolTable.cpp
+++ b/lld/ELF/SymbolTable.cpp
@@ -29,6 +29,13 @@ using namespace llvm::ELF;
 using namespace lld;
 using namespace lld::elf;
 
+void SymbolTable::redirect(Symbol *from, Symbol *to) {
+  int &fromIdx = symMap[CachedHashStringRef(from->getName())];
+  const int toIdx = symMap[CachedHashStringRef(to->getName())];
+
+  fromIdx = toIdx;
+}
+
 void SymbolTable::wrap(Symbol *sym, Symbol *real, Symbol *wrap) {
   // Redirect __real_foo to the original foo and foo to the original __wrap_foo.
   int &idx1 = symMap[CachedHashStringRef(sym->getName())];
diff --git a/lld/ELF/SymbolTable.h b/lld/ELF/SymbolTable.h
index d6443742f7baa..e3a39bac85f97 100644
--- a/lld/ELF/SymbolTable.h
+++ b/lld/ELF/SymbolTable.h
@@ -41,6 +41,7 @@ class SymbolTable {
   SymbolTable(Ctx &ctx) : ctx(ctx) {}
   ArrayRef<Symbol *> getSymbols() const { return symVector; }
 
+  void redirect(Symbol *from, Symbol *to);
   void wrap(Symbol *sym, Symbol *real, Symbol *wrap);
 
   Symbol *insert(StringRef name);
diff --git a/lld/test/ELF/aarch64-got-merging-icf.s b/lld/test/ELF/aarch64-got-merging-icf.s
new file mode 100644
index 0000000000000..9f359cbf3a0a9
--- /dev/null
+++ b/lld/test/ELF/aarch64-got-merging-icf.s
@@ -0,0 +1,68 @@
+// REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
+# RUN: ld.lld %t -o %t2 --icf=all
+# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared %t -o %t3 --icf=all
+# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
+
+## All .rodata.* sections should merge into a single GOT entry
+# EXE: {{.*}}.got 00000008{{.*}}
+
+## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
+# DSO: {{.*}}.got 00000020{{.*}}
+
+.addrsig
+
+callee:
+ret
+
+.section .rodata.dummy1,"a",@progbits
+sym1:
+.long 111
+.long 122
+.byte 123
+
+.section .rodata.dummy2,"a",@progbits
+sym2:
+.long 111
+.long 122
+sym3:
+.byte 123
+
+.macro f, index
+
+.section .text.f1_\index,"ax",@progbits
+f1_\index:
+adrp x0, :got:g\index
+mov x1, #\index
+b f2_\index
+
+.section .text.f2_\index,"ax",@progbits
+f2_\index:
+ldr x0, [x0, :got_lo12:g\index] 
+b callee
+
+.globl g\index
+.section .rodata.g\index,"a",@progbits
+g_\index:
+.long 111
+.long 122
+
+g\index:
+.byte 123
+
+.section .text._start,"ax",@progbits
+bl f1_\index
+
+.endm
+
+.section .text._start,"ax",@progbits
+.globl _start
+_start:
+
+f 0
+f 1
+f 2
+f 3
diff --git a/lld/test/ELF/icf-preemptible.s b/lld/test/ELF/icf-preemptible.s
index 4bd1eca438b19..f79cf73b911ba 100644
--- a/lld/test/ELF/icf-preemptible.s
+++ b/lld/test/ELF/icf-preemptible.s
@@ -11,12 +11,15 @@
 # EXE-NOT:  {{.}}
 # EXE:      selected section {{.*}}:(.text.g1)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.g2)
+# EXE-NEXT:   selected symbol: f1; replaced symbol: f2
 # EXE-NEXT:   removing identical section {{.*}}:(.text.g3)
 # EXE-NEXT: selected section {{.*}}:(.text.f1)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.f2)
 # EXE-NEXT: selected section {{.*}}:(.text.h1)
 # EXE-NEXT:   removing identical section {{.*}}:(.text.h2)
+# EXE-NEXT:   selected symbol: g1; replaced symbol: g2
 # EXE-NEXT:   removing identical section {{.*}}:(.text.h3)
+# EXE-NEXT:   selected symbol: g1; replaced symbol: g3
 # EXE-NOT:  {{.}}
 
 ## Definitions are preemptible in a DSO. Only leaf functions can be folded.

``````````

</details>

author_association: MEMBER
user: login: pranavk
created_at: 2025-04-14T23:42:46Z
body: Once this is merged, I am going to send another PR to fix an issue that was uncovered by James' original diff to disable merging two sections when one refer to a global symbol, while other refer to a local symbol. JFYI.
author_association: CONTRIBUTOR
user: login: MaskRay
created_at: 2025-04-15T07:12:19Z
body: (Apologies. I will have limited internet access between April 20th and May 4th, and my response time may be delayed..)

author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-04-16T20:25:07Z
body: <!--LLVM CODE FORMAT COMMENT: {clang-format}-->

:white_check_mark: With the latest revision this PR passed the C/C++ code formatter.
author_association: NONE
user: login: zmodem
created_at: 2025-05-07T14:25:59Z
body: Just a heads up that we've bisected test failures/crashes on x86_64 Fuchsia to this change (https://crbug.com/415810137). I'm trying to come up with some kind reproducer, but it would be interesting to hear if others have had any issues as well.
author_association: COLLABORATOR
user: login: pranavk
created_at: 2025-05-07T16:42:42Z
body: I am not aware of any other issue that was root-caused to this.  Yeah, a reproducer would be great. I can take a look.
author_association: CONTRIBUTOR
user: login: zmodem
created_at: 2025-05-08T14:46:16Z
body: Our problem seems related to the `'__typeid_..._{bit,byte,unique_member}` symbols inserted by WholeProgramDevirt.cpp. If I exclude those from "redirecting", it appears to fix the tests.

That Fuchsia uses -fexperimental-relative-c++-abi-vtables may or may not explain why we only see it there.
author_association: COLLABORATOR
user: login: nico
created_at: 2025-05-09T14:42:53Z
body: @pranavk If you could reply to https://issues.chromium.org/issues/415810137#comment44 that'd be cool :)

(We've now seen a problem from this on android too.)
author_association: CONTRIBUTOR
user: login: zmodem
created_at: 2025-05-09T16:44:00Z
body: This has some UB, but I think it illustrates the core of our problem:

```
$ cat /tmp/x.c
const int x = 1;
const int y = 2;
extern const int __attribute__((alias("y"))) alias;

int f() { return *(&x + 1); }
int g() { return alias; }
int h() { return alias * 10; }

#include <stdio.h>
int main() {
  printf("%d %d %d\n", f(), g(), h());
  return 0;
}
```

Before the ICF change:

```
$ build/bin/clang /tmp/x.c -ffunction-sections -fuse-ld=lld -Wl,--icf=all && ./a.out
2 2 20
```

After:

```
$ build/bin/clang /tmp/x.c -ffunction-sections -fuse-ld=lld -Wl,--icf=all && ./a.out
2 2 10
```
author_association: COLLABORATOR
user: login: zmodem
created_at: 2025-05-12T09:03:51Z
body: It seems #139493 fixes both the above repro and the tests we were looking at originally :)

Do you think that will land quickly, or could we revert the breaking change to fix trunk in the meantime?
author_association: COLLABORATOR
user: login: zmodem
created_at: 2025-05-13T08:57:35Z
body: Let's revert to green while we figure out the right fix.
author_association: COLLABORATOR