diff options
| -rw-r--r-- | lld/COFF/Chunks.h | 19 | ||||
| -rw-r--r-- | lld/COFF/ICF.cpp | 236 | ||||
| -rw-r--r-- | lld/test/COFF/icf-circular.test | 2 |
3 files changed, 206 insertions, 51 deletions
diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h index 184944a5fa6..1c4ba161db9 100644 --- a/lld/COFF/Chunks.h +++ b/lld/COFF/Chunks.h @@ -111,6 +111,18 @@ protected: uint32_t Align = 1; }; +class SectionChunk; + +// A container of SectionChunks. Used by ICF to store computation +// results of strongly connected components. You can ignore this +// unless you are interested in ICF. +struct Component { + Component(std::vector<SectionChunk *> V) : Members(V) {} + std::vector<SectionChunk *> Members; + std::vector<Component *> Predecessors; + int Outdegree = 0; +}; + // A chunk corresponding a section of an input file. class SectionChunk : public Chunk { public: @@ -182,12 +194,15 @@ public: // with other chunk by ICF, it points to another chunk, // and this chunk is considrered as dead. SectionChunk *Ptr; - int Outdegree = 0; - std::vector<SectionChunk *> Ins; + uint32_t Index = 0; + uint32_t LowLink = 0; + bool OnStack = false; + Component *SCC = nullptr; // The CRC of the contents as described in the COFF spec 4.5.5. // Auxiliary Format 5: Section Definitions. Used for ICF. uint32_t Checksum = 0; + mutable uint64_t Hash = 0; private: ArrayRef<uint8_t> getContents() const; diff --git a/lld/COFF/ICF.cpp b/lld/COFF/ICF.cpp index bccdafcdbe8..e1ff2f0726e 100644 --- a/lld/COFF/ICF.cpp +++ b/lld/COFF/ICF.cpp @@ -7,7 +7,31 @@ // //===----------------------------------------------------------------------===// // -// Implements ICF (Identical COMDAT Folding) +// Identical COMDAT Folding is a feature to merge COMDAT sections not by +// name (which is regular COMDAT handling) but by contents. If two COMDAT +// sections have the same data, relocations, attributes, etc., then the two +// are considered identical and merged by the linker. This optimization +// makes outputs smaller. +// +// ICF is theoretically a problem of reducing graphs by merging as many +// isomorphic subgraphs as possible, if we consider sections as vertices and +// relocations as edges. This may be a bit more complicated problem than you +// might think. The order of processing sections matters since merging two +// sections can make other sections, whose relocations now point to the +// section, mergeable. Graphs may contain cycles, which is common in COFF. +// We need a sophisticated algorithm to do this properly and efficiently. +// +// What we do in this file is this. We first compute strongly connected +// components of the graphs to get acyclic graphs. Then, we remove SCCs whose +// outdegree is zero from the graphs and try to merge them. This operation +// makes other SCCs to have outdegree zero, so we repeat the process until +// all SCCs are removed. +// +// This algorithm is different from what GNU gold does which is described in +// http://research.google.com/pubs/pub36912.html. I don't know which is +// faster, this or Gold's, in practice. It'd be interesting to implement the +// other algorithm to compare. Note that the gold's algorithm cannot handle +// cycles, so we need to tweak it, though. // //===----------------------------------------------------------------------===// @@ -15,6 +39,10 @@ #include "Symbols.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <functional> #include <tuple> #include <unordered_set> #include <vector> @@ -37,15 +65,156 @@ struct Equals { } // anonymous namespace +// Invoke Fn for each live COMDAT successor sections of SC. +static void forEach(SectionChunk *SC, std::function<void(SectionChunk *)> Fn) { + for (SectionChunk *C : SC->children()) + Fn(C); + for (SymbolBody *B : SC->symbols()) { + if (auto *D = dyn_cast<DefinedRegular>(B)) { + SectionChunk *C = D->getChunk(); + if (C->isCOMDAT() && C->isLive()) + Fn(C); + } + } +} + +typedef std::vector<Component *>::iterator ComponentIterator; + +// Try to merge two SCCs, A and B. A and B are likely to be isomorphic +// because all sections have the same hash values. +static void tryMerge(std::vector<SectionChunk *> &A, + std::vector<SectionChunk *> &B) { + // Assume that relocation targets are the same. + size_t End = A.size(); + for (size_t I = 0; I != End; ++I) { + assert(B[I] == B[I]->Ptr); + B[I]->Ptr = A[I]; + } + for (size_t I = 0; I != End; ++I) { + if (A[I]->equals(B[I])) + continue; + // If we reach here, the assumption was wrong. Reset the pointers + // to the original values and terminate the comparison. + for (size_t I = 0; I != End; ++I) + B[I]->Ptr = B[I]; + return; + } + // If we reach here, the assumption was correct. Actually replace them. + for (size_t I = 0; I != End; ++I) + B[I]->replaceWith(A[I]); +} + +// Try to merge components. All components given to this function are +// guaranteed to have the same number of members. +static void doUniquefy(ComponentIterator Begin, ComponentIterator End) { + // Sort component members by hash value. + for (auto It = Begin; It != End; ++It) { + Component *SCC = *It; + auto Comp = [](SectionChunk *A, SectionChunk *B) { + return A->getHash() < B->getHash(); + }; + std::sort(SCC->Members.begin(), SCC->Members.end(), Comp); + } + + // Merge as much component members as possible. + for (auto It = Begin; It != End;) { + Component *SCC = *It; + auto Bound = std::partition(It + 1, End, [&](Component *C) { + for (size_t I = 0, E = SCC->Members.size(); I != E; ++I) + if (SCC->Members[I]->getHash() != C->Members[I]->getHash()) + return false; + return true; + }); + + // Components [I, Bound) are likely to have the same members + // because all members have the same hash values. Verify that. + for (auto I = It + 1; I != Bound; ++I) + tryMerge(SCC->Members, (*I)->Members); + It = Bound; + } +} + +static void uniquefy(ComponentIterator Begin, ComponentIterator End) { + for (auto It = Begin; It != End;) { + Component *SCC = *It; + size_t Size = SCC->Members.size(); + auto Bound = std::partition(It + 1, End, [&](Component *C) { + return C->Members.size() == Size; + }); + doUniquefy(It, Bound); + It = Bound; + } +} + +// Returns strongly connected components of the graph formed by Chunks. +// Chunks (a list of Live COMDAT sections) are considred as vertices, +// and their relocations or association are considered as edges. +static std::vector<Component *> +getSCC(const std::vector<SectionChunk *> &Chunks) { + std::vector<Component *> Ret; + std::vector<SectionChunk *> V; + uint32_t Idx; + + std::function<void(SectionChunk *)> StrongConnect = [&](SectionChunk *SC) { + SC->Index = SC->LowLink = Idx++; + size_t Curr = V.size(); + V.push_back(SC); + SC->OnStack = true; + + forEach(SC, [&](SectionChunk *C) { + if (C->Index == 0) { + StrongConnect(C); + SC->LowLink = std::min(SC->LowLink, C->LowLink); + } else if (C->OnStack) { + SC->LowLink = std::min(SC->LowLink, C->Index); + } + }); + + if (SC->LowLink != SC->Index) + return; + auto *SCC = new Component( + std::vector<SectionChunk *>(V.begin() + Curr, V.end())); + for (size_t I = Curr, E = V.size(); I != E; ++I) { + V[I]->OnStack = false; + V[I]->SCC = SCC; + } + Ret.push_back(SCC); + V.erase(V.begin() + Curr, V.end()); + }; + + for (SectionChunk *SC : Chunks) { + if (SC->Index == 0) { + Idx = 1; + StrongConnect(SC); + } + } + + for (Component *SCC : Ret) { + for (SectionChunk *SC : SCC->Members) { + forEach(SC, [&](SectionChunk *C) { + if (SCC == C->SCC) + return; + ++SCC->Outdegree; + C->SCC->Predecessors.push_back(SCC); + }); + } + } + return Ret; +} + uint64_t SectionChunk::getHash() const { - return hash_combine(getPermissions(), - hash_value(SectionName), - NumRelocs, - uint32_t(Header->SizeOfRawData), - std::distance(Relocs.end(), Relocs.begin()), - Checksum); + if (Hash == 0) { + Hash = hash_combine(getPermissions(), + hash_value(SectionName), + NumRelocs, + uint32_t(Header->SizeOfRawData), + std::distance(Relocs.end(), Relocs.begin()), + Checksum); + } + return Hash; } + // Returns true if this and a given chunk are identical COMDAT sections. bool SectionChunk::equals(const SectionChunk *X) const { // Compare headers @@ -90,28 +259,6 @@ bool SectionChunk::equals(const SectionChunk *X) const { return std::equal(Relocs.begin(), Relocs.end(), X->Relocs.begin(), Eq); } -static void link(SectionChunk *From, SectionChunk *To) { - ++From->Outdegree; - To->Ins.push_back(From); -} - -typedef std::vector<SectionChunk *>::iterator ChunkIterator; - -static void uniquefy(ChunkIterator Begin, ChunkIterator End) { - std::unordered_set<SectionChunk *, Hasher, Equals> Set; - for (auto It = Begin; It != End; ++It) { - SectionChunk *SC = *It; - auto P = Set.insert(SC); - bool Inserted = P.second; - if (Inserted) - continue; - SectionChunk *Existing = *P.first; - SC->replaceWith(Existing); - for (SectionChunk *In : SC->Ins) - --In->Outdegree; - } -} - // Merge identical COMDAT sections. // Two sections are considered the same if their section headers, // contents and relocations are all the same. @@ -122,26 +269,19 @@ void doICF(const std::vector<Chunk *> &Chunks) { if (SC->isCOMDAT() && SC->isLive()) SChunks.push_back(SC); - // Initialize SectionChunks' outdegrees and in-chunk lists. - for (SectionChunk *SC : SChunks) { - for (SectionChunk *C : SC->children()) - link(SC, C); - for (SymbolBody *B : SC->symbols()) - if (auto *D = dyn_cast<DefinedRegular>(B)) - link(SC, D->getChunk()); - } + std::vector<Component *> Components = getSCC(SChunks); - // By merging two sections, more sections can become mergeable - // because two originally different relocations can now point to - // the same section. We process sections whose outdegree is zero - // first to deal with that. - auto Pred = [](SectionChunk *SC) { return SC->Outdegree > 0; }; - for (;;) { - auto Bound = std::partition(SChunks.begin(), SChunks.end(), Pred); - if (Bound == SChunks.end()) - return; - uniquefy(Bound, SChunks.end()); - SChunks.erase(Bound, SChunks.end()); + while (Components.size() > 0) { + auto Bound = std::partition(Components.begin(), Components.end(), + [](Component *SCC) { return SCC->Outdegree > 0; }); + uniquefy(Bound, Components.end()); + + for (auto It = Bound, E = Components.end(); It != E; ++It) { + Component *SCC = *It; + for (Component *Pred : SCC->Predecessors) + --Pred->Outdegree; + } + Components.erase(Bound, Components.end()); } } diff --git a/lld/test/COFF/icf-circular.test b/lld/test/COFF/icf-circular.test index 47dc6dbbb71..368e2a39e91 100644 --- a/lld/test/COFF/icf-circular.test +++ b/lld/test/COFF/icf-circular.test @@ -3,7 +3,7 @@ # RUN: /opt:lldicf /verbose %t.obj > %t.log 2>&1 # RUN: FileCheck %s < %t.log -# CHECK-NOT: Replaced bar +# CHECK: Replaced bar --- header: |

