[lld] [lld][ICF] Don't merge symbols with different addends (PR #139493)

Pranav Kant via llvm-commits llvm-commits at lists.llvm.org
Tue May 13 17:10:20 PDT 2025


https://github.com/pranavk updated https://github.com/llvm/llvm-project/pull/139493

>From faa11ed33f123e30f26116ff1c968fb249d72416 Mon Sep 17 00:00:00 2001
From: Pranav Kant <prka at google.com>
Date: Tue, 13 May 2025 11:20:44 -0700
Subject: [PATCH 1/4] Reapply "[lld] Merge equivalent symbols found during ICF
 (#134342)"

This reverts commit fd3fecfc0936703f2715fe6fea890e81b0b3c2ac.
---
 lld/ELF/ICF.cpp                        | 54 ++++++++++++++-
 lld/ELF/SymbolTable.cpp                |  6 ++
 lld/ELF/SymbolTable.h                  |  1 +
 lld/test/ELF/aarch64-got-merging-icf.s | 95 ++++++++++++++++++++++++++
 lld/test/ELF/icf-preemptible.s         |  6 ++
 5 files changed, 161 insertions(+), 1 deletion(-)
 create mode 100644 lld/test/ELF/aarch64-got-merging-icf.s

diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp
index 77fe7c58e92cd..1882116d4d5f0 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>
@@ -537,14 +560,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
@@ -563,11 +600,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", at 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", at progbits
+f2_\index:
+ldr x0, [x0, :got_lo12:g\index] 
+b callee
+
+# Folded
+.ifnb \isglobal
+.globl g\index
+.endif
+.section .rodata.g\index,"a", at progbits
+g_\index:
+.long 111
+.long 122
+
+g\index:
+.byte 123
+
+.section .text._start,"ax", at 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", at progbits
+t1_0:
+adrp x2, :got:g1
+mov x3, #1
+b t2_0
+
+.section .text.t2_0,"ax", at progbits
+t2_0:
+ldr x2, [x2, :got_lo12:g1]
+b callee
+
+.section .text.t1_1,"ax", at progbits
+t1_1:
+adrp x2, :got:g2
+mov x3, #2
+b t2_1
+
+.section .text.t2_1,"ax", at progbits
+t2_1:
+ldr x2, [x2, :got_lo12:g2]
+b callee
+
+.section .text._start,"ax", at 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.

>From e4041b71b7c2f0abf2bc0baa926cccd0ab74bf55 Mon Sep 17 00:00:00 2001
From: Pranav Kant <prka at google.com>
Date: Tue, 13 May 2025 14:10:14 -0700
Subject: [PATCH 2/4] [lld][ICF] Don't merge symbols with different addends

---
 lld/ELF/ICF.cpp           |  6 +++++-
 lld/test/ELF/icf-addend.s | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 1 deletion(-)
 create mode 100644 lld/test/ELF/icf-addend.s

diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp
index 1882116d4d5f0..643ada56cd5b7 100644
--- a/lld/ELF/ICF.cpp
+++ b/lld/ELF/ICF.cpp
@@ -287,7 +287,11 @@ bool ICF<ELFT>::constantEq(const InputSection *secA, Relocs<RelTy> ra,
     // Relocations referring to InputSections are constant-equal if their
     // section offsets are equal.
     if (isa<InputSection>(da->section)) {
-      if (da->value + addA == db->value + addB)
+      // Our symbol folding logic later merges symbols in two folded sections
+      // We should not merge sections in the first place if their symbols
+      // cannot be merged together.
+      bool canMergeSymbols = addA == addB;
+      if (da->value + addA == db->value + addB && canMergeSymbols)
         continue;
       return false;
     }
diff --git a/lld/test/ELF/icf-addend.s b/lld/test/ELF/icf-addend.s
new file mode 100644
index 0000000000000..98496a832b2b6
--- /dev/null
+++ b/lld/test/ELF/icf-addend.s
@@ -0,0 +1,32 @@
+# REQUIRES: x86
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o
+# RUN: ld.lld %t.o -o /dev/null --icf=all --print-icf-sections | FileCheck --allow-empty %s
+
+# Check that ICF doesn't fold sections containing functions that references
+# unmergeable symbols. We should only merge symbols of two relocations when
+# their addends are same.
+
+# CHECK-NOT: selected section {{.*}}:(.text.f1)
+# CHECK-NOT:   removing identical section {{.*}}:(.text.f2)
+
+.globl x, y
+
+.section .rodata,"a", at progbits
+x:
+.long 11
+y:
+.long 12
+
+.section .text.f1,"ax", at progbits
+f1:
+movq x+4(%rip), %rax 
+
+.section .text.f2,"ax", at progbits
+f2:
+movq y(%rip), %rax
+
+.section .text._start,"ax", at progbits
+.globl _start
+_start:
+call f1
+call f2

>From adef51a1b78ac94bc00a7a8c5fb1579bdcf39eba Mon Sep 17 00:00:00 2001
From: Pranav Kant <prka at google.com>
Date: Tue, 13 May 2025 16:20:28 -0700
Subject: [PATCH 3/4] [lld] Disable section merging only for non-trivial
 relocations

---
 lld/ELF/ICF.cpp           | 82 ++++++++++++++++++++++++++-------------
 lld/ELF/InputSection.h    | 43 ++++++++++++++++++++
 lld/ELF/Relocations.cpp   | 77 +-----------------------------------
 lld/ELF/Relocations.h     | 39 ++++++++++++++++++-
 lld/ELF/Target.cpp        |  1 +
 lld/test/ELF/icf-addend.s | 51 ++++++++++++++++++++----
 6 files changed, 182 insertions(+), 111 deletions(-)

diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp
index 643ada56cd5b7..57fd0232f2b80 100644
--- a/lld/ELF/ICF.cpp
+++ b/lld/ELF/ICF.cpp
@@ -77,9 +77,11 @@
 #include "InputFiles.h"
 #include "LinkerScript.h"
 #include "OutputSections.h"
+#include "Relocations.h"
 #include "SymbolTable.h"
 #include "Symbols.h"
 #include "SyntheticSections.h"
+#include "Target.h"
 #include "llvm/ADT/EquivalenceClasses.h"
 #include "llvm/BinaryFormat/ELF.h"
 #include "llvm/Object/ELF.h"
@@ -105,18 +107,23 @@ template <class ELFT> class ICF {
   void segregate(size_t begin, size_t end, uint32_t eqClassBase, bool constant);
 
   template <class RelTy>
-  bool constantEq(const InputSection *a, Relocs<RelTy> relsA,
-                  const InputSection *b, Relocs<RelTy> relsB);
+  bool constantEq(InputSection *a, Relocs<RelTy> relsA, InputSection *b,
+                  Relocs<RelTy> relsB);
 
   template <class RelTy>
   bool variableEq(const InputSection *a, Relocs<RelTy> relsA,
                   const InputSection *b, Relocs<RelTy> relsB);
 
-  bool equalsConstant(const InputSection *a, const InputSection *b);
+  bool equalsConstant(InputSection *a, InputSection *b);
   bool equalsVariable(const InputSection *a, const InputSection *b);
 
   size_t findBoundary(size_t begin, size_t end);
 
+  // A relocation with side-effects is considered non-trivial. Eg: relocation
+  // creates GOT entry or TLS slot.
+  template <class RelTy>
+  bool isTrivialRelocation(InputSection *a, Symbol &s, RelTy reloc);
+
   void forEachClassRange(size_t begin, size_t end,
                          llvm::function_ref<void(size_t, size_t)> fn);
 
@@ -235,11 +242,30 @@ void ICF<ELFT>::segregate(size_t begin, size_t end, uint32_t eqClassBase,
   }
 }
 
+template <class ELFT>
+template <class RelTy>
+bool ICF<ELFT>::isTrivialRelocation(InputSection *a, Symbol &s, RelTy reloc) {
+  OffsetGetter getter(*a);
+  uint64_t offset = getter.get(ctx, reloc.r_offset);
+  RelExpr expr = ctx.target->getRelExpr(reloc.getType(ctx.arg.isMips64EL), s,
+                                        a->content().data() + offset);
+
+  if (needsGot(expr) || needsTls(s, expr))
+    return false;
+  return true;
+}
+
+// Two symbols referenced by relocations can be merged together safely
+// when their addends are same.
+static bool canMergeSymbols(uint64_t addA, uint64_t addB) {
+  return addA == addB;
+}
+
 // Compare two lists of relocations.
 template <class ELFT>
 template <class RelTy>
-bool ICF<ELFT>::constantEq(const InputSection *secA, Relocs<RelTy> ra,
-                           const InputSection *secB, Relocs<RelTy> rb) {
+bool ICF<ELFT>::constantEq(InputSection *secA, Relocs<RelTy> ra,
+                           InputSection *secB, Relocs<RelTy> rb) {
   if (ra.size() != rb.size())
     return false;
   auto rai = ra.begin(), rae = ra.end(), rbi = rb.begin();
@@ -287,13 +313,14 @@ bool ICF<ELFT>::constantEq(const InputSection *secA, Relocs<RelTy> ra,
     // Relocations referring to InputSections are constant-equal if their
     // section offsets are equal.
     if (isa<InputSection>(da->section)) {
-      // Our symbol folding logic later merges symbols in two folded sections
-      // We should not merge sections in the first place if their symbols
-      // cannot be merged together.
-      bool canMergeSymbols = addA == addB;
-      if (da->value + addA == db->value + addB && canMergeSymbols)
+      if (da->value + addA == db->value + addB) {
+        // For non-trivial relocations, if we cannot merge symbols together,
+        // we must not merge them.
+        if (!isTrivialRelocation(secA, sa, *rai) &&
+            !canMergeSymbols(addA, addB))
+          return false;
         continue;
-      return false;
+      }
     }
 
     // Relocations referring to MergeInputSections are constant-equal if their
@@ -319,7 +346,7 @@ bool ICF<ELFT>::constantEq(const InputSection *secA, Relocs<RelTy> ra,
 // Compare "non-moving" part of two InputSections, namely everything
 // except relocation targets.
 template <class ELFT>
-bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
+bool ICF<ELFT>::equalsConstant(InputSection *a, InputSection *b) {
   if (a->flags != b->flags || a->getSize() != b->getSize() ||
       a->content() != b->content())
     return false;
@@ -338,26 +365,27 @@ 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;
+template <class ELFT, class RelTy>
+static SmallVector<std::pair<Symbol *, uint64_t>>
+getReloc(const InputSection *sec, Relocs<RelTy> relocs) {
+  SmallVector<std::pair<Symbol *, uint64_t>> syms;
   for (auto ri = relocs.begin(), re = relocs.end(); ri != re; ++ri) {
     Symbol &sym = sec->file->getRelocTargetSym(*ri);
-    syms.push_back(&sym);
+    syms.emplace_back(&sym, getAddend<ELFT>(*ri));
   }
   return syms;
 }
 
 template <class ELFT>
-static SmallVector<Symbol *> getRelocTargetSyms(const InputSection *sec) {
+static SmallVector<std::pair<Symbol *, uint64_t>>
+getRelocTargetSyms(const InputSection *sec) {
   const RelsOrRelas<ELFT> rel = sec->template relsOrRelas<ELFT>();
   if (rel.areRelocsCrel())
-    return getReloc(sec, rel.crels);
+    return getReloc<ELFT>(sec, rel.crels);
   if (rel.areRelocsRel())
-    return getReloc(sec, rel.rels);
+    return getReloc<ELFT>(sec, rel.rels);
 
-  return getReloc(sec, rel.relas);
+  return getReloc<ELFT>(sec, rel.relas);
 }
 
 // Compare two lists of relocations. Returns true if all pairs of
@@ -572,19 +600,21 @@ template <class ELFT> void ICF<ELFT>::run() {
     if (end - begin == 1)
       return;
     print() << "selected section " << sections[begin];
-    SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
+    SmallVector<std::pair<Symbol *, uint64_t>> 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 =
+      SmallVector<std::pair<Symbol *, uint64_t>> 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())
+        if (syms[i].first == replacedSyms[i].first ||
+            !syms[i].first->isGlobal() || !replacedSyms[i].first->isGlobal() ||
+            !canMergeSymbols(syms[i].second, replacedSyms[i].second))
           continue;
-        symbolEquivalence.unionSets(syms[i], replacedSyms[i]);
+        symbolEquivalence.unionSets(syms[i].first, replacedSyms[i].first);
       }
 
       // At this point we know sections merged are fully identical and hence
diff --git a/lld/ELF/InputSection.h b/lld/ELF/InputSection.h
index 98e7d5d4ff0cd..4371cc17d2822 100644
--- a/lld/ELF/InputSection.h
+++ b/lld/ELF/InputSection.h
@@ -514,6 +514,49 @@ class SyntheticSection : public InputSection {
   }
 };
 
+class OffsetGetter {
+public:
+  OffsetGetter() = default;
+  explicit OffsetGetter(InputSectionBase &sec) {
+    if (auto *eh = dyn_cast<EhInputSection>(&sec)) {
+      cies = eh->cies;
+      fdes = eh->fdes;
+      i = cies.begin();
+      j = fdes.begin();
+    }
+  }
+
+  // Translates offsets in input sections to offsets in output sections.
+  // Given offset must increase monotonically. We assume that Piece is
+  // sorted by inputOff.
+  uint64_t get(Ctx &ctx, uint64_t off) {
+    if (cies.empty())
+      return off;
+
+    while (j != fdes.end() && j->inputOff <= off)
+      ++j;
+    auto it = j;
+    if (j == fdes.begin() || j[-1].inputOff + j[-1].size <= off) {
+      while (i != cies.end() && i->inputOff <= off)
+        ++i;
+      if (i == cies.begin() || i[-1].inputOff + i[-1].size <= off) {
+        Err(ctx) << ".eh_frame: relocation is not in any piece";
+        return 0;
+      }
+      it = i;
+    }
+
+    // Offset -1 means that the piece is dead (i.e. garbage collected).
+    if (it[-1].outputOff == -1)
+      return -1;
+    return it[-1].outputOff + (off - it[-1].inputOff);
+  }
+
+private:
+  ArrayRef<EhSectionPiece> cies, fdes;
+  ArrayRef<EhSectionPiece>::iterator i, j;
+};
+
 inline bool isStaticRelSecType(uint32_t type) {
   return type == llvm::ELF::SHT_RELA || type == llvm::ELF::SHT_CREL ||
          type == llvm::ELF::SHT_REL;
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 277acb26987bc..07abfe627f6c8 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -127,29 +127,6 @@ void elf::reportRangeError(Ctx &ctx, uint8_t *loc, int64_t v, int n,
   }
 }
 
-// Build a bitmask with one bit set for each 64 subset of RelExpr.
-static constexpr uint64_t buildMask() { return 0; }
-
-template <typename... Tails>
-static constexpr uint64_t buildMask(int head, Tails... tails) {
-  return (0 <= head && head < 64 ? uint64_t(1) << head : 0) |
-         buildMask(tails...);
-}
-
-// Return true if `Expr` is one of `Exprs`.
-// There are more than 64 but less than 128 RelExprs, so we divide the set of
-// exprs into [0, 64) and [64, 128) and represent each range as a constant
-// 64-bit mask. Then we decide which mask to test depending on the value of
-// expr and use a simple shift and bitwise-and to test for membership.
-template <RelExpr... Exprs> static bool oneof(RelExpr expr) {
-  assert(0 <= expr && (int)expr < 128 &&
-         "RelExpr is too large for 128-bit mask!");
-
-  if (expr >= 64)
-    return (uint64_t(1) << (expr - 64)) & buildMask((Exprs - 64)...);
-  return (uint64_t(1) << expr) & buildMask(Exprs...);
-}
-
 static RelType getMipsPairType(RelType type, bool isLocal) {
   switch (type) {
   case R_MIPS_HI16:
@@ -196,15 +173,6 @@ static bool needsPlt(RelExpr expr) {
                RE_PPC64_CALL_PLT>(expr);
 }
 
-bool lld::elf::needsGot(RelExpr expr) {
-  return oneof<R_GOT, RE_AARCH64_AUTH_GOT, RE_AARCH64_AUTH_GOT_PC, R_GOT_OFF,
-               RE_MIPS_GOT_LOCAL_PAGE, RE_MIPS_GOT_OFF, RE_MIPS_GOT_OFF32,
-               RE_AARCH64_GOT_PAGE_PC, RE_AARCH64_AUTH_GOT_PAGE_PC,
-               RE_AARCH64_AUTH_GOT_PAGE_PC, R_GOT_PC, R_GOTPLT,
-               RE_AARCH64_GOT_PAGE, RE_LOONGARCH_GOT, RE_LOONGARCH_GOT_PAGE_PC>(
-      expr);
-}
-
 // True if this expression is of the form Sym - X, where X is a position in the
 // file (PC, or GOT for example).
 static bool isRelExpr(RelExpr expr) {
@@ -403,49 +371,6 @@ template <class ELFT> static void addCopyRelSymbol(Ctx &ctx, SharedSymbol &ss) {
 //
 // For sections other than .eh_frame, this class doesn't do anything.
 namespace {
-class OffsetGetter {
-public:
-  OffsetGetter() = default;
-  explicit OffsetGetter(InputSectionBase &sec) {
-    if (auto *eh = dyn_cast<EhInputSection>(&sec)) {
-      cies = eh->cies;
-      fdes = eh->fdes;
-      i = cies.begin();
-      j = fdes.begin();
-    }
-  }
-
-  // Translates offsets in input sections to offsets in output sections.
-  // Given offset must increase monotonically. We assume that Piece is
-  // sorted by inputOff.
-  uint64_t get(Ctx &ctx, uint64_t off) {
-    if (cies.empty())
-      return off;
-
-    while (j != fdes.end() && j->inputOff <= off)
-      ++j;
-    auto it = j;
-    if (j == fdes.begin() || j[-1].inputOff + j[-1].size <= off) {
-      while (i != cies.end() && i->inputOff <= off)
-        ++i;
-      if (i == cies.begin() || i[-1].inputOff + i[-1].size <= off) {
-        Err(ctx) << ".eh_frame: relocation is not in any piece";
-        return 0;
-      }
-      it = i;
-    }
-
-    // Offset -1 means that the piece is dead (i.e. garbage collected).
-    if (it[-1].outputOff == -1)
-      return -1;
-    return it[-1].outputOff + (off - it[-1].inputOff);
-  }
-
-private:
-  ArrayRef<EhSectionPiece> cies, fdes;
-  ArrayRef<EhSectionPiece>::iterator i, j;
-};
-
 // This class encapsulates states needed to scan relocations for one
 // InputSectionBase.
 class RelocationScanner {
@@ -1593,7 +1518,7 @@ void RelocationScanner::scanOne(typename Relocs<RelTy>::const_iterator &i) {
   //
   // Some RISCV TLSDESC relocations reference a local NOTYPE symbol,
   // but we need to process them in handleTlsRelocation.
-  if (sym.isTls() || oneof<R_TLSDESC_PC, R_TLSDESC_CALL>(expr)) {
+  if (needsTls(sym, expr)) {
     if (unsigned processed =
             handleTlsRelocation(expr, type, offset, sym, addend)) {
       i += processed - 1;
diff --git a/lld/ELF/Relocations.h b/lld/ELF/Relocations.h
index d2a77bc953109..2cefe3fd1f41d 100644
--- a/lld/ELF/Relocations.h
+++ b/lld/ELF/Relocations.h
@@ -9,6 +9,7 @@
 #ifndef LLD_ELF_RELOCATIONS_H
 #define LLD_ELF_RELOCATIONS_H
 
+#include "Symbols.h"
 #include "lld/Common/LLVM.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/STLExtras.h"
@@ -18,7 +19,6 @@
 namespace lld::elf {
 struct Ctx;
 class Defined;
-class Symbol;
 class InputSection;
 class InputSectionBase;
 class OutputSection;
@@ -355,6 +355,29 @@ inline Relocs<RelTy> sortRels(Relocs<RelTy> rels,
   return rels;
 }
 
+// Build a bitmask with one bit set for each 64 subset of RelExpr.
+constexpr uint64_t buildMask() { return 0; }
+
+template <typename... Tails>
+constexpr uint64_t buildMask(int head, Tails... tails) {
+  return (0 <= head && head < 64 ? uint64_t(1) << head : 0) |
+         buildMask(tails...);
+}
+
+// Return true if `Expr` is one of `Exprs`.
+// There are more than 64 but less than 128 RelExprs, so we divide the set of
+// exprs into [0, 64) and [64, 128) and represent each range as a constant
+// 64-bit mask. Then we decide which mask to test depending on the value of
+// expr and use a simple shift and bitwise-and to test for membership.
+template <RelExpr... Exprs> static bool oneof(RelExpr expr) {
+  assert(0 <= expr && (int)expr < 128 &&
+         "RelExpr is too large for 128-bit mask!");
+
+  if (expr >= 64)
+    return (uint64_t(1) << (expr - 64)) & buildMask((Exprs - 64)...);
+  return (uint64_t(1) << expr) & buildMask(Exprs...);
+}
+
 template <bool is64>
 inline Relocs<llvm::object::Elf_Crel_Impl<is64>>
 sortRels(Relocs<llvm::object::Elf_Crel_Impl<is64>> rels,
@@ -367,7 +390,19 @@ RelocationBaseSection &getIRelativeSection(Ctx &ctx);
 // Returns true if Expr refers a GOT entry. Note that this function returns
 // false for TLS variables even though they need GOT, because TLS variables uses
 // GOT differently than the regular variables.
-bool needsGot(RelExpr expr);
+inline bool needsGot(RelExpr expr) {
+  return oneof<R_GOT, RE_AARCH64_AUTH_GOT, RE_AARCH64_AUTH_GOT_PC, R_GOT_OFF,
+               RE_MIPS_GOT_LOCAL_PAGE, RE_MIPS_GOT_OFF, RE_MIPS_GOT_OFF32,
+               RE_AARCH64_GOT_PAGE_PC, RE_AARCH64_AUTH_GOT_PAGE_PC,
+               RE_AARCH64_AUTH_GOT_PAGE_PC, R_GOT_PC, R_GOTPLT,
+               RE_AARCH64_GOT_PAGE, RE_LOONGARCH_GOT, RE_LOONGARCH_GOT_PAGE_PC>(
+      expr);
+}
+
+inline bool needsTls(Symbol &s, RelExpr expr) {
+  return s.isTls() || oneof<R_TLSDESC_PC, R_TLSDESC_CALL>(expr);
+}
+
 } // namespace lld::elf
 
 #endif
diff --git a/lld/ELF/Target.cpp b/lld/ELF/Target.cpp
index c90ef8505aadd..9f57e37c2d6c4 100644
--- a/lld/ELF/Target.cpp
+++ b/lld/ELF/Target.cpp
@@ -26,6 +26,7 @@
 #include "Target.h"
 #include "InputFiles.h"
 #include "OutputSections.h"
+#include "Relocations.h"
 #include "SymbolTable.h"
 #include "Symbols.h"
 #include "SyntheticSections.h"
diff --git a/lld/test/ELF/icf-addend.s b/lld/test/ELF/icf-addend.s
index 98496a832b2b6..a343812688b2c 100644
--- a/lld/test/ELF/icf-addend.s
+++ b/lld/test/ELF/icf-addend.s
@@ -1,13 +1,17 @@
 # REQUIRES: x86
-# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o
-# RUN: ld.lld %t.o -o /dev/null --icf=all --print-icf-sections | FileCheck --allow-empty %s
+# RUN: rm -rf %t && split-file %s %t && cd %t
 
-# Check that ICF doesn't fold sections containing functions that references
-# unmergeable symbols. We should only merge symbols of two relocations when
-# their addends are same.
+#--- trivial-relocation.s
+# For trivial relocations, merging two equivalent sections is allowed but we must not
+# merge their symbols if addends are different.
 
-# CHECK-NOT: selected section {{.*}}:(.text.f1)
-# CHECK-NOT:   removing identical section {{.*}}:(.text.f2)
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux trivial-relocation.s -o trivial.o
+# RUN: ld.lld trivial.o -o /dev/null --icf=all --print-icf-sections | FileCheck %s
+
+# CHECK: selected section {{.*}}:(.text.f1)
+# CHECK:   removing identical section {{.*}}:(.text.f2)
+# CHECK-NOT: redirecting 'y' in symtab to x
+# CHECK-NOT: redirecting 'y' to 'x'
 
 .globl x, y
 
@@ -30,3 +34,36 @@ movq y(%rip), %rax
 _start:
 call f1
 call f2
+
+#--- non-trivial-relocation.s
+# For non-trivial relocations, we must not merge sections if addends are different.
+# Not merging sections would automatically disable symbol merging.
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux trivial-relocation.s -o trivial.o
+# RUN: ld.lld trivial.o -o /dev/null --icf=all --print-icf-sections | FileCheck %s
+
+# CHECK-NOT: selected section {{.*}}:(.text.f1)
+# CHECK-NOT:   removing identical section {{.*}}:(.text.f2)
+
+.globl x, y
+
+.section .rodata,"a", at progbits
+x:
+.long 11
+y:
+.long 12
+
+.section .text.f1,"ax", at progbits
+f1:
+movq x+4 at GOTPCREL(%rip), %rax
+
+.section .text.f2,"ax", at progbits
+f2:
+movq y at GOTPCREL(%rip), %rax
+
+.section .text._start,"ax", at progbits
+.globl _start
+_start:
+call f1
+call f2
+

>From 6b435f9e042b80c7254e7981a304e7c2c6b2e93e Mon Sep 17 00:00:00 2001
From: Pranav Kant <prka at google.com>
Date: Tue, 13 May 2025 17:09:01 -0700
Subject: [PATCH 4/4] [lld][ELF] Prevent merging two sections when they point
 to non-globals

---
 lld/ELF/ICF.cpp                        | 27 +++++++++++++++---------
 lld/test/ELF/aarch64-got-merging-icf.s | 29 +++++++++++++++++++++++---
 2 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp
index 57fd0232f2b80..decc0d3f7fea1 100644
--- a/lld/ELF/ICF.cpp
+++ b/lld/ELF/ICF.cpp
@@ -111,11 +111,11 @@ template <class ELFT> class ICF {
                   Relocs<RelTy> relsB);
 
   template <class RelTy>
-  bool variableEq(const InputSection *a, Relocs<RelTy> relsA,
-                  const InputSection *b, Relocs<RelTy> relsB);
+  bool variableEq(InputSection *a, Relocs<RelTy> relsA, InputSection *b,
+                  Relocs<RelTy> relsB);
 
   bool equalsConstant(InputSection *a, InputSection *b);
-  bool equalsVariable(const InputSection *a, const InputSection *b);
+  bool equalsVariable(InputSection *a, InputSection *b);
 
   size_t findBoundary(size_t begin, size_t end);
 
@@ -315,7 +315,7 @@ bool ICF<ELFT>::constantEq(InputSection *secA, Relocs<RelTy> ra,
     if (isa<InputSection>(da->section)) {
       if (da->value + addA == db->value + addB) {
         // For non-trivial relocations, if we cannot merge symbols together,
-        // we must not merge them.
+        // we must not merge sections either.
         if (!isTrivialRelocation(secA, sa, *rai) &&
             !canMergeSymbols(addA, addB))
           return false;
@@ -392,8 +392,8 @@ getRelocTargetSyms(const InputSection *sec) {
 // relocations point to the same section in terms of ICF.
 template <class ELFT>
 template <class RelTy>
-bool ICF<ELFT>::variableEq(const InputSection *secA, Relocs<RelTy> ra,
-                           const InputSection *secB, Relocs<RelTy> rb) {
+bool ICF<ELFT>::variableEq(InputSection *secA, Relocs<RelTy> ra,
+                           InputSection *secB, Relocs<RelTy> rb) {
   assert(ra.size() == rb.size());
 
   auto rai = ra.begin(), rae = ra.end(), rbi = rb.begin();
@@ -407,6 +407,15 @@ bool ICF<ELFT>::variableEq(const InputSection *secA, Relocs<RelTy> ra,
     auto *da = cast<Defined>(&sa);
     auto *db = cast<Defined>(&sb);
 
+    // Prevent sections containing local symbols from merging into sections with
+    // global symbols, or vice-versa. This is to prevent local-global symbols
+    // getting merged into each other (done later in ICF). We do this as
+    // post-ICF passes cannot handle duplicates when iterating over local
+    // symbols. There are also assertions that prevent this.
+    if ((!da->isGlobal() || !db->isGlobal()) &&
+        !isTrivialRelocation(secA, sa, *rai))
+      return false;
+
     // We already dealt with absolute and non-InputSection symbols in
     // constantEq, and for InputSections we have already checked everything
     // except the equivalence class.
@@ -430,7 +439,7 @@ bool ICF<ELFT>::variableEq(const InputSection *secA, Relocs<RelTy> ra,
 
 // Compare "moving" part of two InputSections, namely relocation targets.
 template <class ELFT>
-bool ICF<ELFT>::equalsVariable(const InputSection *a, const InputSection *b) {
+bool ICF<ELFT>::equalsVariable(InputSection *a, InputSection *b) {
   const RelsOrRelas<ELFT> ra = a->template relsOrRelas<ELFT>();
   const RelsOrRelas<ELFT> rb = b->template relsOrRelas<ELFT>();
   if (ra.areRelocsCrel() || rb.areRelocsCrel())
@@ -610,9 +619,7 @@ template <class ELFT> void ICF<ELFT>::run() {
       assert(syms.size() == replacedSyms.size() &&
              "Should have same number of syms!");
       for (size_t i = 0; i < syms.size(); i++) {
-        if (syms[i].first == replacedSyms[i].first ||
-            !syms[i].first->isGlobal() || !replacedSyms[i].first->isGlobal() ||
-            !canMergeSymbols(syms[i].second, replacedSyms[i].second))
+        if (!canMergeSymbols(syms[i].second, replacedSyms[i].second))
           continue;
         symbolEquivalence.unionSets(syms[i].first, replacedSyms[i].first);
       }
diff --git a/lld/test/ELF/aarch64-got-merging-icf.s b/lld/test/ELF/aarch64-got-merging-icf.s
index 01d7bdfd75185..773bacf3102a1 100644
--- a/lld/test/ELF/aarch64-got-merging-icf.s
+++ b/lld/test/ELF/aarch64-got-merging-icf.s
@@ -3,7 +3,7 @@
 
 # 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 %t -o %t2 --icf=all --print-icf-sections
 # RUN: ld.lld %tcrel -o %tcrel2 --icf=all
 
 # RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
@@ -17,10 +17,32 @@
 
 ## All global g* symbols should merge into a single GOT entry while non-global
 ## gets its own GOT entry.
-# EXE: {{.*}}.got 00000010{{.*}}
+# EXE: {{.*}}.got 00000018{{.*}}
 
 ## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
-# DSO: {{.*}}.got 00000028{{.*}}
+# DSO: {{.*}}.got 00000030{{.*}}
+
+# 1. Sections containing local symbols (f4, f5) are not merged together.
+# 2. Sections containing global symbols are merged together.
+
+# CHECK: selected section {{.*}}:(.rodata.g0)
+# CHECK-NEXT: removing identical section {{.*}}:(.rodata.g1)
+# CHECK-NEXT: removing identical section {{.*}}:(.rodata.g2)
+# CHECK-NEXT: removing identical section {{.*}}:(.rodata.g3)
+# CHECK-NEXT: removing identical section {{.*}}:(.rodata.g4)
+# CHECK-NEXT: removing identical section {{.*}}:(.rodata.g5)
+# CHECK-NEXT: selected section {{.*}}:(.text.t2_0)
+# CHECK-NEXT: removing identical section {{.*}}:(.text.t2_1)
+# CHECK-NEXT: selected section {{.*}}:(.text.f2_0)
+# CHECK-NEXT: removing identical section {{.*}}:(.text.f2_1)
+# CHECK-NEXT: removing identical section {{.*}}:(.text.f2_2)
+# CHECK-NEXT: removing identical section {{.*}}:(.text.f2_3)
+# CHECK-NEXT: redirecting 'g1' in symtab to 'g0'
+# CHECK-NEXT: redirecting 'g2' in symtab to 'g0'
+# CHECK-NEXT: redirecting 'g3' in symtab to 'g0'
+# CHECK-NEXT: redirecting 'g1' to 'g0'
+# CHECK-NEXT: redirecting 'g2' to 'g0'
+# CHECK-NEXT: redirecting 'g3' to 'g0'
 
 .addrsig
 
@@ -93,3 +115,4 @@ f 1 1
 f 2 1
 f 3 1
 f 4
+f 5



More information about the llvm-commits mailing list