[lld] r247770 - ICF: Improve ICF to reduce more sections than before.

Rui Ueyama via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 16 18:12:38 PDT 2015


Thanks! I tried several algorithms including uncommitted ones before
reaching this simple one. This 200-some line of code just worked like a
charm which is kind of amazing to me. :)
2015/09/16 午前11:09 "Rafael Espíndola" <rafael.espindola at gmail.com>:

> This is impressive, congratulations!
>
> On 15 September 2015 at 23:26, Rui Ueyama via llvm-commits
> <llvm-commits at lists.llvm.org> wrote:
> > Author: ruiu
> > Date: Tue Sep 15 22:26:31 2015
> > New Revision: 247770
> >
> > URL: http://llvm.org/viewvc/llvm-project?rev=247770&view=rev
> > Log:
> > ICF: Improve ICF to reduce more sections than before.
> >
> > This is a patch to make LLD to be on par with MSVC in terms of ICF
> > effectiveness. MSVC produces a 27.14MB executable when linking LLD.
> > LLD previously produced a 27.61MB when self-linking. Now the size
> > is reduced to 27.11MB. Note that without ICF the size is 29.63MB.
> >
> > In r247387, I implemented an algorithm that handles section graphs
> > as cyclic graphs and merge them using SCC. The algorithm did not
> > always work as intended as I demonstrated in r247721. The new
> > algortihm implemented in this patch is different from the previous
> > one. If you are interested the details, you want to read the file
> > comment of ICF.cpp.
> >
> > Modified:
> >     lld/trunk/COFF/Chunks.h
> >     lld/trunk/COFF/ICF.cpp
> >     lld/trunk/test/COFF/icf-circular.test
> >     lld/trunk/test/COFF/icf-circular2.test
> >     lld/trunk/test/COFF/icf-simple.test
> >
> > Modified: lld/trunk/COFF/Chunks.h
> > URL:
> http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/Chunks.h?rev=247770&r1=247769&r2=247770&view=diff
> >
> ==============================================================================
> > --- lld/trunk/COFF/Chunks.h (original)
> > +++ lld/trunk/COFF/Chunks.h Tue Sep 15 22:26:31 2015
> > @@ -113,16 +113,6 @@ protected:
> >
> >  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:
> > @@ -187,21 +177,21 @@ public:
> >    // Used for ICF (Identical COMDAT Folding)
> >    void replaceWith(SectionChunk *Other);
> >    uint64_t getHash() const;
> > -  bool equals(const SectionChunk *Other) const;
> > +  static bool equalsVertex(const SectionChunk *A, const SectionChunk
> *B);
> > +  static bool equalsEdge(const SectionChunk *Au, const SectionChunk *B);
> >
> >    // A pointer pointing to a replacement for this chunk.
> >    // Initially it points to "this" object. If this chunk is merged
> >    // with other chunk by ICF, it points to another chunk,
> >    // and this chunk is considrered as dead.
> >    SectionChunk *Ptr;
> > -  uint32_t Index = 0;
> > -  uint32_t LowLink = 0;
> > -  bool OnStack = false;
> > -  Component *SCC = nullptr;
> > +  std::vector<SectionChunk *> Successors;
> > +  void initSuccessors();
> >
> >    // 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;
> > +  uint64_t GroupID;
> >    mutable uint64_t Hash = 0;
> >
> >  private:
> >
> > Modified: lld/trunk/COFF/ICF.cpp
> > URL:
> http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/ICF.cpp?rev=247770&r1=247769&r2=247770&view=diff
> >
> ==============================================================================
> > --- lld/trunk/COFF/ICF.cpp (original)
> > +++ lld/trunk/COFF/ICF.cpp Tue Sep 15 22:26:31 2015
> > @@ -14,24 +14,32 @@
> >  // 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
> > +// identical 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
> same
> >  // 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.
> > +// What we do in this file is this. We split sections into groups.
> Sections
> > +// in the same group are considered identical.
> >  //
> > -// 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.
> > +// First, all sections are grouped by their "constant" values. Constant
> > +// values are values that never change by ICF, such as section contents,
> > +// section name, number of relocations, type and offset of each
> relocation,
> > +// etc. Because we do not care about relocation targets in this spep,
> two
> > +// sections in the same group may be actually not identical, but at
> least
> > +// two sections in different groups can never be identical.
> > +//
> > +// Then, we try to split each group by relocation targets. Relocations
> are
> > +// considered identical if and only if the relocation targets are in the
> > +// same group. Splitting a group may make more groups to be splittable,
> > +// because two relocations that were previously considered identical
> might
> > +// now point to different groups. We repeat this step until the
> convergence
> > +// is obtained.
> > +//
> > +// This algorithm is so-called "optimistic" algorithm described in
> > +// http://research.google.com/pubs/pub36912.html.
> >  //
> >
> //===----------------------------------------------------------------------===//
> >
> > @@ -51,156 +59,6 @@ using namespace llvm;
> >
> >  namespace lld {
> >  namespace coff {
> > -namespace {
> > -
> > -struct Hasher {
> > -  size_t operator()(const SectionChunk *C) const { return C->getHash();
> }
> > -};
> > -
> > -struct Equals {
> > -  bool operator()(const SectionChunk *A, const SectionChunk *B) const {
> > -    return A->equals(B);
> > -  }
> > -};
> > -
> > -} // 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 {
> >    if (Hash == 0) {
> > @@ -214,74 +72,143 @@ uint64_t SectionChunk::getHash() const {
> >    return Hash;
> >  }
> >
> > +void SectionChunk::initSuccessors() {
> > +  Successors = AssocChildren;
> > +  for (const coff_relocation &R : Relocs) {
> > +    SymbolBody *B = File->getSymbolBody(R.SymbolTableIndex)->repl();
> > +    if (auto D = dyn_cast<DefinedRegular>(B))
> > +      Successors.push_back(D->getChunk());
> > +  }
> > +}
> >
> > -// Returns true if this and a given chunk are identical COMDAT sections.
> > -bool SectionChunk::equals(const SectionChunk *X) const {
> > -  // Compare headers
> > -  if (getPermissions() != X->getPermissions())
> > -    return false;
> > -  if (SectionName != X->SectionName)
> > -    return false;
> > -  if (Header->SizeOfRawData != X->Header->SizeOfRawData)
> > -    return false;
> > -  if (NumRelocs != X->NumRelocs)
> > -    return false;
> > -  if (Checksum != X->Checksum)
> > -    return false;
> > -
> > -  // Compare data
> > -  if (getContents() != X->getContents())
> > +bool SectionChunk::equalsVertex(const SectionChunk *A, const
> SectionChunk *B) {
> > +  if (A->getPermissions() != B->getPermissions() ||
> > +      A->SectionName != B->SectionName ||
> > +      A->Header->SizeOfRawData != B->Header->SizeOfRawData ||
> > +      A->NumRelocs != B->NumRelocs ||
> > +      A->Checksum != B->Checksum ||
> > +      A->AssocChildren.size() != B->AssocChildren.size() ||
> > +      A->getContents() != B->getContents()) {
> >      return false;
> > +  }
> >
> > -  // Compare associative sections
> > -  if (AssocChildren.size() != X->AssocChildren.size())
> > -    return false;
> > -  for (size_t I = 0, E = AssocChildren.size(); I != E; ++I)
> > -    if (AssocChildren[I]->Ptr != X->AssocChildren[I]->Ptr)
> > +  for (size_t I = 0, E = A->AssocChildren.size(); I != E; ++I)
> > +    if (A->AssocChildren[I]->GroupID != B->AssocChildren[I]->GroupID)
> >        return false;
> >
> >    // Compare relocations
> >    auto Eq = [&](const coff_relocation &R1, const coff_relocation &R2) {
> > -    if (R1.Type != R2.Type)
> > +    if (R1.Type != R2.Type ||
> > +        R1.VirtualAddress != R2.VirtualAddress) {
> >        return false;
> > -    if (R1.VirtualAddress != R2.VirtualAddress)
> > -      return false;
> > -    SymbolBody *B1 = File->getSymbolBody(R1.SymbolTableIndex)->repl();
> > -    SymbolBody *B2 =
> X->File->getSymbolBody(R2.SymbolTableIndex)->repl();
> > +    }
> > +    SymbolBody *B1 =
> A->File->getSymbolBody(R1.SymbolTableIndex)->repl();
> > +    SymbolBody *B2 =
> B->File->getSymbolBody(R2.SymbolTableIndex)->repl();
> >      if (B1 == B2)
> >        return true;
> >      auto *D1 = dyn_cast<DefinedRegular>(B1);
> >      auto *D2 = dyn_cast<DefinedRegular>(B2);
> > -    return (D1 && D2 &&
> > -            D1->getValue() == D2->getValue() &&
> > -            D1->getChunk() == D2->getChunk());
> > +    return D1 && D2 &&
> > +           D1->getValue() == D2->getValue() &&
> > +           D1->getChunk()->GroupID == D2->getChunk()->GroupID;
> >    };
> > -  return std::equal(Relocs.begin(), Relocs.end(), X->Relocs.begin(),
> Eq);
> > +  return std::equal(A->Relocs.begin(), A->Relocs.end(),
> B->Relocs.begin(), Eq);
> > +}
> > +
> > +bool SectionChunk::equalsEdge(const SectionChunk *A, const SectionChunk
> *B) {
> > +  assert(A->Successors.size() == B->Successors.size());
> > +  return std::equal(A->Successors.begin(), A->Successors.end(),
> > +                    B->Successors.begin(),
> > +                    [](const SectionChunk *X, const SectionChunk *Y) {
> > +                      return X->GroupID == Y->GroupID;
> > +                    });
> > +}
> > +
> > +typedef std::vector<SectionChunk *>::iterator ChunkIterator;
> > +typedef bool (*Comparator)(const SectionChunk *, const SectionChunk *);
> > +static uint64_t NextID = 0;
> > +
> > +static bool partition(ChunkIterator Begin, ChunkIterator End,
> Comparator Eq) {
> > +  bool R = false;
> > +  for (auto It = Begin;;) {
> > +    SectionChunk *Head = *It;
> > +    auto Bound = std::partition(It + 1, End, [&](SectionChunk *SC) {
> > +      return Eq(Head, SC);
> > +    });
> > +    if (Bound == End)
> > +      return R;
> > +    size_t ID = NextID++;
> > +    std::for_each(It, Bound, [&](SectionChunk *SC) { SC->GroupID = ID;
> });
> > +    It = Bound;
> > +    R = true;
> > +  }
> > +}
> > +
> > +static bool forEachGroup(std::vector<SectionChunk *> &SChunks,
> > +                         Comparator Eq) {
> > +  bool R = false;
> > +  for (auto It = SChunks.begin(), End = SChunks.end(); It != End;) {
> > +    SectionChunk *Head = *It;
> > +    auto Bound = std::find_if(It + 1, End, [&](SectionChunk *SC) {
> > +      return SC->GroupID != Head->GroupID;
> > +    });
> > +    if (partition(It, Bound, Eq))
> > +      R = true;
> > +    It = Bound;
> > +  }
> > +  return R;
> >  }
> >
> >  // Merge identical COMDAT sections.
> >  // Two sections are considered the same if their section headers,
> >  // contents and relocations are all the same.
> >  void doICF(const std::vector<Chunk *> &Chunks) {
> > +  if (Config->Verbose)
> > +    llvm::outs() << "\nICF\n";
> > +
> > +  // Collect only mergeable sections.
> >    std::vector<SectionChunk *> SChunks;
> > -  for (Chunk *C : Chunks)
> > -    if (auto *SC = dyn_cast<SectionChunk>(C))
> > -      if (SC->isCOMDAT() && SC->isLive())
> > +  for (Chunk *C : Chunks) {
> > +    if (auto *SC = dyn_cast<SectionChunk>(C)) {
> > +      bool Writable = SC->getPermissions() &
> llvm::COFF::IMAGE_SCN_MEM_WRITE;
> > +      if (SC->isCOMDAT() && SC->isLive() && !Writable) {
> >          SChunks.push_back(SC);
> > +        SC->GroupID = SC->getHash() | (uint64_t(1) << 63);
> > +      } else {
> > +        SC->GroupID = NextID++;
> > +      }
> > +    }
> > +  }
> >
> > -  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;
> > +  std::sort(SChunks.begin(), SChunks.end(),
> > +            [](SectionChunk *A, SectionChunk *B) {
> > +              return A->GroupID < B->GroupID;
> > +            });
> > +
> > +  // Split groups until we get a convergence.
> > +  for (SectionChunk *SC : SChunks)
> > +    SC->initSuccessors();
> > +  forEachGroup(SChunks, SectionChunk::equalsVertex);
> > +  while (forEachGroup(SChunks, SectionChunk::equalsEdge));
> > +
> > +  // Merge sections in the same group.
> > +  for (auto It = SChunks.begin(), End = SChunks.end(); It != End;) {
> > +    SectionChunk *Head = *It;
> > +    auto Bound = std::find_if(It + 1, End, [&](SectionChunk *SC) {
> > +      return Head->GroupID != SC->GroupID;
> > +    });
> > +    if (std::distance(It, Bound) == 1) {
> > +      It = Bound;
> > +      continue;
> > +    }
> > +    if (Config->Verbose)
> > +      llvm::outs() << "Selected " << Head->getDebugName() << "\n";
> > +    for (++It; It != Bound; ++It) {
> > +      SectionChunk *SC = *It;
> > +      if (Config->Verbose)
> > +        llvm::outs() << "  Removed " << SC->getDebugName() << "\n";
> > +      SC->replaceWith(Head);
> >      }
> > -    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=247770&r1=247769&r2=247770&view=diff
> >
> ==============================================================================
> > --- lld/trunk/test/COFF/icf-circular.test (original)
> > +++ lld/trunk/test/COFF/icf-circular.test Tue Sep 15 22:26:31 2015
> > @@ -3,7 +3,8 @@
> >  # RUN:   /opt:lldicf /verbose %t.obj > %t.log 2>&1
> >  # RUN: FileCheck %s < %t.log
> >
> > -# CHECK: Replaced bar
> > +# CHECK: Selected foo
> > +# CHECK:   Removed bar
> >
> >  ---
> >  header:
> >
> > Modified: lld/trunk/test/COFF/icf-circular2.test
> > URL:
> http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-circular2.test?rev=247770&r1=247769&r2=247770&view=diff
> >
> ==============================================================================
> > --- lld/trunk/test/COFF/icf-circular2.test (original)
> > +++ lld/trunk/test/COFF/icf-circular2.test Tue Sep 15 22:26:31 2015
> > @@ -3,8 +3,8 @@
> >  # RUN:   /opt:lldicf /verbose %t.obj > %t.log 2>&1
> >  # RUN: FileCheck %s < %t.log
> >
> > -# TODO: LLD should be able to merge foo and bar.
> > -# CHECK-NOT: Replaced bar
> > +# CHECK: Selected foo
> > +# CHECK:   Removed bar
> >
> >  ---
> >  header:
> >
> > Modified: lld/trunk/test/COFF/icf-simple.test
> > URL:
> http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-simple.test?rev=247770&r1=247769&r2=247770&view=diff
> >
> ==============================================================================
> > --- lld/trunk/test/COFF/icf-simple.test (original)
> > +++ lld/trunk/test/COFF/icf-simple.test Tue Sep 15 22:26:31 2015
> > @@ -3,7 +3,8 @@
> >  # RUN:   /opt:lldicf /verbose %t.obj > %t.log 2>&1
> >  # RUN: FileCheck %s < %t.log
> >
> > -# CHECK: Replaced bar
> > +# CHECK: Selected foo
> > +# CHECK:   Removed 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/20150916/32d85137/attachment.html>


More information about the llvm-commits mailing list