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 == 'z') + skipLeb128(); + else if (c == 'P') + skipAugP(); + else if (c == 'L') + return true; + 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" @@ -103,6 +104,10 @@ private: void segregate(size_t begin, size_t end, bool constant); + template + void handleLSDA(EhFrameSection &eh, EhInputSection &sec, + ArrayRef rels); + template bool constantEq(const InputSection *a, ArrayRef relsA, const InputSection *b, ArrayRef relsB); @@ -451,6 +456,48 @@ message(s); } +// Two text sections may have identical content and relocation 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, we assign a unique +// hash to it so that it will not be folded into another section. This is +// implemented by iterating over CIE/FDE and assigning a unique ID 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 but such cases +// are rare. +// +// This function is very similar to EhFrameSection::addRecords. +template +template +void ICF::handleLSDA(EhFrameSection &eh, EhInputSection &sec, + ArrayRef rels) { + llvm::DenseMap offsetToBool; + uint32_t uniqueId = 0; + for (EhSectionPiece &piece : sec.pieces) { + // Skip ZERO terminator. + if (piece.size == 4) + continue; + + size_t offset = piece.inputOff; + uint32_t id = support::endian::read32( + piece.data().data() + 4); + if (id == 0) { + offsetToBool[offset] = hasLSDA(piece); + continue; + } + uint32_t cieOffset = offset + 4 - id; + if (!offsetToBool[cieOffset]) + continue; + + // The CIE has a LSDA argument. Assign a unique ID to eqClass[0] of the + // defining section. + if (Defined *d = eh.isFdeLive(piece, rels)) + if (auto *s = dyn_cast_or_null(d->section)) + s->eqClass[0] = ++uniqueId; + } +} + // The main function of ICF. template void ICF::run() { // Compute isPreemptible early. We may add more symbols later, so this loop @@ -470,6 +517,19 @@ parallelForEach( sections, [&](InputSection *s) { s->eqClass[0] = xxHash64(s->data()); }); + // Assign unique hash values to text sections with LSDA. + for (Partition &part : partitions) { + for (EhInputSection *sec : part.ehFrame->sections) { + if (sec->areRelocsRela) + handleLSDA(*part.ehFrame, *sec, sec->template relas()); + else + handleLSDA(*part.ehFrame, *sec, sec->template rels()); + } + } + + // 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 @@ -89,6 +89,9 @@ std::vector getFdeData() const; ArrayRef getCieRecords() const { return cieRecords; } + template + Defined *isFdeLive(EhSectionPiece &piece, ArrayRef rels); + private: // This is used only when parsing EhInputSection. We keep it here to avoid // allocating one for each EhInputSection. @@ -104,9 +107,6 @@ template CieRecord *addCie(EhSectionPiece &piece, ArrayRef rels); - template - bool isFdeLive(EhSectionPiece &piece, ArrayRef rels); - uint64_t getFdePc(uint8_t *buf, size_t off, uint8_t enc) const; std::vector cieRecords; 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 +// There is one FDE per function. Returns a non-null pointer if a 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 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,39 @@ +# REQUIRES: x86 +## Test that text sections with LSDA are not folded. + +# 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 | 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: