[lld] r247387 - COFF: Teach ICF to merge cyclic graphs.
Rui Ueyama via llvm-commits
llvm-commits at lists.llvm.org
Fri Sep 11 19:37:03 PDT 2015
I spend some time today to re-implement the feature using scc_iterator with
the hope that simplifies the code, but looks like scc_iterator does not
work well for this ICF.
Defining GraphTraits for SectionChunks needed almost 100 lines of code
(eventually it'd need even more, but I don't know because I gave up before
finishing that). Also the notion of EntryNode exists in GraphTraits does
not exist in SectionChunks -- we just have an array of SectionChunks, which
contains more than one graphs, which don't have notion of entry or exit
nodes. So I think we should keep this code as-is.
On Thu, Sep 10, 2015 at 10:14 PM, Rui Ueyama <ruiu at google.com> wrote:
> Maybe not. scc_iterator seems to fit here nicely. I'll update the code.
>
> On Thu, Sep 10, 2015 at 10:00 PM, Sean Silva <chisophugis at gmail.com>
> wrote:
>
>> Is there a reason you're not using scc_iterator?
>>
>> -- Sean Silva
>>
>> On Thu, Sep 10, 2015 at 9:29 PM, Rui Ueyama via llvm-commits <
>> llvm-commits at lists.llvm.org> wrote:
>>
>>> Author: ruiu
>>> Date: Thu Sep 10 23:29:03 2015
>>> New Revision: 247387
>>>
>>> URL: http://llvm.org/viewvc/llvm-project?rev=247387&view=rev
>>> Log:
>>> COFF: Teach ICF to merge cyclic graphs.
>>>
>>> Previously, LLD's ICF couldn't merge cyclic graphs. That was unfortunate
>>> because, in COFF, cyclic graphs are not exceptional at all. That is
>>> pretty common.
>>>
>>> In this patch, sections are grouped by Tarjan's strongly connected
>>> component algorithm to get acyclic graphs. And then we try to merge
>>> SCCs whose outdegree is zero, and remove them from the graph. This
>>> makes other SCCs to have outdegree zero, so we can repeat the
>>> process until all SCCs are removed. When comparing two SCCs, we handle
>>> cycles properly.
>>>
>>> This algorithm works better than previous one. Previously, self-linking
>>> produced a 29.0MB executable. It now produces a 27.7MB. There's still
>>> some
>>> gap compared to MSVC linker which produces a 27.1MB executable for the
>>> same input. So the gap is narrowed, but still LLD is not on par with
>>> MSVC.
>>> I'll investigate that later.
>>>
>>> Modified:
>>> lld/trunk/COFF/Chunks.h
>>> lld/trunk/COFF/ICF.cpp
>>> lld/trunk/test/COFF/icf-circular.test
>>>
>>> Modified: lld/trunk/COFF/Chunks.h
>>> URL:
>>> http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/Chunks.h?rev=247387&r1=247386&r2=247387&view=diff
>>>
>>> ==============================================================================
>>> --- lld/trunk/COFF/Chunks.h (original)
>>> +++ lld/trunk/COFF/Chunks.h Thu Sep 10 23:29:03 2015
>>> @@ -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;
>>>
>>> Modified: lld/trunk/COFF/ICF.cpp
>>> URL:
>>> http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/ICF.cpp?rev=247387&r1=247386&r2=247387&view=diff
>>>
>>> ==============================================================================
>>> --- lld/trunk/COFF/ICF.cpp (original)
>>> +++ lld/trunk/COFF/ICF.cpp Thu Sep 10 23:29:03 2015
>>> @@ -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 SectionC
>>> 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 *> &C
>>> 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());
>>> - }
>>> -
>>> - // 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());
>>> + std::vector<Component *> Components = getSCC(SChunks);
>>> +
>>> + 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());
>>> }
>>> }
>>>
>>>
>>> Modified: lld/trunk/test/COFF/icf-circular.test
>>> URL:
>>> http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-circular.test?rev=247387&r1=247386&r2=247387&view=diff
>>>
>>> ==============================================================================
>>> --- lld/trunk/test/COFF/icf-circular.test (original)
>>> +++ lld/trunk/test/COFF/icf-circular.test Thu Sep 10 23:29:03 2015
>>> @@ -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:
>>>
>>>
>>> _______________________________________________
>>> llvm-commits mailing list
>>> llvm-commits at lists.llvm.org
>>> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
>>>
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20150911/953e9177/attachment-0001.html>
More information about the llvm-commits
mailing list