[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