Skip to content

Commit 8389d6f

Browse files
authored
[lld] Merge equivalent symbols found during ICF (#134342)
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 #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 #129122 Co-authored by: @jyknight
1 parent 7810d84 commit 8389d6f

File tree

5 files changed

+161
-1
lines changed

5 files changed

+161
-1
lines changed

lld/ELF/ICF.cpp

+53-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
#include "SymbolTable.h"
8181
#include "Symbols.h"
8282
#include "SyntheticSections.h"
83+
#include "llvm/ADT/EquivalenceClasses.h"
8384
#include "llvm/BinaryFormat/ELF.h"
8485
#include "llvm/Object/ELF.h"
8586
#include "llvm/Support/Parallel.h"
@@ -333,6 +334,28 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
333334
: constantEq(a, ra.relas, b, rb.relas);
334335
}
335336

337+
template <class RelTy>
338+
static SmallVector<Symbol *> getReloc(const InputSection *sec,
339+
Relocs<RelTy> relocs) {
340+
SmallVector<Symbol *> syms;
341+
for (auto ri = relocs.begin(), re = relocs.end(); ri != re; ++ri) {
342+
Symbol &sym = sec->file->getRelocTargetSym(*ri);
343+
syms.push_back(&sym);
344+
}
345+
return syms;
346+
}
347+
348+
template <class ELFT>
349+
static SmallVector<Symbol *> getRelocTargetSyms(const InputSection *sec) {
350+
const RelsOrRelas<ELFT> rel = sec->template relsOrRelas<ELFT>();
351+
if (rel.areRelocsCrel())
352+
return getReloc(sec, rel.crels);
353+
if (rel.areRelocsRel())
354+
return getReloc(sec, rel.rels);
355+
356+
return getReloc(sec, rel.relas);
357+
}
358+
336359
// Compare two lists of relocations. Returns true if all pairs of
337360
// relocations point to the same section in terms of ICF.
338361
template <class ELFT>
@@ -535,14 +558,28 @@ template <class ELFT> void ICF<ELFT>::run() {
535558
auto print = [&ctx = ctx]() -> ELFSyncStream {
536559
return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
537560
};
561+
562+
EquivalenceClasses<Symbol *> symbolEquivalence;
538563
// Merge sections by the equivalence class.
564+
// Merge symbols identified as equivalent during ICF.
539565
forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
540566
if (end - begin == 1)
541567
return;
542568
print() << "selected section " << sections[begin];
569+
SmallVector<Symbol *> syms = getRelocTargetSyms<ELFT>(sections[begin]);
543570
for (size_t i = begin + 1; i < end; ++i) {
544571
print() << " removing identical section " << sections[i];
545572
sections[begin]->replace(sections[i]);
573+
SmallVector<Symbol *> replacedSyms =
574+
getRelocTargetSyms<ELFT>(sections[i]);
575+
assert(syms.size() == replacedSyms.size() &&
576+
"Should have same number of syms!");
577+
for (size_t i = 0; i < syms.size(); i++) {
578+
if (syms[i] == replacedSyms[i] || !syms[i]->isGlobal() ||
579+
!replacedSyms[i]->isGlobal())
580+
continue;
581+
symbolEquivalence.unionSets(syms[i], replacedSyms[i]);
582+
}
546583

547584
// At this point we know sections merged are fully identical and hence
548585
// we want to remove duplicate implicit dependencies such as link order
@@ -561,11 +598,26 @@ template <class ELFT> void ICF<ELFT>::run() {
561598
d->folded = true;
562599
}
563600
};
564-
for (Symbol *sym : ctx.symtab->getSymbols())
601+
for (Symbol *sym : ctx.symtab->getSymbols()) {
565602
fold(sym);
603+
auto it = symbolEquivalence.findLeader(sym);
604+
if (it != symbolEquivalence.member_end() && *it != sym) {
605+
print() << "redirecting '" << sym->getName() << "' in symtab to '"
606+
<< (*it)->getName() << "'";
607+
ctx.symtab->redirect(sym, *it);
608+
}
609+
}
566610
parallelForEach(ctx.objectFiles, [&](ELFFileBase *file) {
567611
for (Symbol *sym : file->getLocalSymbols())
568612
fold(sym);
613+
for (Symbol *&sym : file->getMutableGlobalSymbols()) {
614+
auto it = symbolEquivalence.findLeader(sym);
615+
if (it != symbolEquivalence.member_end() && *it != sym) {
616+
print() << "redirecting '" << sym->getName() << "' to '"
617+
<< (*it)->getName() << "'";
618+
sym = *it;
619+
}
620+
}
569621
});
570622

571623
// InputSectionDescription::sections is populated by processSectionCommands().

lld/ELF/SymbolTable.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ using namespace llvm::ELF;
2929
using namespace lld;
3030
using namespace lld::elf;
3131

32+
void SymbolTable::redirect(Symbol *from, Symbol *to) {
33+
int &fromIdx = symMap[CachedHashStringRef(from->getName())];
34+
const int toIdx = symMap[CachedHashStringRef(to->getName())];
35+
fromIdx = toIdx;
36+
}
37+
3238
void SymbolTable::wrap(Symbol *sym, Symbol *real, Symbol *wrap) {
3339
// Redirect __real_foo to the original foo and foo to the original __wrap_foo.
3440
int &idx1 = symMap[CachedHashStringRef(sym->getName())];

lld/ELF/SymbolTable.h

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class SymbolTable {
4141
SymbolTable(Ctx &ctx) : ctx(ctx) {}
4242
ArrayRef<Symbol *> getSymbols() const { return symVector; }
4343

44+
void redirect(Symbol *from, Symbol *to);
4445
void wrap(Symbol *sym, Symbol *real, Symbol *wrap);
4546

4647
Symbol *insert(StringRef name);
+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
## REQUIRES: aarch64
2+
## Check that symbols that ICF assumes to be the same get a single GOT entry
3+
4+
# RUN: llvm-mc -filetype=obj -triple=aarch64 %s -o %t
5+
# RUN: llvm-mc -filetype=obj -crel -triple=aarch64 %s -o %tcrel
6+
# RUN: ld.lld %t -o %t2 --icf=all
7+
# RUN: ld.lld %tcrel -o %tcrel2 --icf=all
8+
9+
# RUN: llvm-objdump --section-headers %t2 | FileCheck %s --check-prefix=EXE
10+
# RUN: llvm-objdump --section-headers %tcrel2 | FileCheck %s --check-prefix=EXE
11+
12+
# RUN: ld.lld -shared %t -o %t3 --icf=all
13+
# RUN: ld.lld -shared %tcrel -o %tcrel3 --icf=all
14+
15+
# RUN: llvm-objdump --section-headers %t3 | FileCheck %s --check-prefix=DSO
16+
# RUN: llvm-objdump --section-headers %tcrel3 | FileCheck %s --check-prefix=DSO
17+
18+
## All global g* symbols should merge into a single GOT entry while non-global
19+
## gets its own GOT entry.
20+
# EXE: {{.*}}.got 00000010{{.*}}
21+
22+
## When symbols are preemptible in DSO mode, GOT entries wouldn't be merged
23+
# DSO: {{.*}}.got 00000028{{.*}}
24+
25+
.addrsig
26+
27+
callee:
28+
ret
29+
30+
.macro f, index, isglobal
31+
32+
# (Kept unique) first instruction of the GOT code sequence
33+
.section .text.f1_\index,"ax",@progbits
34+
f1_\index:
35+
adrp x0, :got:g\index
36+
mov x1, #\index
37+
b f2_\index
38+
39+
# Folded, second instruction of the GOT code sequence
40+
.section .text.f2_\index,"ax",@progbits
41+
f2_\index:
42+
ldr x0, [x0, :got_lo12:g\index]
43+
b callee
44+
45+
# Folded
46+
.ifnb \isglobal
47+
.globl g\index
48+
.endif
49+
.section .rodata.g\index,"a",@progbits
50+
g_\index:
51+
.long 111
52+
.long 122
53+
54+
g\index:
55+
.byte 123
56+
57+
.section .text._start,"ax",@progbits
58+
bl f1_\index
59+
60+
.endm
61+
62+
## Another set of sections merging: g1 <- g2. Linker should be able to
63+
## resolve both g1 and g2 to g0 based on ICF on previous sections.
64+
65+
.section .text.t1_0,"ax",@progbits
66+
t1_0:
67+
adrp x2, :got:g1
68+
mov x3, #1
69+
b t2_0
70+
71+
.section .text.t2_0,"ax",@progbits
72+
t2_0:
73+
ldr x2, [x2, :got_lo12:g1]
74+
b callee
75+
76+
.section .text.t1_1,"ax",@progbits
77+
t1_1:
78+
adrp x2, :got:g2
79+
mov x3, #2
80+
b t2_1
81+
82+
.section .text.t2_1,"ax",@progbits
83+
t2_1:
84+
ldr x2, [x2, :got_lo12:g2]
85+
b callee
86+
87+
.section .text._start,"ax",@progbits
88+
.globl _start
89+
_start:
90+
91+
f 0 1
92+
f 1 1
93+
f 2 1
94+
f 3 1
95+
f 4

lld/test/ELF/icf-preemptible.s

+6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
# EXE-NEXT: selected section {{.*}}:(.text.h1)
1818
# EXE-NEXT: removing identical section {{.*}}:(.text.h2)
1919
# EXE-NEXT: removing identical section {{.*}}:(.text.h3)
20+
# EXE-NEXT: redirecting 'f2' in symtab to 'f1'
21+
# EXE-NEXT: redirecting 'g2' in symtab to 'g1'
22+
# EXE-NEXT: redirecting 'g3' in symtab to 'g1'
23+
# EXE-NEXT: redirecting 'f2' to 'f1'
24+
# EXE-NEXT: redirecting 'g2' to 'g1'
25+
# EXE-NEXT: redirecting 'g3' to 'g1'
2026
# EXE-NOT: {{.}}
2127

2228
## Definitions are preemptible in a DSO. Only leaf functions can be folded.

0 commit comments

Comments
 (0)