[lld] [lld][ELF] Fold LSDA-bearing sections with equivalent LSDA and CIE (PR #224929)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 20 09:09:39 PDT 2026
https://github.com/artemkulyk updated https://github.com/llvm/llvm-project/pull/224929
>From 280e7b62c23c6e9bd2fd77421e04c5f868cf9ca6 Mon Sep 17 00:00:00 2001
From: artemkulyk <artem.kulyk at gmail.com>
Date: Sun, 20 Sep 2026 14:41:08 +0200
Subject: [PATCH] [lld][ELF] Fold LSDA-bearing sections with equivalent LSDA
and CIE
Since 21b4f8060ab3 ("[ELF] --icf: don't fold text sections with LSDA"),
--icf=all never folds a text section whose .eh_frame FDE has an LSDA. That
restriction is stronger than necessary: folding is safe when the two LSDA
sections are equivalent and the CIEs (personality, encodings, CFI) match,
because the unwinder then observes the same exception-handling behavior for
either function.
Compare the LSDA and CIE of two LSDA-bearing sections in addition to their
contents and relocations. A section whose LSDA is not a section at offset 0
(e.g. a shared .gcc_except_table referenced at a nonzero offset), or whose
personality cannot be resolved, remains uneligible as before. An FDE whose
augmentation data does not contain exactly one LSDA relocation (for example
after RISC-V linker relaxation adds an R_RISCV_ADD32/R_RISCV_SUB32 pair to the
PC range) cannot be analyzed; such a section is conservatively kept unique
instead of being treated as having no LSDA.
The personality function is compared after following the DW_EH_PE_indirect
per-object pointer a compiler emits for it ("DW.ref.<personality>"), so the
same personality in different objects compares equal.
On the official ClickHouse v26.8.7.19-lts release recipe (ThinLTO, --icf=all,
identical frozen inputs, only the linker differs), the final artifact shrinks
by 7.19%: .text -10.06%, .eh_frame -12.89%, .gcc_except_table -7.66%, with
48,353 fewer FDEs. Proton v3.0.31 (-8.81% stripped), dwarfs v0.15.7
(-3.32%) and Next.js/Turbopack native SWC (-2.20%) show similar results.
Link time and peak memory are unchanged. The measurements are from this
change backported to the lld 22.1.8 release branch.
Assisted-by: opencode
---
lld/ELF/Driver.cpp | 45 +++-
lld/ELF/EhFrame.cpp | 47 +++-
lld/ELF/EhFrame.h | 9 +-
lld/ELF/ICF.cpp | 247 +++++++++++++++++-
lld/ELF/SyntheticSections.cpp | 108 ++++++--
lld/ELF/SyntheticSections.h | 26 +-
lld/docs/ReleaseNotes.md | 3 +
lld/test/ELF/icf-eh-frame.s | 78 ++++--
lld/test/ELF/icf-lsda-extra-fde-reloc.s | 116 ++++++++
lld/test/ELF/icf-lsda-landing-pad.s | 70 +++++
.../ELF/icf-lsda-personality-indirect-cross.s | 75 ++++++
lld/test/ELF/icf-lsda-personality-indirect.s | 92 +++++++
lld/test/ELF/icf-lsda-personality.s | 120 +++++++++
lld/test/ELF/icf-lsda-safe.s | 89 +++++++
14 files changed, 1057 insertions(+), 68 deletions(-)
create mode 100644 lld/test/ELF/icf-lsda-extra-fde-reloc.s
create mode 100644 lld/test/ELF/icf-lsda-landing-pad.s
create mode 100644 lld/test/ELF/icf-lsda-personality-indirect-cross.s
create mode 100644 lld/test/ELF/icf-lsda-personality-indirect.s
create mode 100644 lld/test/ELF/icf-lsda-personality.s
create mode 100644 lld/test/ELF/icf-lsda-safe.s
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index c5e8d1be2e4e9..5b7f5733c4b5d 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2677,13 +2677,17 @@ static void replaceCommonSymbols(Ctx &ctx) {
// The section referred to by `s` is considered address-significant. Set the
// keepUnique flag on the section if appropriate.
-static void markAddrsig(bool icfSafe, Symbol *s) {
+static void markAddrsig(bool icfSafe, Symbol *s,
+ DenseSet<InputSectionBase *> *explicitlySignificant) {
// We don't need to keep text sections unique under --icf=all even if they
// are address-significant.
if (auto *d = dyn_cast_or_null<Defined>(s))
if (auto *sec = dyn_cast_or_null<InputSectionBase>(d->section))
- if (icfSafe || !(sec->flags & SHF_EXECINSTR))
+ if (icfSafe || !(sec->flags & SHF_EXECINSTR)) {
sec->keepUnique = true;
+ if (explicitlySignificant)
+ explicitlySignificant->insert(sec);
+ }
}
// Record sections that define symbols mentioned in --keep-unique <symbol>
@@ -2691,6 +2695,9 @@ static void markAddrsig(bool icfSafe, Symbol *s) {
// ineligible for ICF.
template <class ELFT>
static void findKeepUniqueSections(Ctx &ctx, opt::InputArgList &args) {
+ // Sections explicitly requested via --keep-unique; they win over the LSDA
+ // exemption below.
+ SmallPtrSet<InputSectionBase *, 4> forced;
for (auto *arg : args.filtered(OPT_keep_unique)) {
StringRef name = arg->getValue();
auto *d = dyn_cast_or_null<Defined>(ctx.symtab->find(name));
@@ -2698,10 +2705,18 @@ static void findKeepUniqueSections(Ctx &ctx, opt::InputArgList &args) {
Warn(ctx) << "could not find symbol " << name << " to keep unique";
continue;
}
- if (auto *sec = dyn_cast<InputSectionBase>(d->section))
+ if (auto *sec = dyn_cast<InputSectionBase>(d->section)) {
sec->keepUnique = true;
+ forced.insert(sec);
+ }
}
+ // Sections marked address-significant by the dynsym or an address-
+ // significance table. The LSDA exemption below only clears the conservative
+ // fallback marking for objects without an address-significance table, so
+ // these sections stay unique.
+ DenseSet<InputSectionBase *> explicitlySignificant;
+
// --icf=all --ignore-data-address-equality means that we can ignore
// the dynsym and address-significance tables entirely.
if (ctx.arg.icf == ICFLevel::All && ctx.arg.ignoreDataAddressEquality)
@@ -2712,7 +2727,7 @@ static void findKeepUniqueSections(Ctx &ctx, opt::InputArgList &args) {
bool icfSafe = ctx.arg.icf == ICFLevel::Safe;
for (Symbol *sym : ctx.symtab->getSymbols())
if (sym->isExported)
- markAddrsig(icfSafe, sym);
+ markAddrsig(icfSafe, sym, &explicitlySignificant);
// Visit the address-significance table in each object file and mark each
// referenced symbol as address-significant.
@@ -2731,16 +2746,34 @@ static void findKeepUniqueSections(Ctx &ctx, opt::InputArgList &args) {
Err(ctx) << f << ": could not decode addrsig section: " << err;
break;
}
- markAddrsig(icfSafe, syms[symIndex]);
+ markAddrsig(icfSafe, syms[symIndex], &explicitlySignificant);
cur += size;
}
} else {
// If an object file does not have an address-significance table,
// conservatively mark all of its symbols as address-significant.
for (Symbol *s : syms)
- markAddrsig(icfSafe, s);
+ markAddrsig(icfSafe, s, nullptr);
}
}
+
+ // ICF folds an LSDA-bearing function only when its LSDA and CIE are
+ // equivalent to the surviving function's (ICF::lsdaEqualVariable), so the
+ // conservative keepUnique marking of an LSDA section can be cleared. Explicit
+ // --keep-unique requests and address-significant sections (dynsym or
+ // .llvm_addrsig) stay unique. Diagnostics are reported later by
+ // EhFrameSection::finalizeContents.
+ ctx.in.ehFrame->iterateFDEWithLSDATarget<ELFT>(
+ [&](InputSection &, const CieInfo &, const Symbol *sym, int64_t) {
+ if (!sym)
+ return;
+ auto *d = dyn_cast<Defined>(sym);
+ auto *sec = d ? dyn_cast_or_null<InputSection>(d->section) : nullptr;
+ if (sec && !forced.contains(sec) &&
+ !explicitlySignificant.contains(sec))
+ sec->keepUnique = false;
+ },
+ /*reportErrors=*/false);
}
static void markBuffersAsDontNeed(Ctx &ctx, bool skipLinkedOutput) {
diff --git a/lld/ELF/EhFrame.cpp b/lld/ELF/EhFrame.cpp
index aade9291d1fba..17d2888cd76bf 100644
--- a/lld/ELF/EhFrame.cpp
+++ b/lld/ELF/EhFrame.cpp
@@ -33,12 +33,16 @@ using namespace lld::elf;
namespace {
class EhReader {
public:
- EhReader(InputSectionBase *s, ArrayRef<uint8_t> d) : isec(s), d(d) {}
+ EhReader(InputSectionBase *s, ArrayRef<uint8_t> d, bool reportErrors = true)
+ : isec(s), d(d), reportErrors(reportErrors) {}
uint8_t getFdeEncoding();
+ std::optional<uint8_t> getPersonalityEncoding();
bool hasLSDA();
private:
template <class P> void errOn(const P *loc, const Twine &msg) {
+ if (!reportErrors)
+ return;
Ctx &ctx = isec->file->ctx;
Err(ctx) << "corrupted .eh_frame: " << msg << "\n>>> defined in "
<< isec->getObjMsg((const uint8_t *)loc - isec->content().data());
@@ -49,10 +53,12 @@ class EhReader {
StringRef readString();
void skipLeb128();
void skipAugP();
+ void skipAugPData(uint8_t enc);
StringRef getAugmentation();
InputSectionBase *isec;
ArrayRef<uint8_t> d;
+ bool reportErrors;
};
}
@@ -119,8 +125,7 @@ static size_t getAugPSize(Ctx &ctx, unsigned enc) {
return 0;
}
-void EhReader::skipAugP() {
- uint8_t enc = readByte();
+void EhReader::skipAugPData(uint8_t enc) {
if ((enc & 0xf0) == DW_EH_PE_aligned)
return errOn(d.data() - 1, "DW_EH_PE_aligned encoding is not supported");
size_t size = getAugPSize(isec->getCtx(), enc);
@@ -131,12 +136,22 @@ void EhReader::skipAugP() {
d = d.slice(size);
}
+void EhReader::skipAugP() {
+ uint8_t enc = readByte();
+ skipAugPData(enc);
+}
+
uint8_t elf::getFdeEncoding(EhSectionPiece *p) {
return EhReader(p->sec, p->data()).getFdeEncoding();
}
-bool elf::hasLSDA(const EhSectionPiece &p) {
- return EhReader(p.sec, p.data()).hasLSDA();
+std::optional<uint8_t> elf::getPersonalityEncoding(const EhSectionPiece &p,
+ bool reportErrors) {
+ return EhReader(p.sec, p.data(), reportErrors).getPersonalityEncoding();
+}
+
+bool elf::hasLSDA(const EhSectionPiece &p, bool reportErrors) {
+ return EhReader(p.sec, p.data(), reportErrors).hasLSDA();
}
StringRef EhReader::getAugmentation() {
@@ -185,6 +200,28 @@ uint8_t EhReader::getFdeEncoding() {
return DW_EH_PE_absptr;
}
+std::optional<uint8_t> EhReader::getPersonalityEncoding() {
+ StringRef aug = getAugmentation();
+ for (char c : aug) {
+ if (c == 'P') {
+ uint8_t enc = readByte();
+ skipAugPData(enc);
+ return enc;
+ }
+ if (c == 'z')
+ skipLeb128();
+ else if (c == 'L')
+ readByte();
+ else if (c == 'R')
+ readByte();
+ else if (c != 'B' && c != 'S' && c != 'G') {
+ errOn(aug.data(), "unknown .eh_frame augmentation string: " + aug);
+ break;
+ }
+ }
+ return std::nullopt;
+}
+
bool EhReader::hasLSDA() {
StringRef aug = getAugmentation();
for (char c : aug) {
diff --git a/lld/ELF/EhFrame.h b/lld/ELF/EhFrame.h
index 95264166e36cb..6980cbd6767d6 100644
--- a/lld/ELF/EhFrame.h
+++ b/lld/ELF/EhFrame.h
@@ -11,11 +11,18 @@
#include "lld/Common/LLVM.h"
+#include <optional>
+
namespace lld::elf {
struct EhSectionPiece;
uint8_t getFdeEncoding(EhSectionPiece *p);
-bool hasLSDA(const EhSectionPiece &p);
+// Returns the 'P' (personality) encoding of a CIE, if it has one.
+std::optional<uint8_t> getPersonalityEncoding(const EhSectionPiece &p,
+ bool reportErrors = true);
+// reportErrors=false suppresses diagnostics (used by pre-passes that run
+// before EhFrameSection::finalizeContents, which reports them).
+bool hasLSDA(const EhSectionPiece &p, bool reportErrors = true);
}
#endif
diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp
index 62c42bdbb550d..e65c0389d83bc 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/BinaryFormat/Dwarf.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/TimeProfiler.h"
@@ -113,6 +114,25 @@ template <class ELFT> class ICF {
bool equalsConstant(const InputSection *a, const InputSection *b);
bool equalsVariable(const InputSection *a, const InputSection *b);
+ bool lsdaEqualVariable(const InputSection *a, const InputSection *b);
+ bool cieRelsEqual(ArrayRef<Relocation> a, ArrayRef<Relocation> b) const;
+ bool personalityEqual(const Symbol *a, const Symbol *b) const;
+
+ struct LsdaInfo {
+ // The .gcc_except_table section the FDE's LSDA pointer refers to.
+ const InputSection *lsda;
+ // The CIE the FDE references.
+ ArrayRef<uint8_t> cieData;
+ // The personality function and its addend, resolved through a
+ // DW_EH_PE_indirect thunk if needed. personalityKnown is false when the
+ // personality cannot be resolved (conservatively not foldable).
+ bool personalityKnown;
+ const Symbol *personality;
+ int64_t personalityAddend;
+ // CIE relocations other than the personality pointer.
+ ArrayRef<Relocation> otherCieRels;
+ };
+
size_t findBoundary(size_t begin, size_t end);
void forEachClassRange(size_t begin, size_t end,
@@ -123,6 +143,13 @@ template <class ELFT> class ICF {
Ctx &ctx;
SmallVector<InputSection *, 0> sections;
+ // For each section whose .eh_frame FDE has an LSDA, the associated
+ // .gcc_except_table section and the CIE the FDE references. Two sections may
+ // be folded only if their LSDA sections are equal and their CIEs are
+ // equivalent (personality, encodings, CFI), otherwise exception handling
+ // behavior could change.
+ llvm::DenseMap<const InputSection *, LsdaInfo> lsdaMap;
+
// We repeat the main loop while `Repeat` is true.
std::atomic<bool> repeat;
@@ -375,6 +402,9 @@ 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) {
+ if (!lsdaEqualVariable(a, b))
+ return false;
+
const RelsOrRelas<ELFT> ra = a->template relsOrRelas<ELFT>();
const RelsOrRelas<ELFT> rb = b->template relsOrRelas<ELFT>();
if (ra.areRelocsCrel() || rb.areRelocsCrel())
@@ -384,6 +414,129 @@ bool ICF<ELFT>::equalsVariable(const InputSection *a, const InputSection *b) {
: variableEq(a, ra.relas, b, rb.relas);
}
+// Compare two lists of CIE relocations (e.g. the personality function).
+// Relocation targets are compared like variableEq() compares section
+// relocations: same symbol, or equivalent section-defined targets whose
+// equivalence classes have converged. This looks through the per-object
+// indirection thunk a compiler emits for an indirect personality pointer:
+// equal thunk sections imply equal personality functions.
+template <class ELFT>
+bool ICF<ELFT>::cieRelsEqual(ArrayRef<Relocation> a,
+ ArrayRef<Relocation> b) const {
+ if (a.size() != b.size())
+ return false;
+ for (size_t i = 0; i != a.size(); ++i) {
+ if (a[i].offset != b[i].offset || a[i].type != b[i].type ||
+ a[i].addend != b[i].addend || a[i].expr != b[i].expr)
+ return false;
+ Symbol *sa = a[i].sym;
+ Symbol *sb = b[i].sym;
+ if (sa == sb)
+ continue;
+ auto *da = dyn_cast<Defined>(sa);
+ auto *db = dyn_cast<Defined>(sb);
+ if (!da || !db)
+ return false;
+ // Absolute symbols are equal if their values are equal.
+ if (!da->section || !db->section) {
+ if (da->value != db->value)
+ return false;
+ continue;
+ }
+ // Section-defined symbols with different kind or offset are not equal.
+ if (da->section->kind() != db->section->kind() ||
+ da->value + a[i].addend != db->value + b[i].addend)
+ return false;
+ auto *x = dyn_cast<InputSection>(da->section);
+ auto *y = dyn_cast<InputSection>(db->section);
+ if (!x || !y)
+ return false;
+ if (x->eqClass[current] == 0 || x->eqClass[current] != y->eqClass[current])
+ return false;
+ }
+ return true;
+}
+
+// Return the symbol referenced by the relocation at `off` in `sec` and set
+// `addend`, or return nullptr if there is no such relocation (or more than
+// one).
+template <class ELFT>
+static const Symbol *relocTargetAt(const InputSection *sec, uint64_t off,
+ int64_t &addend) {
+ const RelsOrRelas<ELFT> rs = sec->template relsOrRelas<ELFT>();
+ if (rs.areRelocsCrel())
+ return nullptr;
+ const Symbol *sym = nullptr;
+ if (rs.areRelocsRel()) {
+ for (const typename ELFT::Rel &r : rs.rels)
+ if (r.r_offset == off) {
+ if (sym)
+ return nullptr;
+ sym = &sec->file->getRelocTargetSym(r);
+ addend = 0;
+ }
+ } else {
+ for (const typename ELFT::Rela &r : rs.relas)
+ if (r.r_offset == off) {
+ if (sym)
+ return nullptr;
+ sym = &sec->file->getRelocTargetSym(r);
+ addend = r.r_addend;
+ }
+ }
+ return sym;
+}
+
+// Compare two resolved personality functions like variableEq() compares
+// relocation targets: same symbol, or equivalent section-defined targets whose
+// equivalence classes have converged.
+template <class ELFT>
+bool ICF<ELFT>::personalityEqual(const Symbol *a, const Symbol *b) const {
+ if (a == b)
+ return true;
+ auto *da = dyn_cast<Defined>(a);
+ auto *db = dyn_cast<Defined>(b);
+ if (!da || !db)
+ return false;
+ if (!da->section || !db->section)
+ return da->value == db->value;
+ if (da->section->kind() != db->section->kind() || da->value != db->value)
+ return false;
+ auto *x = dyn_cast<InputSection>(da->section);
+ auto *y = dyn_cast<InputSection>(db->section);
+ if (!x || !y)
+ return false;
+ return x->eqClass[current] != 0 && x->eqClass[current] == y->eqClass[current];
+}
+
+// Return whether two text sections have equivalent exception-handling
+// metadata: the same LSDA (equal .gcc_except_table section and relocation
+// targets) and the same CIE (personality, encodings, CFI). A section with an
+// LSDA never equals a section without one.
+template <class ELFT>
+bool ICF<ELFT>::lsdaEqualVariable(const InputSection *a,
+ const InputSection *b) {
+ auto itA = lsdaMap.find(a);
+ auto itB = lsdaMap.find(b);
+ if (itA == lsdaMap.end() && itB == lsdaMap.end())
+ return true;
+ if (itA == lsdaMap.end() || itB == lsdaMap.end())
+ return false;
+ const LsdaInfo &x = itA->second;
+ const LsdaInfo &y = itB->second;
+ // The LSDA contents and their relocation targets must be equivalent. This
+ // uses the same equivalence-class refinement as normal ICF comparisons.
+ if (x.lsda->eqClass[current] == 0 ||
+ x.lsda->eqClass[current] != y.lsda->eqClass[current])
+ return false;
+ // The CIE (personality, encodings, CFI) must be equivalent as well.
+ if (x.cieData != y.cieData || !x.personalityKnown || !y.personalityKnown ||
+ x.personalityAddend != y.personalityAddend ||
+ !personalityEqual(x.personality, y.personality))
+ return false;
+ return cieRelsEqual(x.otherCieRels, y.otherCieRels);
+}
+
template <class ELFT> size_t ICF<ELFT>::findBoundary(size_t begin, size_t end) {
uint32_t eqClass = sections[begin]->eqClass[current];
for (size_t i = begin + 1; i < end; ++i)
@@ -463,17 +616,81 @@ static void combineRelocHashes(unsigned cnt, InputSection *isec,
// The main function of ICF.
template <class ELFT> void ICF<ELFT>::run() {
// Two text sections may have identical content and relocations but different
- // LSDA, e.g. the two functions may have catch blocks of different types. If a
- // text section is referenced by a .eh_frame FDE with LSDA, it is not
- // eligible. This is implemented by iterating over CIE/FDE and setting
- // eqClass[0] to the referenced text section from a live FDE.
- //
- // If two .gcc_except_table have identical semantics (usually identical
- // content with PC-relative encoding), we will lose folding opportunity.
+ // LSDA, e.g. the two functions may have catch blocks of different types.
+ // A section whose FDE has an LSDA is foldable only when the associated LSDA
+ // section is known and is equal for both sections (see lsdaMap). Sections
+ // with an LSDA that cannot be analyzed (e.g. a shared .gcc_except_table
+ // section referenced at a nonzero offset) are not eligible, as before.
uint32_t uniqueId = 0;
- ctx.in.ehFrame->iterateFDEWithLSDA<ELFT>(
- [&](InputSection &s) { s.eqClass[0] = s.eqClass[1] = ++uniqueId; });
-
+ DenseMap<InputSection *, LsdaInfo> lsdaSecs;
+ DenseSet<InputSection *> lsdaConflict;
+ SmallVector<InputSection *, 0> lsdaUncomparable;
+ ctx.in.ehFrame->iterateFDEWithLSDATarget<ELFT>(
+ [&](InputSection &text, const CieInfo &cie, const Symbol *sym,
+ int64_t addend) {
+ if (!sym) {
+ lsdaUncomparable.push_back(&text);
+ return;
+ }
+ auto *d = dyn_cast<Defined>(sym);
+ InputSection *lsda =
+ d ? dyn_cast_or_null<InputSection>(d->section) : nullptr;
+ if (!lsda || d->value + addend != 0) {
+ lsdaUncomparable.push_back(&text);
+ return;
+ }
+ // Resolve the personality function. A compiler usually materializes
+ // the CIE personality pointer indirectly through a per-object pointer
+ // ("DW.ref.<personality>"); follow that indirection so that the same
+ // personality in different objects compares equal even though the
+ // local thunk symbols differ.
+ const Symbol *personality = nullptr;
+ int64_t personalityAddend = 0;
+ bool personalityKnown = true;
+ ArrayRef<Relocation> otherCieRels = cie.rels;
+ if (cie.personalityEncoding) {
+ if (cie.rels.empty()) {
+ personalityKnown = false;
+ } else {
+ const Relocation &r = cie.rels.front();
+ otherCieRels = cie.rels.drop_front();
+ if (*cie.personalityEncoding & llvm::dwarf::DW_EH_PE_indirect) {
+ auto *pd = dyn_cast<Defined>(r.sym);
+ auto *thunk =
+ pd ? dyn_cast_or_null<InputSection>(pd->section) : nullptr;
+ personality =
+ thunk ? relocTargetAt<ELFT>(thunk, pd->value + r.addend,
+ personalityAddend)
+ : nullptr;
+ if (!personality)
+ personalityKnown = false;
+ } else {
+ personality = r.sym;
+ personalityAddend = r.addend;
+ }
+ }
+ }
+ LsdaInfo info{lsda, cie.piece->data(), personalityKnown,
+ personality, personalityAddend, otherCieRels};
+ auto it = lsdaSecs.try_emplace(&text, info);
+ if (!it.second) {
+ const LsdaInfo &prev = it.first->second;
+ if (prev.lsda != lsda || prev.cieData != cie.piece->data() ||
+ prev.personalityKnown != personalityKnown ||
+ prev.personality != personality ||
+ prev.personalityAddend != personalityAddend ||
+ !cieRelsEqual(prev.otherCieRels, otherCieRels))
+ lsdaConflict.insert(&text);
+ }
+ });
+ for (InputSection *s : lsdaUncomparable)
+ s->eqClass[0] = s->eqClass[1] = ++uniqueId;
+ for (auto &[sec, info] : lsdaSecs) {
+ if (lsdaConflict.contains(sec))
+ sec->eqClass[0] = sec->eqClass[1] = ++uniqueId;
+ else
+ lsdaMap.try_emplace(sec, info);
+ }
// Collect sections to merge.
for (InputSectionBase *sec : ctx.inputSections) {
auto *s = dyn_cast<InputSection>(sec);
@@ -487,10 +704,16 @@ template <class ELFT> void ICF<ELFT>::run() {
}
}
- // Initially, we use hash values to partition sections.
+ // Initially, we use hash values to partition sections. Mix in the LSDA and
+ // CIE hashes so that sections that only differ in their exception-handling
+ // metadata start in different classes.
parallelForEach(sections, [&](InputSection *s) {
// Set MSB to 1 to avoid collisions with unique IDs.
- s->eqClass[0] = xxh3_64bits(s->content()) | (1U << 31);
+ uint64_t h = xxh3_64bits(s->content());
+ if (auto it = lsdaMap.find(s); it != lsdaMap.end())
+ h += xxh3_64bits(it->second.lsda->content()) +
+ xxh3_64bits(it->second.cieData);
+ s->eqClass[0] = h | (1U << 31);
});
// Perform 2 rounds of relocation hash propagation. 2 is an empirical value to
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 510d13251978d..4c1ccb498bb1c 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -292,34 +292,84 @@ template <endianness e> void EhFrameSection::addRecords(EhInputSection *sec) {
}
}
-// Used by ICF<ELFT>::handleLSDA(). This function is very similar to
-// EhFrameSection::addRecords().
+// Used by ICF<ELFT>::run() and findKeepUniqueSections(). This function is very
+// similar to EhFrameSection::addRecords().
+// Call fn for each live FDE whose CIE has an LSDA. `ciesWithLSDA` is a
+// scratch map, reused across input sections.
template <class ELFT>
-void EhFrameSection::iterateFDEWithLSDAAux(
- EhInputSection &sec, DenseSet<size_t> &ciesWithLSDA,
- llvm::function_ref<void(InputSection &)> fn) {
+void EhFrameSection::iterateFDEWithLSDATargetAux(
+ EhInputSection &sec, DenseMap<size_t, CieInfo> &ciesWithLSDA,
+ llvm::function_ref<void(InputSection &, const CieInfo &, const Symbol *,
+ int64_t)>
+ fn,
+ bool reportErrors) {
for (EhSectionPiece &cie : sec.cies)
- if (hasLSDA(cie))
- ciesWithLSDA.insert(cie.inputOff);
+ if (hasLSDA(cie, reportErrors)) {
+ // Parsing the personality encoding re-reads the augmentation; keep it
+ // silent so that this scan reports at most as many diagnostics as the
+ // pre-ICF scan did (EhFrameSection::finalizeContents reports the rest).
+ CieInfo info{
+ &cie, {}, getPersonalityEncoding(cie, /*reportErrors=*/false)};
+ ciesWithLSDA.try_emplace(cie.inputOff, info);
+ }
for (EhSectionPiece &fde : sec.fdes) {
uint32_t id = endian::read32<ELFT::Endianness>(fde.data().data() + 4);
- if (!ciesWithLSDA.contains(fde.inputOff + 4 - id))
+ auto cieIt = ciesWithLSDA.find(fde.inputOff + 4 - id);
+ if (cieIt == ciesWithLSDA.end())
continue;
-
- // The CIE has a LSDA argument. Call fn with d's section.
- if (Defined *d = isFdeLive(fde, sec.rels))
- if (auto *s = dyn_cast_or_null<InputSection>(d->section))
- fn(*s);
+ const CieInfo &cie = cieIt->second;
+ Defined *d = isFdeLive(fde, sec.rels);
+ if (!d)
+ continue;
+ auto *s = dyn_cast_or_null<InputSection>(d->section);
+ if (!s)
+ continue;
+ // The CIE's relocations (e.g. the personality function).
+ ArrayRef<Relocation> cieRels;
+ if (cie.piece->firstRelocation != unsigned(-1)) {
+ unsigned n = 0;
+ for (unsigned i = cie.piece->firstRelocation;
+ i != sec.rels.size() &&
+ sec.rels[i].offset < cie.piece->inputOff + cie.piece->size;
+ ++i)
+ ++n;
+ cieRels =
+ ArrayRef<Relocation>(sec.rels).slice(cie.piece->firstRelocation, n);
+ }
+ CieInfo info{cie.piece, cieRels, cie.personalityEncoding};
+ // The FDE's augmentation data contains one LSDA pointer, which is the
+ // relocation following the initial location relocation. If there is not
+ // exactly one such relocation, the FDE cannot be analyzed: pass a null
+ // symbol so that the caller does not mistake the section for one without
+ // an LSDA.
+ unsigned firstRel = fde.firstRelocation;
+ const Relocation *lsdaRel = nullptr;
+ unsigned nRels = 0;
+ if (firstRel != unsigned(-1))
+ for (unsigned i = firstRel + 1;
+ i != sec.rels.size() && sec.rels[i].offset < fde.inputOff + fde.size;
+ ++i) {
+ ++nRels;
+ lsdaRel = &sec.rels[i];
+ }
+ if (nRels != 1) {
+ fn(*s, info, nullptr, 0);
+ continue;
+ }
+ fn(*s, info, lsdaRel->sym, lsdaRel->addend);
}
}
template <class ELFT>
-void EhFrameSection::iterateFDEWithLSDA(
- llvm::function_ref<void(InputSection &)> fn) {
- DenseSet<size_t> ciesWithLSDA;
+void EhFrameSection::iterateFDEWithLSDATarget(
+ llvm::function_ref<void(InputSection &, const CieInfo &, const Symbol *,
+ int64_t)>
+ fn,
+ bool reportErrors) {
+ DenseMap<size_t, CieInfo> ciesWithLSDA;
for (EhInputSection *sec : sections) {
ciesWithLSDA.clear();
- iterateFDEWithLSDAAux<ELFT>(*sec, ciesWithLSDA, fn);
+ iterateFDEWithLSDATargetAux<ELFT>(*sec, ciesWithLSDA, fn, reportErrors);
}
}
@@ -4689,14 +4739,22 @@ template void elf::splitSections<ELF32BE>(Ctx &);
template void elf::splitSections<ELF64LE>(Ctx &);
template void elf::splitSections<ELF64BE>(Ctx &);
-template void EhFrameSection::iterateFDEWithLSDA<ELF32LE>(
- function_ref<void(InputSection &)>);
-template void EhFrameSection::iterateFDEWithLSDA<ELF32BE>(
- function_ref<void(InputSection &)>);
-template void EhFrameSection::iterateFDEWithLSDA<ELF64LE>(
- function_ref<void(InputSection &)>);
-template void EhFrameSection::iterateFDEWithLSDA<ELF64BE>(
- function_ref<void(InputSection &)>);
+template void EhFrameSection::iterateFDEWithLSDATarget<ELF32LE>(
+ function_ref<void(InputSection &, const CieInfo &, const Symbol *,
+ int64_t)>,
+ bool);
+template void EhFrameSection::iterateFDEWithLSDATarget<ELF32BE>(
+ function_ref<void(InputSection &, const CieInfo &, const Symbol *,
+ int64_t)>,
+ bool);
+template void EhFrameSection::iterateFDEWithLSDATarget<ELF64LE>(
+ function_ref<void(InputSection &, const CieInfo &, const Symbol *,
+ int64_t)>,
+ bool);
+template void EhFrameSection::iterateFDEWithLSDATarget<ELF64BE>(
+ function_ref<void(InputSection &, const CieInfo &, const Symbol *,
+ int64_t)>,
+ bool);
template class elf::SymbolTableSection<ELF32LE>;
template class elf::SymbolTableSection<ELF32BE>;
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index f8b895d9d2a99..c2d3ce7264a15 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -46,6 +46,14 @@ struct CieRecord {
SmallVector<EhSectionPiece *, 0> fdes;
};
+// CIE information used to decide whether two FDEs are equivalent for ICF.
+struct CieInfo {
+ const EhSectionPiece *piece;
+ ArrayRef<Relocation> rels;
+ // The 'P' augmentation encoding, if the CIE has a personality function.
+ std::optional<uint8_t> personalityEncoding;
+};
+
// Section for .eh_frame.
class EhFrameSection final : public SyntheticSection {
public:
@@ -68,8 +76,15 @@ class EhFrameSection final : public SyntheticSection {
};
ArrayRef<CieRecord *> getCieRecords() const { return cieRecords; }
+ // Call fn for each live FDE with LSDA. fn receives the section the FDE
+ // describes, the CIE the FDE references, the LSDA target symbol (null if the
+ // FDE's LSDA cannot be analyzed) and the relocation addend.
template <class ELFT>
- void iterateFDEWithLSDA(llvm::function_ref<void(InputSection &)> fn);
+ void iterateFDEWithLSDATarget(
+ llvm::function_ref<void(InputSection &, const CieInfo &, const Symbol *,
+ int64_t)>
+ fn,
+ bool reportErrors = true);
private:
// This is used only when parsing EhInputSection. We keep it here to avoid
@@ -78,9 +93,12 @@ class EhFrameSection final : public SyntheticSection {
template <llvm::endianness E> void addRecords(EhInputSection *s);
template <class ELFT>
- void iterateFDEWithLSDAAux(EhInputSection &sec,
- llvm::DenseSet<size_t> &ciesWithLSDA,
- llvm::function_ref<void(InputSection &)> fn);
+ void iterateFDEWithLSDATargetAux(
+ EhInputSection &sec, llvm::DenseMap<size_t, CieInfo> &ciesWithLSDA,
+ llvm::function_ref<void(InputSection &, const CieInfo &, const Symbol *,
+ int64_t)>
+ fn,
+ bool reportErrors);
CieRecord *addCie(EhSectionPiece &piece, ArrayRef<Relocation> rels);
Defined *isFdeLive(EhSectionPiece &piece, ArrayRef<Relocation> rels);
diff --git a/lld/docs/ReleaseNotes.md b/lld/docs/ReleaseNotes.md
index 827d09bec5776..8fe53c297186f 100644
--- a/lld/docs/ReleaseNotes.md
+++ b/lld/docs/ReleaseNotes.md
@@ -29,6 +29,9 @@ from the [LLVM releases web site](https://llvm.org/releases/).
### ELF Improvements
+* `--icf=all` now folds functions whose LSDA and CIE metadata are equivalent,
+ instead of disabling folding for every section with an LSDA.
+
### Breaking changes
### COFF Improvements
diff --git a/lld/test/ELF/icf-eh-frame.s b/lld/test/ELF/icf-eh-frame.s
index a09db0208194a..2b51208769b87 100644
--- a/lld/test/ELF/icf-eh-frame.s
+++ b/lld/test/ELF/icf-eh-frame.s
@@ -1,17 +1,24 @@
# REQUIRES: x86
-## Test that text sections with LSDA are not folded.
+## Test LSDA-aware ICF:
+## * text sections with the same LSDA are folded;
+## * text sections with different LSDA are not folded;
+## * text sections whose LSDA is at a nonzero offset in a shared
+## .gcc_except_table section are not folded.
## Test REL.
# RUN: llvm-mc -filetype=obj -triple=i386 %s -o %t1.o
-# RUN: ld.lld --icf=all %t1.o -o /dev/null --print-icf-sections | FileCheck %s --implicit-check-not=removing
+# RUN: ld.lld --icf=all %t1.o -o /dev/null --print-icf-sections | FileCheck %s --implicit-check-not=Z1[abgh]v
## Test RELA.
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t2.o
-# RUN: ld.lld --icf=all %t2.o -o /dev/null --print-icf-sections | FileCheck %s --implicit-check-not=removing
+# RUN: ld.lld --icf=all %t2.o -o /dev/null --print-icf-sections | FileCheck %s --implicit-check-not=Z1[abgh]v
-# CHECK: selected section {{.*}}.o:(.text.Z1cv)
-# CHECK-NEXT: removing identical section {{.*}}.o:(.text.Z1dv)
+# CHECK-DAG: selected section {{.*}}.o:(.text.Z1cv)
+# CHECK-DAG: removing identical section {{.*}}.o:(.text.Z1dv)
+# CHECK-DAG: selected section {{.*}}.o:(.text.Z1ev)
+# CHECK-DAG: removing identical section {{.*}}.o:(.text.Z1fv)
-.globl _Z1av, _Z1bv, _Z1cv, _Z1dv
+## Z1av and Z1bv have identical code but different LSDA sections.
+.globl _Z1av, _Z1bv
.section .text.Z1av,"ax", at progbits
_Z1av:
.cfi_startproc
@@ -22,30 +29,71 @@ _Z1av:
.section .text.Z1bv,"ax", at progbits
_Z1bv:
.cfi_startproc
- .cfi_lsda 27, .Lexception0
+ .cfi_lsda 27, .Lexception1
ret
.cfi_endproc
+## Z1cv and Z1dv share the same LSDA and are folded.
+.globl _Z1cv, _Z1dv
.section .text.Z1cv,"ax", at progbits
_Z1cv:
.cfi_startproc
- .cfi_signal_frame
+ .cfi_lsda 27, .Lexception2
ret
.cfi_endproc
.section .text.Z1dv,"ax", at progbits
_Z1dv:
+ .cfi_startproc
+ .cfi_lsda 27, .Lexception2
+ ret
+ .cfi_endproc
+
+## Z1ev and Z1fv have no LSDA and are folded.
+.globl _Z1ev, _Z1fv
+.section .text.Z1ev,"ax", at progbits
+_Z1ev:
+ .cfi_startproc
+ ret
+ .cfi_endproc
+
+.section .text.Z1fv,"ax", at progbits
+_Z1fv:
.cfi_startproc
ret
.cfi_endproc
-.section .gcc_except_table,"a", at progbits
-## The actual content does not matter.
+## Z1gv and Z1hv have identical code but their LSDAs are at different
+## offsets of the same .gcc_except_table section, so they are not eligible.
+.globl _Z1gv, _Z1hv
+.section .text.Z1gv,"ax", at progbits
+_Z1gv:
+ .cfi_startproc
+ .cfi_lsda 27, .Lexception3
+ ret
+ .cfi_endproc
+
+.section .text.Z1hv,"ax", at progbits
+_Z1hv:
+ .cfi_startproc
+ .cfi_lsda 27, .Lexception3b
+ ret
+ .cfi_endproc
+
+.section .gcc_except_table.0,"a", at progbits
.Lexception0:
+ .long 0
+
+.section .gcc_except_table.1,"a", at progbits
+.Lexception1:
+ .long 1
-## .rodata.Z1[ab]v reference .text.Z1[ab]v. Dont fold them.
-.section .rodata.Z1av,"a", at progbits
- .long .text.Z1av - .
+.section .gcc_except_table.2,"a", at progbits
+.Lexception2:
+ .long 2
-.section .rodata.Z1bv,"a", at progbits
- .long .text.Z1bv - .
+.section .gcc_except_table.3,"a", at progbits
+.Lexception3:
+ .long 3
+.Lexception3b:
+ .long 4
diff --git a/lld/test/ELF/icf-lsda-extra-fde-reloc.s b/lld/test/ELF/icf-lsda-extra-fde-reloc.s
new file mode 100644
index 0000000000000..400fa548b8741
--- /dev/null
+++ b/lld/test/ELF/icf-lsda-extra-fde-reloc.s
@@ -0,0 +1,116 @@
+# REQUIRES: x86
+## An FDE with more than one relocation after the PC-begin relocation cannot be
+## analyzed. LSDA-aware ICF must conservatively keep the function unique rather
+## than treating it as a function without an LSDA. RISC-V linker relaxation
+## (R_RISCV_ADD32/R_RISCV_SUB32 on the PC range) is a realistic producer of
+## such FDEs.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o
+# RUN: ld.lld --icf=all --print-icf-sections %t.o -o /dev/null | \
+# RUN: FileCheck %s --implicit-check-not=Z1av --implicit-check-not=Z1bv
+
+## _Z1av and _Z1bv have identical code and different LSDA, but their FDEs carry
+## an extra relocation, so they must not fold. _Z1cv and _Z1dv exercise the same
+## hand-written .eh_frame shape without the extra relocation: their LSDA is
+## identical and they fold.
+# CHECK-DAG: selected section {{.*}}.o:(.text.Z1cv)
+# CHECK-DAG: removing identical section {{.*}}.o:(.text.Z1dv)
+
+.globl _Z1av, _Z1bv, _Z1cv, _Z1dv
+.section .text.Z1av,"ax", at progbits
+_Z1av:
+ ret
+.section .text.Z1bv,"ax", at progbits
+_Z1bv:
+ ret
+.section .text.Z1cv,"ax", at progbits
+_Z1cv:
+ ret
+.section .text.Z1dv,"ax", at progbits
+_Z1dv:
+ ret
+
+.section .gcc_except_table.a,"a", at progbits
+.Llsda_a:
+ .long 0x11111111
+.section .gcc_except_table.b,"a", at progbits
+.Llsda_b:
+ .long 0x22222222
+.section .gcc_except_table.c,"a", at progbits
+.Llsda_c:
+ .long 0x33333333
+.section .gcc_except_table.d,"a", at progbits
+.Llsda_d:
+ .long 0x33333333
+
+## The extra relocation target.
+.section .rodata.extra,"a", at progbits
+extra:
+ .byte 0
+
+.section .text.per,"ax", at progbits
+per:
+ ret
+
+## Hand-written .eh_frame: an FDE with an extra relocation cannot be produced
+## with .cfi_lsda alone.
+.section .eh_frame,"a", at progbits
+.Lcie:
+ .long .Lcie_end - .Lcie_begin
+.Lcie_begin:
+ .long 0 # CIE id
+ .byte 1 # version
+ .asciz "zPLR"
+ .uleb128 1 # code alignment
+ .sleb128 -8 # data alignment
+ .byte 16 # return address register
+ .uleb128 7 # augmentation length
+ .byte 0x1b # personality encoding
+ .long per - .
+ .byte 0x1b # LSDA encoding
+ .byte 0x1b # FDE encoding
+.Lcie_end:
+
+## _Z1av: PC-begin, LSDA and an extra relocation.
+.Lfde_a:
+ .long .Lfde_a_end - .Lfde_a_begin
+.Lfde_a_begin:
+ .long .Lfde_a_begin - .Lcie
+ .long _Z1av - . # PC begin
+ .long 1 # range
+ .uleb128 4 # augmentation length
+ .long .Llsda_a - . # LSDA
+ .long extra - . # extra relocation
+.Lfde_a_end:
+
+## _Z1bv: PC-begin, LSDA and an extra relocation.
+.Lfde_b:
+ .long .Lfde_b_end - .Lfde_b_begin
+.Lfde_b_begin:
+ .long .Lfde_b_begin - .Lcie
+ .long _Z1bv - .
+ .long 1
+ .uleb128 4
+ .long .Llsda_b - .
+ .long extra - .
+.Lfde_b_end:
+
+## _Z1cv and _Z1dv: PC-begin and LSDA only.
+.Lfde_c:
+ .long .Lfde_c_end - .Lfde_c_begin
+.Lfde_c_begin:
+ .long .Lfde_c_begin - .Lcie
+ .long _Z1cv - .
+ .long 1
+ .uleb128 4
+ .long .Llsda_c - .
+.Lfde_c_end:
+
+.Lfde_d:
+ .long .Lfde_d_end - .Lfde_d_begin
+.Lfde_d_begin:
+ .long .Lfde_d_begin - .Lcie
+ .long _Z1dv - .
+ .long 1
+ .uleb128 4
+ .long .Llsda_d - .
+.Lfde_d_end:
diff --git a/lld/test/ELF/icf-lsda-landing-pad.s b/lld/test/ELF/icf-lsda-landing-pad.s
new file mode 100644
index 0000000000000..f5c20301277ec
--- /dev/null
+++ b/lld/test/ELF/icf-lsda-landing-pad.s
@@ -0,0 +1,70 @@
+# REQUIRES: x86
+## LSDA sections that relocate to their function's landing pad are compared
+## with relocations taken into account: two .gcc_except_table sections with the
+## same bytes are equivalent only if their relocation targets are equivalent.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o
+# RUN: ld.lld --icf=all --print-icf-sections %t.o -o /dev/null | \
+# RUN: FileCheck %s --implicit-check-not=Z1cv --implicit-check-not=Z1dv
+
+## _Z1av and _Z1bv have identical code and their LSDA sections point to
+## equivalent landing pads: fold.
+# CHECK-DAG: selected section {{.*}}.o:(.text.Z1av)
+# CHECK-DAG: removing identical section {{.*}}.o:(.text.Z1bv)
+
+.globl _Z1av, _Z1bv, _Z1cv, _Z1dv
+.section .text.Z1av,"ax", at progbits
+_Z1av:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_a
+ ret
+ .cfi_endproc
+.section .text.Z1bv,"ax", at progbits
+_Z1bv:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_b
+ ret
+ .cfi_endproc
+
+## _Z1cv and _Z1dv have identical code, but their LSDA sections point to
+## different landing pads: do not fold.
+.section .text.Z1cv,"ax", at progbits
+_Z1cv:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_c
+ ret
+ .cfi_endproc
+.section .text.Z1dv,"ax", at progbits
+_Z1dv:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_d
+ ret
+ .cfi_endproc
+
+.section .text.lpad.a,"ax", at progbits
+.Lpad_a:
+ ret
+.section .text.lpad.b,"ax", at progbits
+.Lpad_b:
+ ret
+.section .text.lpad.c,"ax", at progbits
+.Lpad_c:
+ nop
+ ret
+.section .text.lpad.d,"ax", at progbits
+.Lpad_d:
+ nop
+ nop
+ ret
+
+.section .gcc_except_table.a,"a", at progbits
+.Llsda_a:
+ .long .Lpad_a - .
+.section .gcc_except_table.b,"a", at progbits
+.Llsda_b:
+ .long .Lpad_b - .
+.section .gcc_except_table.c,"a", at progbits
+.Llsda_c:
+ .long .Lpad_c - .
+.section .gcc_except_table.d,"a", at progbits
+.Llsda_d:
+ .long .Lpad_d - .
diff --git a/lld/test/ELF/icf-lsda-personality-indirect-cross.s b/lld/test/ELF/icf-lsda-personality-indirect-cross.s
new file mode 100644
index 0000000000000..18c4b61117113
--- /dev/null
+++ b/lld/test/ELF/icf-lsda-personality-indirect-cross.s
@@ -0,0 +1,75 @@
+# REQUIRES: x86
+## Test that the CIE personality of functions in different object files is
+## compared through each object's DW_EH_PE_indirect thunk: thunks resolving to
+## the same personality fold, a thunk resolving to a different personality
+## prevents folding.
+
+# RUN: split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/a.s -o %t/a.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/b.s -o %t/b.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/c.s -o %t/c.o
+# RUN: ld.lld --icf=all --print-icf-sections %t/a.o %t/b.o -o /dev/null | \
+# RUN: FileCheck %s --check-prefix=FOLD
+# RUN: ld.lld --icf=all --print-icf-sections %t/a.o %t/c.o -o /dev/null | \
+# RUN: FileCheck %s --check-prefix=NOFOLD --implicit-check-not=Z1av --implicit-check-not=Z1cv
+
+# FOLD-DAG: selected section {{.*}}a.o:(.text.Z1av)
+# FOLD-DAG: removing identical section {{.*}}b.o:(.text.Z1bv)
+## The equivalent exception tables still fold even though the functions do not.
+# NOFOLD-DAG: selected section {{.*}}a.o:(.gcc_except_table.a)
+# NOFOLD-DAG: removing identical section {{.*}}c.o:(.gcc_except_table.c)
+
+#--- a.s
+.globl _Z1av, __gxx_personality_v0
+.section .text.Z1av,"ax", at progbits
+_Z1av:
+ .cfi_startproc
+ .cfi_personality 0x9b, thunk_a
+ .cfi_lsda 27, .Llsda_a
+ ret
+ .cfi_endproc
+.section .data.rel.ro.a,"aw", at progbits
+thunk_a:
+ .quad __gxx_personality_v0
+.section .gcc_except_table.a,"a", at progbits
+.Llsda_a:
+ .long 0x11111111
+.section .text.per,"ax", at progbits
+__gxx_personality_v0:
+ ret
+
+#--- b.s
+.globl _Z1bv
+.section .text.Z1bv,"ax", at progbits
+_Z1bv:
+ .cfi_startproc
+ .cfi_personality 0x9b, thunk_b
+ .cfi_lsda 27, .Llsda_b
+ ret
+ .cfi_endproc
+.section .data.rel.ro.b,"aw", at progbits
+thunk_b:
+ .quad __gxx_personality_v0
+.section .gcc_except_table.b,"a", at progbits
+.Llsda_b:
+ .long 0x11111111
+
+#--- c.s
+.globl _Z1cv
+.section .text.Z1cv,"ax", at progbits
+_Z1cv:
+ .cfi_startproc
+ .cfi_personality 0x9b, thunk_c
+ .cfi_lsda 27, .Llsda_c
+ ret
+ .cfi_endproc
+.section .data.rel.ro.c,"aw", at progbits
+thunk_c:
+ .quad other_personality
+.section .gcc_except_table.c,"a", at progbits
+.Llsda_c:
+ .long 0x11111111
+.section .text.per2,"ax", at progbits
+other_personality:
+ nop
+ ret
diff --git a/lld/test/ELF/icf-lsda-personality-indirect.s b/lld/test/ELF/icf-lsda-personality-indirect.s
new file mode 100644
index 0000000000000..5019011fcf541
--- /dev/null
+++ b/lld/test/ELF/icf-lsda-personality-indirect.s
@@ -0,0 +1,92 @@
+# REQUIRES: x86
+## Test that the CIE personality is compared through the DW_EH_PE_indirect
+## pointer a compiler emits for it (a per-object "DW.ref" thunk): functions
+## whose thunks resolve to the same personality fold, functions whose thunks
+## resolve to different personalities do not.
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o
+# RUN: ld.lld --icf=all %t.o -o /dev/null --print-icf-sections | \
+# RUN: FileCheck %s --implicit-check-not=Z1av --implicit-check-not=Z1bv
+
+## Z1av/Z1bv use thunks to different personalities: no fold.
+## Z1cv/Z1dv use thunks to the same personality: fold.
+
+# CHECK-DAG: selected section {{.*}}.o:(.text.Z1cv)
+# CHECK-DAG: removing identical section {{.*}}.o:(.text.Z1dv)
+
+.globl _Z1av, _Z1bv, _Z1cv, _Z1dv
+
+.section .text.Z1av,"ax", at progbits
+_Z1av:
+ .cfi_startproc
+ .cfi_personality 0x9b, thunk_a
+ .cfi_lsda 27, .Llsda_a
+ ret
+ .cfi_endproc
+
+.section .text.Z1bv,"ax", at progbits
+_Z1bv:
+ .cfi_startproc
+ .cfi_personality 0x9b, thunk_b
+ .cfi_lsda 27, .Llsda_b
+ ret
+ .cfi_endproc
+
+.section .text.Z1cv,"ax", at progbits
+_Z1cv:
+ .cfi_startproc
+ .cfi_personality 0x9b, thunk_c
+ .cfi_lsda 27, .Llsda_c
+ ret
+ .cfi_endproc
+
+.section .text.Z1dv,"ax", at progbits
+_Z1dv:
+ .cfi_startproc
+ .cfi_personality 0x9b, thunk_d
+ .cfi_lsda 27, .Llsda_d
+ ret
+ .cfi_endproc
+
+.section .data.rel.ro.a,"aw", at progbits
+thunk_a:
+ .quad per1
+
+.section .data.rel.ro.b,"aw", at progbits
+thunk_b:
+ .quad per2
+
+.section .data.rel.ro.c,"aw", at progbits
+thunk_c:
+ .quad per1
+
+.section .data.rel.ro.d,"aw", at progbits
+thunk_d:
+ .quad per1
+
+.section .gcc_except_table.a,"a", at progbits
+.Llsda_a:
+ .long 0x11111111
+
+.section .gcc_except_table.b,"a", at progbits
+.Llsda_b:
+ .long 0x11111111
+
+.section .gcc_except_table.c,"a", at progbits
+.Llsda_c:
+ .long 0x22222222
+
+.section .gcc_except_table.d,"a", at progbits
+.Llsda_d:
+ .long 0x22222222
+
+.section .text.per1,"ax", at progbits
+.globl per1
+per1:
+ ret
+
+.section .text.per2,"ax", at progbits
+.globl per2
+per2:
+ nop
+ ret
diff --git a/lld/test/ELF/icf-lsda-personality.s b/lld/test/ELF/icf-lsda-personality.s
new file mode 100644
index 0000000000000..4faa3ebcd61cd
--- /dev/null
+++ b/lld/test/ELF/icf-lsda-personality.s
@@ -0,0 +1,120 @@
+# REQUIRES: x86
+## Test that LSDA-aware ICF also compares the CIE (personality, encodings, CFI):
+## * identical code + LSDA but different CIE personalities are not folded;
+## * identical code + LSDA + same personality are folded;
+## * --keep-unique on an LSDA symbol keeps the associated functions unique.
+
+## Test REL.
+# RUN: llvm-mc -filetype=obj -triple=i386 %s -o %t1.o
+# RUN: ld.lld --icf=all %t1.o -o /dev/null --print-icf-sections | \
+# RUN: FileCheck %s --implicit-check-not=Z1av --implicit-check-not=Z1bv
+## Test RELA.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t2.o
+# RUN: ld.lld --icf=all %t2.o -o /dev/null --print-icf-sections | \
+# RUN: FileCheck %s --implicit-check-not=Z1av --implicit-check-not=Z1bv
+## --keep-unique on an exported LSDA symbol prevents folding of its functions.
+# RUN: ld.lld --icf=all --keep-unique=lsdae %t2.o -o /dev/null --print-icf-sections | \
+# RUN: FileCheck %s --check-prefix=KEEPUNIQUE --implicit-check-not=Z1ev --implicit-check-not=Z1fv
+
+# CHECK-DAG: selected section {{.*}}.o:(.text.Z1cv)
+# CHECK-DAG: removing identical section {{.*}}.o:(.text.Z1dv)
+## Z1ev/Z1fv have an exported LSDA symbol; ICF may still fold them because the
+## LSDA and CIE are equivalent.
+# CHECK-DAG: selected section {{.*}}.o:(.text.Z1ev)
+# CHECK-DAG: removing identical section {{.*}}.o:(.text.Z1fv)
+
+# KEEPUNIQUE-DAG: selected section {{.*}}.o:(.text.Z1cv)
+# KEEPUNIQUE-DAG: removing identical section {{.*}}.o:(.text.Z1dv)
+
+.globl _Z1av, _Z1bv, _Z1cv, _Z1dv, _Z1ev, _Z1fv
+
+## Z1av and Z1bv have identical code and identical LSDA, but different CIE
+## personalities, so they must not be folded.
+.section .text.Z1av,"ax", at progbits
+_Z1av:
+ .cfi_startproc
+ .cfi_personality 27, per1
+ .cfi_lsda 27, .Llsda_a
+ ret
+ .cfi_endproc
+
+.section .text.Z1bv,"ax", at progbits
+_Z1bv:
+ .cfi_startproc
+ .cfi_personality 27, per2
+ .cfi_lsda 27, .Llsda_b
+ ret
+ .cfi_endproc
+
+## Z1cv and Z1dv share the personality and have identical LSDA: fold.
+.section .text.Z1cv,"ax", at progbits
+_Z1cv:
+ .cfi_startproc
+ .cfi_personality 27, per1
+ .cfi_lsda 27, .Llsda_c
+ ret
+ .cfi_endproc
+
+.section .text.Z1dv,"ax", at progbits
+_Z1dv:
+ .cfi_startproc
+ .cfi_personality 27, per1
+ .cfi_lsda 27, .Llsda_d
+ ret
+ .cfi_endproc
+
+## Z1ev and Z1fv are folded unless --keep-unique keeps their LSDA unique.
+.section .text.Z1ev,"ax", at progbits
+_Z1ev:
+ .cfi_startproc
+ .cfi_personality 27, per1
+ .cfi_lsda 27, lsdae
+ ret
+ .cfi_endproc
+
+.section .text.Z1fv,"ax", at progbits
+_Z1fv:
+ .cfi_startproc
+ .cfi_personality 27, per1
+ .cfi_lsda 27, lsdaef
+ ret
+ .cfi_endproc
+
+.section .gcc_except_table.a,"a", at progbits
+.Llsda_a:
+ .long 0x11111111
+
+.section .gcc_except_table.b,"a", at progbits
+.Llsda_b:
+ .long 0x11111111
+
+.section .gcc_except_table.c,"a", at progbits
+.Llsda_c:
+ .long 0x22222222
+
+.section .gcc_except_table.d,"a", at progbits
+.Llsda_d:
+ .long 0x22222222
+
+.section .gcc_except_table.e,"a", at progbits
+.globl lsdae
+lsdae:
+ .long 0x33333333
+
+.section .gcc_except_table.f,"a", at progbits
+.globl lsdaef
+lsdaef:
+ .long 0x33333333
+
+## Personalities referenced by the CIEs above. They are deliberately
+## different (different contents) so that they are not folded into each other.
+.section .text.per1,"ax", at progbits
+.globl per1
+per1:
+ ret
+
+.section .text.per2,"ax", at progbits
+.globl per2
+per2:
+ nop
+ ret
diff --git a/lld/test/ELF/icf-lsda-safe.s b/lld/test/ELF/icf-lsda-safe.s
new file mode 100644
index 0000000000000..5841f26c8dc2a
--- /dev/null
+++ b/lld/test/ELF/icf-lsda-safe.s
@@ -0,0 +1,89 @@
+# REQUIRES: x86
+## --icf=safe and LSDA-aware ICF:
+## * an LSDA-bearing function that is not address-significant is foldable;
+## * an address-significant (addrsig or exported) function stays unique even
+## when its LSDA and CIE are equivalent;
+## * an object without an address-significance table stays conservative.
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o
+# RUN: ld.lld --icf=safe --print-icf-sections %t.o -o /dev/null | \
+# RUN: FileCheck %s --implicit-check-not=Z1cv --implicit-check-not=Z1dv \
+# RUN: --implicit-check-not=Z1ev --implicit-check-not=Z1fv
+# RUN: ld.lld -shared --icf=safe --print-icf-sections %t.o -o /dev/null | \
+# RUN: FileCheck %s --check-prefix=EXPORT --implicit-check-not=Z1av --implicit-check-not=Z1bv
+# RUN: llvm-objcopy --remove-section=.llvm_addrsig %t.o %t-noaddrsig.o
+# RUN: ld.lld --icf=safe --print-icf-sections %t-noaddrsig.o -o /dev/null | \
+# RUN: FileCheck %s --check-prefix=NOADDRSIG --implicit-check-not=Z1av --implicit-check-not=Z1bv
+
+# CHECK-DAG: selected section {{.*}}.o:(.text.Z1av)
+# CHECK-DAG: removing identical section {{.*}}.o:(.text.Z1bv)
+# EXPORT-DAG: selected section {{.*}}.o:(.gcc_except_table.a)
+# EXPORT-DAG: removing identical section {{.*}}.o:(.gcc_except_table.b)
+# NOADDRSIG-DAG: selected section {{.*}}.o:(.gcc_except_table.a)
+# NOADDRSIG-DAG: removing identical section {{.*}}.o:(.gcc_except_table.b)
+
+.globl _Z1av, _Z1bv, _Z1cv, _Z1dv, _Z1ev, _Z1fv
+
+## Z1av/Z1bv: identical code and LSDA, not address-significant: fold.
+.section .text.Z1av,"ax", at progbits
+_Z1av:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_a
+ ret
+ .cfi_endproc
+.section .text.Z1bv,"ax", at progbits
+_Z1bv:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_b
+ ret
+ .cfi_endproc
+
+## Z1cv/Z1dv: identical code and LSDA, but _Z1cv is address-significant.
+.section .text.Z1cv,"ax", at progbits
+_Z1cv:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_c
+ ret
+ .cfi_endproc
+.section .text.Z1dv,"ax", at progbits
+_Z1dv:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_d
+ ret
+ .cfi_endproc
+
+## Z1ev/Z1fv: different LSDA: never fold.
+.section .text.Z1ev,"ax", at progbits
+_Z1ev:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_e
+ ret
+ .cfi_endproc
+.section .text.Z1fv,"ax", at progbits
+_Z1fv:
+ .cfi_startproc
+ .cfi_lsda 27, .Llsda_f
+ ret
+ .cfi_endproc
+
+.section .gcc_except_table.a,"a", at progbits
+.Llsda_a:
+ .long 0x11111111
+.section .gcc_except_table.b,"a", at progbits
+.Llsda_b:
+ .long 0x11111111
+.section .gcc_except_table.c,"a", at progbits
+.Llsda_c:
+ .long 0x22222222
+.section .gcc_except_table.d,"a", at progbits
+.Llsda_d:
+ .long 0x22222222
+.section .gcc_except_table.e,"a", at progbits
+.Llsda_e:
+ .long 0x33333333
+.section .gcc_except_table.f,"a", at progbits
+.Llsda_f:
+ .long 0x44444444
+
+.addrsig
+.addrsig_sym _Z1cv
More information about the llvm-commits
mailing list