[lld] 21b4f80 - [ELF] --icf: don't fold text sections with LSDA
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 5 09:22:08 PDT 2020
Author: Fangrui Song
Date: 2020-08-05T09:16:28-07:00
New Revision: 21b4f8060ab3b313009cf8995140edd5adbf7a4a
URL: https://github.com/llvm/llvm-project/commit/21b4f8060ab3b313009cf8995140edd5adbf7a4a
DIFF: https://github.com/llvm/llvm-project/commit/21b4f8060ab3b313009cf8995140edd5adbf7a4a.diff
LOG: [ELF] --icf: don't fold text sections with LSDA
Fix PR36272 and PR46835
A .eh_frame FDE references a text section and (optionally) a LSDA (in
.gcc_except_table). Even if two text sections have identical content and
relocations (e.g. a() and b()), we cannot fold them if their LSDA are different.
```
void foo();
void a() {
try { foo(); } catch (int) { }
}
void b() {
try { foo(); } catch (float) { }
}
```
Scan .eh_frame pieces with LSDA and disallow referenced text sections to be
folded. If two .gcc_except_table have identical semantics (usually identical
content with PC-relative encoding), we will lose folding opportunity.
For ClickHouse (an exception-heavy application), this can reduce --icf=all efficiency
from 9% to 5%. There may be some percentage we can reclaim without affecting
correctness, if we analyze .eh_frame and .gcc_except_table sections.
gold 2.24 implemented a more complex fix (resolution to
https://sourceware.org/bugzilla/show_bug.cgi?id=21066) which combines the
checksum of .eh_frame CIE/FDE pieces.
Reviewed By: grimar
Differential Revision: https://reviews.llvm.org/D84610
Added:
lld/test/ELF/icf-eh-frame.s
Modified:
lld/ELF/EhFrame.cpp
lld/ELF/EhFrame.h
lld/ELF/ICF.cpp
lld/ELF/InputSection.h
lld/ELF/SyntheticSections.cpp
lld/ELF/SyntheticSections.h
Removed:
################################################################################
diff --git a/lld/ELF/EhFrame.cpp b/lld/ELF/EhFrame.cpp
index f97e3b604eb7..4596a014838e 100644
--- a/lld/ELF/EhFrame.cpp
+++ b/lld/ELF/EhFrame.cpp
@@ -38,6 +38,7 @@ class EhReader {
EhReader(InputSectionBase *s, ArrayRef<uint8_t> d) : isec(s), d(d) {}
size_t readEhRecordSize();
uint8_t getFdeEncoding();
+ bool hasLSDA();
private:
template <class P> void failOn(const P *loc, const Twine &msg) {
@@ -50,6 +51,7 @@ class EhReader {
StringRef readString();
void skipLeb128();
void skipAugP();
+ StringRef getAugmentation();
InputSectionBase *isec;
ArrayRef<uint8_t> d;
@@ -152,7 +154,11 @@ uint8_t elf::getFdeEncoding(EhSectionPiece *p) {
return EhReader(p->sec, p->data()).getFdeEncoding();
}
-uint8_t EhReader::getFdeEncoding() {
+bool elf::hasLSDA(const EhSectionPiece &p) {
+ return EhReader(p.sec, p.data()).hasLSDA();
+}
+
+StringRef EhReader::getAugmentation() {
skipBytes(8);
int version = readByte();
if (version != 1 && version != 3)
@@ -171,10 +177,14 @@ uint8_t EhReader::getFdeEncoding() {
readByte();
else
skipLeb128();
+ return aug;
+}
+uint8_t EhReader::getFdeEncoding() {
// We only care about an 'R' value, but other records may precede an 'R'
// record. Unfortunately records are not in TLV (type-length-value) format,
// so we need to teach the linker how to skip records for each type.
+ StringRef aug = getAugmentation();
for (char c : aug) {
if (c == 'R')
return readByte();
@@ -194,3 +204,20 @@ uint8_t EhReader::getFdeEncoding() {
}
return DW_EH_PE_absptr;
}
+
+bool EhReader::hasLSDA() {
+ StringRef aug = getAugmentation();
+ for (char c : aug) {
+ if (c == 'L')
+ return true;
+ if (c == 'z')
+ skipLeb128();
+ else if (c == 'P')
+ skipAugP();
+ else if (c == 'R')
+ readByte();
+ else
+ failOn(aug.data(), "unknown .eh_frame augmentation string: " + aug);
+ }
+ return false;
+}
diff --git a/lld/ELF/EhFrame.h b/lld/ELF/EhFrame.h
index 20dd6126ec8e..e44832317b3a 100644
--- a/lld/ELF/EhFrame.h
+++ b/lld/ELF/EhFrame.h
@@ -18,6 +18,7 @@ struct EhSectionPiece;
size_t readEhRecordSize(InputSectionBase *s, size_t off);
uint8_t getFdeEncoding(EhSectionPiece *p);
+bool hasLSDA(const EhSectionPiece &p);
} // namespace elf
} // namespace lld
diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp
index ecf0a282420d..c9d922f558dc 100644
--- a/lld/ELF/ICF.cpp
+++ b/lld/ELF/ICF.cpp
@@ -74,6 +74,7 @@
#include "ICF.h"
#include "Config.h"
+#include "EhFrame.h"
#include "LinkerScript.h"
#include "OutputSections.h"
#include "SymbolTable.h"
@@ -459,10 +460,22 @@ template <class ELFT> void ICF<ELFT>::run() {
for (Symbol *sym : symtab->symbols())
sym->isPreemptible = computeIsPreemptible(*sym);
+ // Two text sections may have identical content and relocations but
diff erent
+ // LSDA, e.g. the two functions may have catch blocks of
diff erent 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.
+ for (Partition &part : partitions)
+ part.ehFrame->iterateFDEWithLSDA<ELFT>(
+ [&](InputSection &s) { s.eqClass[0] = 1; });
+
// Collect sections to merge.
for (InputSectionBase *sec : inputSections) {
auto *s = cast<InputSection>(sec);
- if (isEligible(s))
+ if (isEligible(s) && s->eqClass[0] == 0)
sections.push_back(s);
}
@@ -470,6 +483,9 @@ template <class ELFT> void ICF<ELFT>::run() {
parallelForEach(
sections, [&](InputSection *s) { s->eqClass[0] = xxHash64(s->data()); });
+ // Perform 2 rounds of relocation hash propagation. 2 is an empirical value to
+ // reduce the average sizes of equivalence classes, i.e. segregate() which has
+ // a large time complexity will have less work to do.
for (unsigned cnt = 0; cnt != 2; ++cnt) {
parallelForEach(sections, [&](InputSection *s) {
if (s->areRelocsRela)
diff --git a/lld/ELF/InputSection.h b/lld/ELF/InputSection.h
index 112c6ab49a38..00024a6f05d7 100644
--- a/lld/ELF/InputSection.h
+++ b/lld/ELF/InputSection.h
@@ -314,7 +314,7 @@ struct EhSectionPiece {
unsigned firstRelocation)
: inputOff(off), sec(sec), size(size), firstRelocation(firstRelocation) {}
- ArrayRef<uint8_t> data() {
+ ArrayRef<uint8_t> data() const {
return {sec->data().data() + this->inputOff, size};
}
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 731b9f658060..ec9fba6362db 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -370,10 +370,10 @@ CieRecord *EhFrameSection::addCie(EhSectionPiece &cie, ArrayRef<RelTy> rels) {
return rec;
}
-// There is one FDE per function. Returns true if a given FDE
-// points to a live function.
+// There is one FDE per function. Returns a non-null pointer to the function
+// symbol if the given FDE points to a live function.
template <class ELFT, class RelTy>
-bool EhFrameSection::isFdeLive(EhSectionPiece &fde, ArrayRef<RelTy> rels) {
+Defined *EhFrameSection::isFdeLive(EhSectionPiece &fde, ArrayRef<RelTy> rels) {
auto *sec = cast<EhInputSection>(fde.sec);
unsigned firstRelI = fde.firstRelocation;
@@ -383,7 +383,7 @@ bool EhFrameSection::isFdeLive(EhSectionPiece &fde, ArrayRef<RelTy> rels) {
// corresponding FDEs, which results in creating bad .eh_frame sections.
// To deal with that, we ignore such FDEs.
if (firstRelI == (unsigned)-1)
- return false;
+ return nullptr;
const RelTy &rel = rels[firstRelI];
Symbol &b = sec->template getFile<ELFT>()->getRelocTargetSym(rel);
@@ -391,9 +391,9 @@ bool EhFrameSection::isFdeLive(EhSectionPiece &fde, ArrayRef<RelTy> rels) {
// FDEs for garbage-collected or merged-by-ICF sections, or sections in
// another partition, are dead.
if (auto *d = dyn_cast<Defined>(&b))
- if (SectionBase *sec = d->section)
- return sec->partition == partition;
- return false;
+ if (d->section && d->section->partition == partition)
+ return d;
+ return nullptr;
}
// .eh_frame is a sequence of CIE or FDE records. In general, there
@@ -447,6 +447,51 @@ void EhFrameSection::addSection(EhInputSection *sec) {
dependentSections.push_back(ds);
}
+// Used by ICF<ELFT>::handleLSDA(). This function is very similar to
+// EhFrameSection::addRecords().
+template <class ELFT, class RelTy>
+void EhFrameSection::iterateFDEWithLSDAAux(
+ EhInputSection &sec, ArrayRef<RelTy> rels, DenseSet<size_t> &ciesWithLSDA,
+ llvm::function_ref<void(InputSection &)> fn) {
+ for (EhSectionPiece &piece : sec.pieces) {
+ // Skip ZERO terminator.
+ if (piece.size == 4)
+ continue;
+
+ size_t offset = piece.inputOff;
+ uint32_t id =
+ endian::read32<ELFT::TargetEndianness>(piece.data().data() + 4);
+ if (id == 0) {
+ if (hasLSDA(piece))
+ ciesWithLSDA.insert(offset);
+ continue;
+ }
+ uint32_t cieOffset = offset + 4 - id;
+ if (ciesWithLSDA.count(cieOffset) == 0)
+ continue;
+
+ // The CIE has a LSDA argument. Call fn with d's section.
+ if (Defined *d = isFdeLive<ELFT>(piece, rels))
+ if (auto *s = dyn_cast_or_null<InputSection>(d->section))
+ fn(*s);
+ }
+}
+
+template <class ELFT>
+void EhFrameSection::iterateFDEWithLSDA(
+ llvm::function_ref<void(InputSection &)> fn) {
+ DenseSet<uint64_t> ciesWithLSDA;
+ for (EhInputSection *sec : sections) {
+ ciesWithLSDA.clear();
+ if (sec->areRelocsRela)
+ iterateFDEWithLSDAAux<ELFT>(*sec, sec->template relas<ELFT>(),
+ ciesWithLSDA, fn);
+ else
+ iterateFDEWithLSDAAux<ELFT>(*sec, sec->template rels<ELFT>(),
+ ciesWithLSDA, fn);
+ }
+}
+
static void writeCieFde(uint8_t *buf, ArrayRef<uint8_t> d) {
memcpy(buf, d.data(), d.size());
@@ -3759,6 +3804,15 @@ template class elf::MipsOptionsSection<ELF32BE>;
template class elf::MipsOptionsSection<ELF64LE>;
template class elf::MipsOptionsSection<ELF64BE>;
+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 class elf::MipsReginfoSection<ELF32LE>;
template class elf::MipsReginfoSection<ELF32BE>;
template class elf::MipsReginfoSection<ELF64LE>;
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index 8ed82ba64a6e..7779efcd5fe5 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -23,6 +23,7 @@
#include "DWARF.h"
#include "EhFrame.h"
#include "InputSection.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/MC/StringTableBuilder.h"
#include "llvm/Support/Endian.h"
@@ -88,6 +89,8 @@ class EhFrameSection final : public SyntheticSection {
std::vector<FdeData> getFdeData() const;
ArrayRef<CieRecord *> getCieRecords() const { return cieRecords; }
+ template <class ELFT>
+ void iterateFDEWithLSDA(llvm::function_ref<void(InputSection &)> fn);
private:
// This is used only when parsing EhInputSection. We keep it here to avoid
@@ -98,14 +101,17 @@ class EhFrameSection final : public SyntheticSection {
template <class ELFT, class RelTy>
void addRecords(EhInputSection *s, llvm::ArrayRef<RelTy> rels);
- template <class ELFT>
- void addSectionAux(EhInputSection *s);
+ template <class ELFT> void addSectionAux(EhInputSection *s);
+ template <class ELFT, class RelTy>
+ void iterateFDEWithLSDAAux(EhInputSection &sec, ArrayRef<RelTy> rels,
+ llvm::DenseSet<size_t> &ciesWithLSDA,
+ llvm::function_ref<void(InputSection &)> fn);
template <class ELFT, class RelTy>
CieRecord *addCie(EhSectionPiece &piece, ArrayRef<RelTy> rels);
template <class ELFT, class RelTy>
- bool isFdeLive(EhSectionPiece &piece, ArrayRef<RelTy> rels);
+ Defined *isFdeLive(EhSectionPiece &piece, ArrayRef<RelTy> rels);
uint64_t getFdePc(uint8_t *buf, size_t off, uint8_t enc) const;
diff --git a/lld/test/ELF/icf-eh-frame.s b/lld/test/ELF/icf-eh-frame.s
new file mode 100644
index 000000000000..e90df5f96d9f
--- /dev/null
+++ b/lld/test/ELF/icf-eh-frame.s
@@ -0,0 +1,43 @@
+# REQUIRES: x86
+## Test that text sections with LSDA 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
+## 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
+
+# CHECK: selected section {{.*}}.o:(.text.Z1cv)
+# CHECK-NEXT: removing identical section {{.*}}.o:(.text.Z1dv)
+
+.globl _Z1av, _Z1bv, _Z1cv, _Z1dv
+.section .text.Z1av,"ax", at progbits
+_Z1av:
+ .cfi_startproc
+ .cfi_lsda 27, .Lexception0
+ ret
+ .cfi_endproc
+
+.section .text.Z1bv,"ax", at progbits
+_Z1bv:
+ .cfi_startproc
+ .cfi_lsda 27, .Lexception0
+ ret
+ .cfi_endproc
+
+.section .text.Z1cv,"ax", at progbits
+_Z1cv:
+ .cfi_startproc
+ ret
+ .cfi_endproc
+
+.section .text.Z1dv,"ax", at progbits
+_Z1dv:
+ .cfi_startproc
+ ret
+ .cfi_endproc
+
+.section .gcc_except_table,"a", at progbits
+## The actual content does not matter.
+.Lexception0:
More information about the llvm-commits
mailing list