diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp index 1cdcf6be9d8a9..af7865615fcb9 100644 --- a/lld/ELF/ICF.cpp +++ b/lld/ELF/ICF.cpp @@ -262,15 +262,11 @@ bool ICF::constantEq(const InputSection *secA, Relocs ra, auto *db = dyn_cast(&sb); // Placeholder symbols generated by linker scripts look the same now but - // may have different values later. - if (!da || !db || da->scriptDefined || db->scriptDefined) - return false; - - // When comparing a pair of relocations, if they refer to different symbols, - // and either symbol is preemptible, the containing sections should be - // considered different. This is because even if the sections are identical - // in this DSO, they may not be after preemption. - if (da->isPreemptible || db->isPreemptible) + // may have different values later. Similarly, preemptible symbols may be + // different after preemption. When comparing a pair of relocations, if they + // refer to different symbols, the containing sections should be considered + // different. + if (!da || !db || !da->isFoldable() || !db->isFoldable()) return false; // Relocations referring to absolute symbols are constant-equal if their diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp index 629702b45965b..99fd91b5f2d1f 100644 --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -915,8 +915,7 @@ void elf::addGotEntry(Ctx &ctx, Symbol &sym) { } static void addGotAuthEntry(Ctx &ctx, Symbol &sym) { - ctx.in.got->addEntry(sym); - ctx.in.got->addAuthEntry(sym); + ctx.in.got->addEntry(sym, /*authEntry = */ true); uint64_t off = sym.getGotOffset(ctx); // If preemptible, emit a GLOB_DAT relocation. diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h index 64f2f6eaa8d09..775e881cb129f 100644 --- a/lld/ELF/Symbols.h +++ b/lld/ELF/Symbols.h @@ -156,6 +156,8 @@ class Symbol { } uint8_t computeBinding(Ctx &) const; + // Preemptible and script defined symbols cannot ever be treated same + bool isFoldable() const { return !isPreemptible && !scriptDefined; } bool isGlobal() const { return binding == llvm::ELF::STB_GLOBAL; } bool isWeak() const { return binding == llvm::ELF::STB_WEAK; } diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp index b03c4282ab1aa..18eb77e2e3551 100644 --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -665,13 +665,31 @@ GotSection::GotSection(Ctx &ctx) } void GotSection::addConstant(const Relocation &r) { relocations.push_back(r); } -void GotSection::addEntry(const Symbol &sym) { +void GotSection::addEntry(const Symbol &sym, bool authEntry) { assert(sym.auxIdx == ctx.symAux.size() - 1); - ctx.symAux.back().gotIdx = numEntries++; -} + auto *d = dyn_cast(&sym); + std::optional finalGotIdx; + if (d && d->isFoldable()) { + // Generate one GOT entry for all foldable symbols. This could be due to + // ICF where containing sections have now been folded into one, or aliases + // that all point to the same symbol. + auto [it, inserted] = gotEntries.insert(std::make_pair( + std::make_tuple(d->section, d->value, sym.type), numEntries)); + if (!inserted) { + finalGotIdx = it->getSecond(); + } + } + + if (!finalGotIdx.has_value()) { + finalGotIdx = numEntries++; + } + + if (authEntry) { + authEntries.push_back( + {finalGotIdx.value() * ctx.arg.wordsize, sym.isFunc()}); + } -void GotSection::addAuthEntry(const Symbol &sym) { - authEntries.push_back({(numEntries - 1) * ctx.arg.wordsize, sym.isFunc()}); + ctx.symAux.back().gotIdx = finalGotIdx.value(); } bool GotSection::addTlsDescEntry(const Symbol &sym) { diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h index c977562f0b174..3e68c28fa8a75 100644 --- a/lld/ELF/SyntheticSections.h +++ b/lld/ELF/SyntheticSections.h @@ -111,8 +111,7 @@ class GotSection final : public SyntheticSection { void writeTo(uint8_t *buf) override; void addConstant(const Relocation &r); - void addEntry(const Symbol &sym); - void addAuthEntry(const Symbol &sym); + void addEntry(const Symbol &sym, bool authEntry = false); bool addTlsDescEntry(const Symbol &sym); void addTlsDescAuthEntry(); bool addDynTlsEntry(const Symbol &sym); @@ -138,6 +137,14 @@ class GotSection final : public SyntheticSection { bool isSymbolFunc; }; SmallVector authEntries; + + // Map of GOT entries keyed by section, offset, and type. The purpose is to + // reuse GOT entries when multiple same-type, foldable symbols refer to the + // image location. In general, this is a GOT-size optimization, but it is + // also required for some cases involving multi-instruction GOT access + // patterns and ICF. + llvm::DenseMap, uint32_t> + gotEntries; }; // .note.GNU-stack section. 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