diff --git a/lld/ELF/EhFrame.h b/lld/ELF/EhFrame.h --- a/lld/ELF/EhFrame.h +++ b/lld/ELF/EhFrame.h @@ -18,6 +18,7 @@ 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/EhFrame.cpp b/lld/ELF/EhFrame.cpp --- a/lld/ELF/EhFrame.cpp +++ b/lld/ELF/EhFrame.cpp @@ -38,6 +38,7 @@ EhReader(InputSectionBase *s, ArrayRef d) : isec(s), d(d) {} size_t readEhRecordSize(); uint8_t getFdeEncoding(); + bool hasLSDA(); private: template void failOn(const P *loc, const Twine &msg) { @@ -50,6 +51,7 @@ StringRef readString(); void skipLeb128(); void skipAugP(); + StringRef getAugmentation(); InputSectionBase *isec; ArrayRef d; @@ -152,7 +154,11 @@ 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 @@ 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 @@ } 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/ICF.cpp b/lld/ELF/ICF.cpp --- 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 @@ for (Symbol *sym : symtab->symbols()) sym->isPreemptible = computeIsPreemptible(*sym); + // 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. + for (Partition &part : partitions) + part.ehFrame->iterateFDEWithLSDA( + [&](InputSection &s) { s.eqClass[0] = 1; }); + // Collect sections to merge. for (InputSectionBase *sec : inputSections) { auto *s = cast(sec); - if (isEligible(s)) + if (isEligible(s) && s->eqClass[0] == 0) sections.push_back(s); } @@ -470,6 +483,9 @@ 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 --- a/lld/ELF/InputSection.h +++ b/lld/ELF/InputSection.h @@ -314,7 +314,7 @@ unsigned firstRelocation) : inputOff(off), sec(sec), size(size), firstRelocation(firstRelocation) {} - ArrayRef data() { + ArrayRef data() const { return {sec->data().data() + this->inputOff, size}; } diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h --- 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 @@ std::vector getFdeData() const; ArrayRef getCieRecords() const { return cieRecords; } + template + void iterateFDEWithLSDA(llvm::function_ref fn); private: // This is used only when parsing EhInputSection. We keep it here to avoid @@ -98,14 +101,17 @@ template void addRecords(EhInputSection *s, llvm::ArrayRef rels); - template - void addSectionAux(EhInputSection *s); + template void addSectionAux(EhInputSection *s); + template + void iterateFDEWithLSDAAux(EhInputSection &sec, ArrayRef rels, + llvm::DenseSet &ciesWithLSDA, + llvm::function_ref fn); template CieRecord *addCie(EhSectionPiece &piece, ArrayRef rels); template - bool isFdeLive(EhSectionPiece &piece, ArrayRef rels); + Defined *isFdeLive(EhSectionPiece &piece, ArrayRef rels); uint64_t getFdePc(uint8_t *buf, size_t off, uint8_t enc) const; diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -370,10 +370,10 @@ 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 -bool EhFrameSection::isFdeLive(EhSectionPiece &fde, ArrayRef rels) { +Defined *EhFrameSection::isFdeLive(EhSectionPiece &fde, ArrayRef rels) { auto *sec = cast(fde.sec); unsigned firstRelI = fde.firstRelocation; @@ -383,7 +383,7 @@ // 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()->getRelocTargetSym(rel); @@ -391,9 +391,9 @@ // FDEs for garbage-collected or merged-by-ICF sections, or sections in // another partition, are dead. if (auto *d = dyn_cast(&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 @@ dependentSections.push_back(ds); } +// Used by ICF::handleLSDA(). This function is very similar to +// EhFrameSection::addRecords(). +template +void EhFrameSection::iterateFDEWithLSDAAux( + EhInputSection &sec, ArrayRef rels, DenseSet &ciesWithLSDA, + llvm::function_ref fn) { + for (EhSectionPiece &piece : sec.pieces) { + // Skip ZERO terminator. + if (piece.size == 4) + continue; + + size_t offset = piece.inputOff; + uint32_t id = + endian::read32(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(piece, rels)) + if (auto *s = dyn_cast_or_null(d->section)) + fn(*s); + } +} + +template +void EhFrameSection::iterateFDEWithLSDA( + llvm::function_ref fn) { + DenseSet ciesWithLSDA; + for (EhInputSection *sec : sections) { + ciesWithLSDA.clear(); + if (sec->areRelocsRela) + iterateFDEWithLSDAAux(*sec, sec->template relas(), + ciesWithLSDA, fn); + else + iterateFDEWithLSDAAux(*sec, sec->template rels(), + ciesWithLSDA, fn); + } +} + static void writeCieFde(uint8_t *buf, ArrayRef d) { memcpy(buf, d.data(), d.size()); @@ -3759,6 +3804,15 @@ template class elf::MipsOptionsSection; template class elf::MipsOptionsSection; +template void EhFrameSection::iterateFDEWithLSDA( + function_ref); +template void EhFrameSection::iterateFDEWithLSDA( + function_ref); +template void EhFrameSection::iterateFDEWithLSDA( + function_ref); +template void EhFrameSection::iterateFDEWithLSDA( + function_ref); + template class elf::MipsReginfoSection; template class elf::MipsReginfoSection; template class elf::MipsReginfoSection; diff --git a/lld/test/ELF/icf-eh-frame.s b/lld/test/ELF/icf-eh-frame.s new file mode 100644 --- /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",@progbits +_Z1av: + .cfi_startproc + .cfi_lsda 27, .Lexception0 + ret + .cfi_endproc + +.section .text.Z1bv,"ax",@progbits +_Z1bv: + .cfi_startproc + .cfi_lsda 27, .Lexception0 + ret + .cfi_endproc + +.section .text.Z1cv,"ax",@progbits +_Z1cv: + .cfi_startproc + ret + .cfi_endproc + +.section .text.Z1dv,"ax",@progbits +_Z1dv: + .cfi_startproc + ret + .cfi_endproc + +.section .gcc_except_table,"a",@progbits +## The actual content does not matter. +.Lexception0: