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

Rafael EspĂ­ndola via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 16 11:09:27 PDT 2015


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


More information about the llvm-commits mailing list