[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 06:14:19 PDT 2026


https://github.com/artemkulyk created https://github.com/llvm/llvm-project/pull/224929

# [lld][ELF] Fold LSDA-bearing sections with equivalent LSDA and CIE

## Problem

`--icf=all` never folds a text section whose `.eh_frame` FDE has an LSDA.
Commit 21b4f8060ab3 ("[ELF] --icf: don't fold text sections with LSDA",
D84610) introduced this to fix PR36272/PR46835: two functions with identical
code can have different `catch` blocks, and folding them would make one
function unwind through the other function's exception table.  The commit
noted that "there may be some percentage we can reclaim without affecting
correctness, if we analyze .eh_frame and .gcc_except_table sections" (for
ClickHouse it estimated 9% -> 5% ICF efficiency).

## Why current lld misses valid folds

The current rule disables folding for *every* LSDA-bearing section, including
duplicated functions whose LSDA sections and CIE metadata are identical.  This
is the common case for template instantiations and duplicated functions in
exception-heavy C++ programs built with `-ffunction-sections` (clang gives each
function its own `.gcc_except_table.<sym>` section at offset 0).

lld/MachO already folds FDEs with identical unwinding data
(#216895, #220155); this is the ELF analogue.

## Semantic correctness model

Two text sections `A` and `B` are folded only if all of the following hold:

1. **Existing ICF rules**: identical contents and equivalent relocations
   (`equalsConstant`/`equalsVariable`).
2. **LSDA equivalence**: the FDEs of `A` and `B` reference `.gcc_except_table`
   sections in the same ICF equivalence class (identical contents and
   equivalent relocation targets).  A section with an LSDA never equals a
   section without one.
3. **CIE equivalence**: the CIEs referenced by the two FDEs have identical
   bytes, the same personality function (and addend) and identical remaining
   CIE relocations.  The personality is resolved through the
   `DW_EH_PE_indirect` pointer-to-pointer thunk (`DW.ref.<personality>`) a
   compiler emits, so the same personality in different objects compares
   equal even though the local thunk symbols differ.

Under these conditions the surviving function's FDE references byte-identical
LSDA data with the same personality and encodings, so the unwinder observes
the same behavior for either function.  Anything that cannot be analyzed
stays unfolded:

* LSDA targets that are not a section (absolute symbols, thunks);
* shared `.gcc_except_table` sections referenced at a nonzero offset
  (this change intentionally keeps the conservative offset-0 rule);
* text sections whose multiple FDEs disagree about LSDA/CIE;
* sections kept unique by `--keep-unique`, by the dynamic symbol table or by
  a real `.llvm_addrsig` (the change only clears the conservative fallback
  marking that lld applies to objects without an address-significance table).

## What changed

* `lld/ELF/EhFrame.{h,cpp}`: expose the CIE personality encoding; allow
  suppressing `.eh_frame` diagnostics in pre-passes
  (`EhFrameSection::finalizeContents` still reports them once).
* `lld/ELF/SyntheticSections.{h,cpp}`: new `iterateFDEWithLSDATarget` that
  reports, for each live FDE with an LSDA, the described section, the CIE and
  the LSDA symbol/addend.  The existing `iterateFDEWithLSDA` is reimplemented
  on top of it (no duplicated FDE scanning).
* `lld/ELF/ICF.cpp`: build the per-section LSDA/CIE information, compare it in
  `equalsVariable`, and mix the LSDA/CIE hashes into the initial partition.
* `lld/ELF/Driver.cpp`: clear the conservative address-significance marking of
  LSDA sections so they can be folded; explicit `--keep-unique`, dynsym and
  `.llvm_addrsig` markings still win.

## Tests

* `lld/test/ELF/icf-eh-frame.s` (extended): positive fold with the same LSDA;
  different LSDA not folded; shared exception table referenced at a nonzero
  offset not folded; functions without LSDA still fold.
* `lld/test/ELF/icf-lsda-personality.s` (new): identical code+LSDA with
  different personalities not folded; same personality folded (including an
  exported LSDA symbol, exercising the conservative-marking clearing);
  `--keep-unique` on an LSDA symbol keeps its functions unique.
* `lld/test/ELF/icf-lsda-personality-indirect.s` (new): `DW_EH_PE_indirect`
  personality thunks resolving to the same/different personalities.

Both REL (i386) and RELA (x86-64) are covered.  `llvm-lit` passes for the
three tests plus the surrounding ICF tests.

## Measured impact (benchmark evidence, separate from correctness)

Frozen-input A/B: A = pristine lld 22.1.8, B = the same revision with only
this change; identical ThinLTO inputs and link command.  All numbers are
B relative to A:

| project (release recipe) | effect |
|---|---|
| ClickHouse v26.8.7.19-lts (official recipe, ThinLTO, `--icf=all`) | final artifact **-7.19%**, `.text` -10.06%, `.eh_frame` -12.89%, `.eh_frame_hdr` -12.07%, `.gcc_except_table` -7.66%, -48,353 FDEs, -44,349 LSDA FDEs; link time and peak RSS unchanged |
| Proton v3.0.31 (ClickHouse fork, same flags) | stripped artifact **-8.81%** |
| dwarfs v0.15.7 (clang, lld, `--icf=all`) | **-3.32%** |
| Next.js/Turbopack native SWC (Rust, ThinLTO, `--icf=all`) | **-2.20%** (`.gcc_except_table` -33.5%) |
| capnproto 1.0.2 with `--icf=all` forced (opt-in) | -6.86% |

## Validation summary

* Differential runtime testing of A/B binaries: ClickHouse (39-query battery
  with 25 exception/error paths, server smoke, official stateless subset,
  multithreaded stress), Proton (30-query native-dialect battery, streaming,
  start/stop), dwarfs (2,024-file image create/check/extract, corrupt inputs,
  FUSE), Next.js/SWC (binding API, error/panic paths, 200 sequential + 50
  concurrent transforms), capnproto (225+135 upstream tests), MegEngine
  (upstream CPU tests).  A and B are behaviorally identical in all cases
  (only documented nondeterminism differs).
* Link cost: A and B link time/user CPU/peak RSS are indistinguishable.
* No new diagnostics: malformed `.eh_frame` reporting is unchanged (the
  pre-pass is silent, `finalizeContents` reports as before).

## Known limitations / non-goals

* Offset-0 rule only: a shared `.gcc_except_table` section referenced at a
  nonzero offset is still not foldable.  A range-level rule would need to
  parse LSDA records and compare their relocations; not attempted here.
* Non-section LSDA targets (absolute symbols, thunks) remain uncomparable.
* A text section whose FDEs reference different LSDAs/CIEs stays uneligible.
* No changes to `--icf=safe` semantics beyond not blocking equivalent LSDA
  folds that are already permitted by address-significance information.

## AI tool disclosure

The code and text were developed with an AI coding assistant and reviewed,
tested and edited by the human contributor, who is responsible for the
content.  Per the LLVM AI tool policy, this PR description is the disclosure
of tool usage (the commit message carries no `Assisted-by:` trailer).


>From 26c6154e01a8d94a12013c7ccb0fca128d40b1aa 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.

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.
---
 lld/ELF/Driver.cpp                           |  43 +++-
 lld/ELF/EhFrame.cpp                          |  47 +++-
 lld/ELF/EhFrame.h                            |   9 +-
 lld/ELF/ICF.cpp                              | 243 ++++++++++++++++++-
 lld/ELF/SyntheticSections.cpp                | 101 ++++++--
 lld/ELF/SyntheticSections.h                  |  26 +-
 lld/docs/ReleaseNotes.md                     |   3 +
 lld/test/ELF/icf-eh-frame.s                  |  78 ++++--
 lld/test/ELF/icf-lsda-personality-indirect.s |  92 +++++++
 lld/test/ELF/icf-lsda-personality.s          | 120 +++++++++
 10 files changed, 705 insertions(+), 57 deletions(-)
 create mode 100644 lld/test/ELF/icf-lsda-personality-indirect.s
 create mode 100644 lld/test/ELF/icf-lsda-personality.s

diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index c5e8d1be2e4e9..e863e28ff7ec6 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,32 @@ 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) {
+        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..cb0726c3583dd 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,77 @@ 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) {
+        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 &kv : lsdaSecs) {
+    if (lsdaConflict.contains(kv.first))
+      kv.first->eqClass[0] = kv.first->eqClass[1] = ++uniqueId;
+    else
+      lsdaMap.try_emplace(kv.first, kv.second);
+  }
   // Collect sections to merge.
   for (InputSectionBase *sec : ctx.inputSections) {
     auto *s = dyn_cast<InputSection>(sec);
@@ -487,10 +700,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..c42bf76aa6f52 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -294,35 +294,89 @@ template <endianness e> void EhFrameSection::addRecords(EhInputSection *sec) {
 
 // Used by ICF<ELFT>::handleLSDA(). 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 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.
+    unsigned firstRel = fde.firstRelocation;
+    if (firstRel == unsigned(-1))
+      continue;
+    const Relocation *lsdaRel = nullptr;
+    unsigned nRels = 0;
+    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)
+      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};
+    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);
   }
 }
 
+template <class ELFT>
+void EhFrameSection::iterateFDEWithLSDA(
+    llvm::function_ref<void(InputSection &)> fn) {
+  iterateFDEWithLSDATarget<ELFT>([&](InputSection &s, const CieInfo &,
+                                     const Symbol &, int64_t) { fn(s); });
+}
+
 static void writeCieFde(Ctx &ctx, uint8_t *buf, ArrayRef<uint8_t> d) {
   memcpy(buf, d.data(), d.size());
   // Fix the size field. -4 since size does not include the size field itself.
@@ -4698,6 +4752,23 @@ template void EhFrameSection::iterateFDEWithLSDA<ELF64LE>(
 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>;
 template class elf::SymbolTableSection<ELF64LE>;
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index f8b895d9d2a99..9e763e9fa258a 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:
@@ -70,6 +78,15 @@ class EhFrameSection final : public SyntheticSection {
   ArrayRef<CieRecord *> getCieRecords() const { return cieRecords; }
   template <class ELFT>
   void iterateFDEWithLSDA(llvm::function_ref<void(InputSection &)> fn);
+  // Call fn for each live FDE with LSDA. fn receives the section the FDE
+  // describes, the CIE the FDE references, the LSDA target symbol and the
+  // relocation addend.
+  template <class ELFT>
+  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 +95,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..c4ef31a46b2c4 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 LSBAs 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-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



More information about the llvm-commits mailing list