<p dir="ltr">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. :)</p>
<div class="gmail_quote">2015/09/16 午前11:09 "Rafael Espíndola" <<a href="mailto:rafael.espindola@gmail.com">rafael.espindola@gmail.com</a>>:<br type="attribution"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">This is impressive, congratulations!<br>
<br>
On 15 September 2015 at 23:26, Rui Ueyama via llvm-commits<br>
<<a href="mailto:llvm-commits@lists.llvm.org">llvm-commits@lists.llvm.org</a>> wrote:<br>
> Author: ruiu<br>
> Date: Tue Sep 15 22:26:31 2015<br>
> New Revision: 247770<br>
><br>
> URL: <a href="http://llvm.org/viewvc/llvm-project?rev=247770&view=rev" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project?rev=247770&view=rev</a><br>
> Log:<br>
> ICF: Improve ICF to reduce more sections than before.<br>
><br>
> This is a patch to make LLD to be on par with MSVC in terms of ICF<br>
> effectiveness. MSVC produces a 27.14MB executable when linking LLD.<br>
> LLD previously produced a 27.61MB when self-linking. Now the size<br>
> is reduced to 27.11MB. Note that without ICF the size is 29.63MB.<br>
><br>
> In r247387, I implemented an algorithm that handles section graphs<br>
> as cyclic graphs and merge them using SCC. The algorithm did not<br>
> always work as intended as I demonstrated in r247721. The new<br>
> algortihm implemented in this patch is different from the previous<br>
> one. If you are interested the details, you want to read the file<br>
> comment of ICF.cpp.<br>
><br>
> Modified:<br>
>     lld/trunk/COFF/Chunks.h<br>
>     lld/trunk/COFF/ICF.cpp<br>
>     lld/trunk/test/COFF/icf-circular.test<br>
>     lld/trunk/test/COFF/icf-circular2.test<br>
>     lld/trunk/test/COFF/icf-simple.test<br>
><br>
> Modified: lld/trunk/COFF/Chunks.h<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/Chunks.h?rev=247770&r1=247769&r2=247770&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/Chunks.h?rev=247770&r1=247769&r2=247770&view=diff</a><br>
> ==============================================================================<br>
> --- lld/trunk/COFF/Chunks.h (original)<br>
> +++ lld/trunk/COFF/Chunks.h Tue Sep 15 22:26:31 2015<br>
> @@ -113,16 +113,6 @@ protected:<br>
><br>
>  class SectionChunk;<br>
><br>
> -// A container of SectionChunks. Used by ICF to store computation<br>
> -// results of strongly connected components. You can ignore this<br>
> -// unless you are interested in ICF.<br>
> -struct Component {<br>
> -  Component(std::vector<SectionChunk *> V) : Members(V) {}<br>
> -  std::vector<SectionChunk *> Members;<br>
> -  std::vector<Component *> Predecessors;<br>
> -  int Outdegree = 0;<br>
> -};<br>
> -<br>
>  // A chunk corresponding a section of an input file.<br>
>  class SectionChunk : public Chunk {<br>
>  public:<br>
> @@ -187,21 +177,21 @@ public:<br>
>    // Used for ICF (Identical COMDAT Folding)<br>
>    void replaceWith(SectionChunk *Other);<br>
>    uint64_t getHash() const;<br>
> -  bool equals(const SectionChunk *Other) const;<br>
> +  static bool equalsVertex(const SectionChunk *A, const SectionChunk *B);<br>
> +  static bool equalsEdge(const SectionChunk *Au, const SectionChunk *B);<br>
><br>
>    // A pointer pointing to a replacement for this chunk.<br>
>    // Initially it points to "this" object. If this chunk is merged<br>
>    // with other chunk by ICF, it points to another chunk,<br>
>    // and this chunk is considrered as dead.<br>
>    SectionChunk *Ptr;<br>
> -  uint32_t Index = 0;<br>
> -  uint32_t LowLink = 0;<br>
> -  bool OnStack = false;<br>
> -  Component *SCC = nullptr;<br>
> +  std::vector<SectionChunk *> Successors;<br>
> +  void initSuccessors();<br>
><br>
>    // The CRC of the contents as described in the COFF spec 4.5.5.<br>
>    // Auxiliary Format 5: Section Definitions. Used for ICF.<br>
>    uint32_t Checksum = 0;<br>
> +  uint64_t GroupID;<br>
>    mutable uint64_t Hash = 0;<br>
><br>
>  private:<br>
><br>
> Modified: lld/trunk/COFF/ICF.cpp<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/ICF.cpp?rev=247770&r1=247769&r2=247770&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lld/trunk/COFF/ICF.cpp?rev=247770&r1=247769&r2=247770&view=diff</a><br>
> ==============================================================================<br>
> --- lld/trunk/COFF/ICF.cpp (original)<br>
> +++ lld/trunk/COFF/ICF.cpp Tue Sep 15 22:26:31 2015<br>
> @@ -14,24 +14,32 @@<br>
>  // makes outputs smaller.<br>
>  //<br>
>  // ICF is theoretically a problem of reducing graphs by merging as many<br>
> -// isomorphic subgraphs as possible, if we consider sections as vertices and<br>
> +// identical subgraphs as possible, if we consider sections as vertices and<br>
>  // relocations as edges. This may be a bit more complicated problem than you<br>
>  // might think. The order of processing sections matters since merging two<br>
>  // sections can make other sections, whose relocations now point to the same<br>
>  // section, mergeable. Graphs may contain cycles, which is common in COFF.<br>
>  // We need a sophisticated algorithm to do this properly and efficiently.<br>
>  //<br>
> -// What we do in this file is this. We first compute strongly connected<br>
> -// components of the graphs to get acyclic graphs. Then, we remove SCCs whose<br>
> -// outdegree is zero from the graphs and try to merge them. This operation<br>
> -// makes other SCCs to have outdegree zero, so we repeat the process until<br>
> -// all SCCs are removed.<br>
> +// What we do in this file is this. We split sections into groups. Sections<br>
> +// in the same group are considered identical.<br>
>  //<br>
> -// This algorithm is different from what GNU gold does which is described in<br>
> -// <a href="http://research.google.com/pubs/pub36912.html" rel="noreferrer" target="_blank">http://research.google.com/pubs/pub36912.html</a>. I don't know which is<br>
> -// faster, this or Gold's, in practice. It'd be interesting to implement the<br>
> -// other algorithm to compare. Note that the gold's algorithm cannot handle<br>
> -// cycles, so we need to tweak it, though.<br>
> +// First, all sections are grouped by their "constant" values. Constant<br>
> +// values are values that never change by ICF, such as section contents,<br>
> +// section name, number of relocations, type and offset of each relocation,<br>
> +// etc. Because we do not care about relocation targets in this spep, two<br>
> +// sections in the same group may be actually not identical, but at least<br>
> +// two sections in different groups can never be identical.<br>
> +//<br>
> +// Then, we try to split each group by relocation targets. Relocations are<br>
> +// considered identical if and only if the relocation targets are in the<br>
> +// same group. Splitting a group may make more groups to be splittable,<br>
> +// because two relocations that were previously considered identical might<br>
> +// now point to different groups. We repeat this step until the convergence<br>
> +// is obtained.<br>
> +//<br>
> +// This algorithm is so-called "optimistic" algorithm described in<br>
> +// <a href="http://research.google.com/pubs/pub36912.html" rel="noreferrer" target="_blank">http://research.google.com/pubs/pub36912.html</a>.<br>
>  //<br>
>  //===----------------------------------------------------------------------===//<br>
><br>
> @@ -51,156 +59,6 @@ using namespace llvm;<br>
><br>
>  namespace lld {<br>
>  namespace coff {<br>
> -namespace {<br>
> -<br>
> -struct Hasher {<br>
> -  size_t operator()(const SectionChunk *C) const { return C->getHash(); }<br>
> -};<br>
> -<br>
> -struct Equals {<br>
> -  bool operator()(const SectionChunk *A, const SectionChunk *B) const {<br>
> -    return A->equals(B);<br>
> -  }<br>
> -};<br>
> -<br>
> -} // anonymous namespace<br>
> -<br>
> -// Invoke Fn for each live COMDAT successor sections of SC.<br>
> -static void forEach(SectionChunk *SC, std::function<void(SectionChunk *)> Fn) {<br>
> -  for (SectionChunk *C : SC->children())<br>
> -    Fn(C);<br>
> -  for (SymbolBody *B : SC->symbols()) {<br>
> -    if (auto *D = dyn_cast<DefinedRegular>(B)) {<br>
> -      SectionChunk *C = D->getChunk();<br>
> -      if (C->isCOMDAT() && C->isLive())<br>
> -        Fn(C);<br>
> -    }<br>
> -  }<br>
> -}<br>
> -<br>
> -typedef std::vector<Component *>::iterator ComponentIterator;<br>
> -<br>
> -// Try to merge two SCCs, A and B. A and B are likely to be isomorphic<br>
> -// because all sections have the same hash values.<br>
> -static void tryMerge(std::vector<SectionChunk *> &A,<br>
> -                     std::vector<SectionChunk *> &B) {<br>
> -  // Assume that relocation targets are the same.<br>
> -  size_t End = A.size();<br>
> -  for (size_t I = 0; I != End; ++I) {<br>
> -    assert(B[I] == B[I]->Ptr);<br>
> -    B[I]->Ptr = A[I];<br>
> -  }<br>
> -  for (size_t I = 0; I != End; ++I) {<br>
> -    if (A[I]->equals(B[I]))<br>
> -      continue;<br>
> -    // If we reach here, the assumption was wrong. Reset the pointers<br>
> -    // to the original values and terminate the comparison.<br>
> -    for (size_t I = 0; I != End; ++I)<br>
> -      B[I]->Ptr = B[I];<br>
> -    return;<br>
> -  }<br>
> -  // If we reach here, the assumption was correct. Actually replace them.<br>
> -  for (size_t I = 0; I != End; ++I)<br>
> -    B[I]->replaceWith(A[I]);<br>
> -}<br>
> -<br>
> -// Try to merge components. All components given to this function are<br>
> -// guaranteed to have the same number of members.<br>
> -static void doUniquefy(ComponentIterator Begin, ComponentIterator End) {<br>
> -  // Sort component members by hash value.<br>
> -  for (auto It = Begin; It != End; ++It) {<br>
> -    Component *SCC = *It;<br>
> -    auto Comp = [](SectionChunk *A, SectionChunk *B) {<br>
> -      return A->getHash() < B->getHash();<br>
> -    };<br>
> -    std::sort(SCC->Members.begin(), SCC->Members.end(), Comp);<br>
> -  }<br>
> -<br>
> -  // Merge as much component members as possible.<br>
> -  for (auto It = Begin; It != End;) {<br>
> -    Component *SCC = *It;<br>
> -    auto Bound = std::partition(It + 1, End, [&](Component *C) {<br>
> -      for (size_t I = 0, E = SCC->Members.size(); I != E; ++I)<br>
> -        if (SCC->Members[I]->getHash() != C->Members[I]->getHash())<br>
> -          return false;<br>
> -      return true;<br>
> -    });<br>
> -<br>
> -    // Components [I, Bound) are likely to have the same members<br>
> -    // because all members have the same hash values. Verify that.<br>
> -    for (auto I = It + 1; I != Bound; ++I)<br>
> -      tryMerge(SCC->Members, (*I)->Members);<br>
> -    It = Bound;<br>
> -  }<br>
> -}<br>
> -<br>
> -static void uniquefy(ComponentIterator Begin, ComponentIterator End) {<br>
> -  for (auto It = Begin; It != End;) {<br>
> -    Component *SCC = *It;<br>
> -    size_t Size = SCC->Members.size();<br>
> -    auto Bound = std::partition(It + 1, End, [&](Component *C) {<br>
> -      return C->Members.size() == Size;<br>
> -    });<br>
> -    doUniquefy(It, Bound);<br>
> -    It = Bound;<br>
> -  }<br>
> -}<br>
> -<br>
> -// Returns strongly connected components of the graph formed by Chunks.<br>
> -// Chunks (a list of Live COMDAT sections) are considred as vertices,<br>
> -// and their relocations or association are considered as edges.<br>
> -static std::vector<Component *><br>
> -getSCC(const std::vector<SectionChunk *> &Chunks) {<br>
> -  std::vector<Component *> Ret;<br>
> -  std::vector<SectionChunk *> V;<br>
> -  uint32_t Idx;<br>
> -<br>
> -  std::function<void(SectionChunk *)> StrongConnect = [&](SectionChunk *SC) {<br>
> -    SC->Index = SC->LowLink = Idx++;<br>
> -    size_t Curr = V.size();<br>
> -    V.push_back(SC);<br>
> -    SC->OnStack = true;<br>
> -<br>
> -    forEach(SC, [&](SectionChunk *C) {<br>
> -      if (C->Index == 0) {<br>
> -        StrongConnect(C);<br>
> -        SC->LowLink = std::min(SC->LowLink, C->LowLink);<br>
> -      } else if (C->OnStack) {<br>
> -        SC->LowLink = std::min(SC->LowLink, C->Index);<br>
> -      }<br>
> -    });<br>
> -<br>
> -    if (SC->LowLink != SC->Index)<br>
> -      return;<br>
> -    auto *SCC = new Component(<br>
> -        std::vector<SectionChunk *>(V.begin() + Curr, V.end()));<br>
> -    for (size_t I = Curr, E = V.size(); I != E; ++I) {<br>
> -      V[I]->OnStack = false;<br>
> -      V[I]->SCC = SCC;<br>
> -    }<br>
> -    Ret.push_back(SCC);<br>
> -    V.erase(V.begin() + Curr, V.end());<br>
> -  };<br>
> -<br>
> -  for (SectionChunk *SC : Chunks) {<br>
> -    if (SC->Index == 0) {<br>
> -      Idx = 1;<br>
> -      StrongConnect(SC);<br>
> -    }<br>
> -  }<br>
> -<br>
> -  for (Component *SCC : Ret) {<br>
> -    for (SectionChunk *SC : SCC->Members) {<br>
> -      forEach(SC, [&](SectionChunk *C) {<br>
> -        if (SCC == C->SCC)<br>
> -          return;<br>
> -        ++SCC->Outdegree;<br>
> -        C->SCC->Predecessors.push_back(SCC);<br>
> -      });<br>
> -    }<br>
> -  }<br>
> -  return Ret;<br>
> -}<br>
><br>
>  uint64_t SectionChunk::getHash() const {<br>
>    if (Hash == 0) {<br>
> @@ -214,74 +72,143 @@ uint64_t SectionChunk::getHash() const {<br>
>    return Hash;<br>
>  }<br>
><br>
> +void SectionChunk::initSuccessors() {<br>
> +  Successors = AssocChildren;<br>
> +  for (const coff_relocation &R : Relocs) {<br>
> +    SymbolBody *B = File->getSymbolBody(R.SymbolTableIndex)->repl();<br>
> +    if (auto D = dyn_cast<DefinedRegular>(B))<br>
> +      Successors.push_back(D->getChunk());<br>
> +  }<br>
> +}<br>
><br>
> -// Returns true if this and a given chunk are identical COMDAT sections.<br>
> -bool SectionChunk::equals(const SectionChunk *X) const {<br>
> -  // Compare headers<br>
> -  if (getPermissions() != X->getPermissions())<br>
> -    return false;<br>
> -  if (SectionName != X->SectionName)<br>
> -    return false;<br>
> -  if (Header->SizeOfRawData != X->Header->SizeOfRawData)<br>
> -    return false;<br>
> -  if (NumRelocs != X->NumRelocs)<br>
> -    return false;<br>
> -  if (Checksum != X->Checksum)<br>
> -    return false;<br>
> -<br>
> -  // Compare data<br>
> -  if (getContents() != X->getContents())<br>
> +bool SectionChunk::equalsVertex(const SectionChunk *A, const SectionChunk *B) {<br>
> +  if (A->getPermissions() != B->getPermissions() ||<br>
> +      A->SectionName != B->SectionName ||<br>
> +      A->Header->SizeOfRawData != B->Header->SizeOfRawData ||<br>
> +      A->NumRelocs != B->NumRelocs ||<br>
> +      A->Checksum != B->Checksum ||<br>
> +      A->AssocChildren.size() != B->AssocChildren.size() ||<br>
> +      A->getContents() != B->getContents()) {<br>
>      return false;<br>
> +  }<br>
><br>
> -  // Compare associative sections<br>
> -  if (AssocChildren.size() != X->AssocChildren.size())<br>
> -    return false;<br>
> -  for (size_t I = 0, E = AssocChildren.size(); I != E; ++I)<br>
> -    if (AssocChildren[I]->Ptr != X->AssocChildren[I]->Ptr)<br>
> +  for (size_t I = 0, E = A->AssocChildren.size(); I != E; ++I)<br>
> +    if (A->AssocChildren[I]->GroupID != B->AssocChildren[I]->GroupID)<br>
>        return false;<br>
><br>
>    // Compare relocations<br>
>    auto Eq = [&](const coff_relocation &R1, const coff_relocation &R2) {<br>
> -    if (R1.Type != R2.Type)<br>
> +    if (R1.Type != R2.Type ||<br>
> +        R1.VirtualAddress != R2.VirtualAddress) {<br>
>        return false;<br>
> -    if (R1.VirtualAddress != R2.VirtualAddress)<br>
> -      return false;<br>
> -    SymbolBody *B1 = File->getSymbolBody(R1.SymbolTableIndex)->repl();<br>
> -    SymbolBody *B2 = X->File->getSymbolBody(R2.SymbolTableIndex)->repl();<br>
> +    }<br>
> +    SymbolBody *B1 = A->File->getSymbolBody(R1.SymbolTableIndex)->repl();<br>
> +    SymbolBody *B2 = B->File->getSymbolBody(R2.SymbolTableIndex)->repl();<br>
>      if (B1 == B2)<br>
>        return true;<br>
>      auto *D1 = dyn_cast<DefinedRegular>(B1);<br>
>      auto *D2 = dyn_cast<DefinedRegular>(B2);<br>
> -    return (D1 && D2 &&<br>
> -            D1->getValue() == D2->getValue() &&<br>
> -            D1->getChunk() == D2->getChunk());<br>
> +    return D1 && D2 &&<br>
> +           D1->getValue() == D2->getValue() &&<br>
> +           D1->getChunk()->GroupID == D2->getChunk()->GroupID;<br>
>    };<br>
> -  return std::equal(Relocs.begin(), Relocs.end(), X->Relocs.begin(), Eq);<br>
> +  return std::equal(A->Relocs.begin(), A->Relocs.end(), B->Relocs.begin(), Eq);<br>
> +}<br>
> +<br>
> +bool SectionChunk::equalsEdge(const SectionChunk *A, const SectionChunk *B) {<br>
> +  assert(A->Successors.size() == B->Successors.size());<br>
> +  return std::equal(A->Successors.begin(), A->Successors.end(),<br>
> +                    B->Successors.begin(),<br>
> +                    [](const SectionChunk *X, const SectionChunk *Y) {<br>
> +                      return X->GroupID == Y->GroupID;<br>
> +                    });<br>
> +}<br>
> +<br>
> +typedef std::vector<SectionChunk *>::iterator ChunkIterator;<br>
> +typedef bool (*Comparator)(const SectionChunk *, const SectionChunk *);<br>
> +static uint64_t NextID = 0;<br>
> +<br>
> +static bool partition(ChunkIterator Begin, ChunkIterator End, Comparator Eq) {<br>
> +  bool R = false;<br>
> +  for (auto It = Begin;;) {<br>
> +    SectionChunk *Head = *It;<br>
> +    auto Bound = std::partition(It + 1, End, [&](SectionChunk *SC) {<br>
> +      return Eq(Head, SC);<br>
> +    });<br>
> +    if (Bound == End)<br>
> +      return R;<br>
> +    size_t ID = NextID++;<br>
> +    std::for_each(It, Bound, [&](SectionChunk *SC) { SC->GroupID = ID; });<br>
> +    It = Bound;<br>
> +    R = true;<br>
> +  }<br>
> +}<br>
> +<br>
> +static bool forEachGroup(std::vector<SectionChunk *> &SChunks,<br>
> +                         Comparator Eq) {<br>
> +  bool R = false;<br>
> +  for (auto It = SChunks.begin(), End = SChunks.end(); It != End;) {<br>
> +    SectionChunk *Head = *It;<br>
> +    auto Bound = std::find_if(It + 1, End, [&](SectionChunk *SC) {<br>
> +      return SC->GroupID != Head->GroupID;<br>
> +    });<br>
> +    if (partition(It, Bound, Eq))<br>
> +      R = true;<br>
> +    It = Bound;<br>
> +  }<br>
> +  return R;<br>
>  }<br>
><br>
>  // Merge identical COMDAT sections.<br>
>  // Two sections are considered the same if their section headers,<br>
>  // contents and relocations are all the same.<br>
>  void doICF(const std::vector<Chunk *> &Chunks) {<br>
> +  if (Config->Verbose)<br>
> +    llvm::outs() << "\nICF\n";<br>
> +<br>
> +  // Collect only mergeable sections.<br>
>    std::vector<SectionChunk *> SChunks;<br>
> -  for (Chunk *C : Chunks)<br>
> -    if (auto *SC = dyn_cast<SectionChunk>(C))<br>
> -      if (SC->isCOMDAT() && SC->isLive())<br>
> +  for (Chunk *C : Chunks) {<br>
> +    if (auto *SC = dyn_cast<SectionChunk>(C)) {<br>
> +      bool Writable = SC->getPermissions() & llvm::COFF::IMAGE_SCN_MEM_WRITE;<br>
> +      if (SC->isCOMDAT() && SC->isLive() && !Writable) {<br>
>          SChunks.push_back(SC);<br>
> +        SC->GroupID = SC->getHash() | (uint64_t(1) << 63);<br>
> +      } else {<br>
> +        SC->GroupID = NextID++;<br>
> +      }<br>
> +    }<br>
> +  }<br>
><br>
> -  std::vector<Component *> Components = getSCC(SChunks);<br>
> -<br>
> -  while (Components.size() > 0) {<br>
> -    auto Bound = std::partition(Components.begin(), Components.end(),<br>
> -                                [](Component *SCC) { return SCC->Outdegree > 0; });<br>
> -    uniquefy(Bound, Components.end());<br>
> -<br>
> -    for (auto It = Bound, E = Components.end(); It != E; ++It) {<br>
> -      Component *SCC = *It;<br>
> -      for (Component *Pred : SCC->Predecessors)<br>
> -        --Pred->Outdegree;<br>
> +  std::sort(SChunks.begin(), SChunks.end(),<br>
> +            [](SectionChunk *A, SectionChunk *B) {<br>
> +              return A->GroupID < B->GroupID;<br>
> +            });<br>
> +<br>
> +  // Split groups until we get a convergence.<br>
> +  for (SectionChunk *SC : SChunks)<br>
> +    SC->initSuccessors();<br>
> +  forEachGroup(SChunks, SectionChunk::equalsVertex);<br>
> +  while (forEachGroup(SChunks, SectionChunk::equalsEdge));<br>
> +<br>
> +  // Merge sections in the same group.<br>
> +  for (auto It = SChunks.begin(), End = SChunks.end(); It != End;) {<br>
> +    SectionChunk *Head = *It;<br>
> +    auto Bound = std::find_if(It + 1, End, [&](SectionChunk *SC) {<br>
> +      return Head->GroupID != SC->GroupID;<br>
> +    });<br>
> +    if (std::distance(It, Bound) == 1) {<br>
> +      It = Bound;<br>
> +      continue;<br>
> +    }<br>
> +    if (Config->Verbose)<br>
> +      llvm::outs() << "Selected " << Head->getDebugName() << "\n";<br>
> +    for (++It; It != Bound; ++It) {<br>
> +      SectionChunk *SC = *It;<br>
> +      if (Config->Verbose)<br>
> +        llvm::outs() << "  Removed " << SC->getDebugName() << "\n";<br>
> +      SC->replaceWith(Head);<br>
>      }<br>
> -    Components.erase(Bound, Components.end());<br>
>    }<br>
>  }<br>
><br>
><br>
> Modified: lld/trunk/test/COFF/icf-circular.test<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-circular.test?rev=247770&r1=247769&r2=247770&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-circular.test?rev=247770&r1=247769&r2=247770&view=diff</a><br>
> ==============================================================================<br>
> --- lld/trunk/test/COFF/icf-circular.test (original)<br>
> +++ lld/trunk/test/COFF/icf-circular.test Tue Sep 15 22:26:31 2015<br>
> @@ -3,7 +3,8 @@<br>
>  # RUN:   /opt:lldicf /verbose %t.obj > %t.log 2>&1<br>
>  # RUN: FileCheck %s < %t.log<br>
><br>
> -# CHECK: Replaced bar<br>
> +# CHECK: Selected foo<br>
> +# CHECK:   Removed bar<br>
><br>
>  ---<br>
>  header:<br>
><br>
> Modified: lld/trunk/test/COFF/icf-circular2.test<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-circular2.test?rev=247770&r1=247769&r2=247770&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-circular2.test?rev=247770&r1=247769&r2=247770&view=diff</a><br>
> ==============================================================================<br>
> --- lld/trunk/test/COFF/icf-circular2.test (original)<br>
> +++ lld/trunk/test/COFF/icf-circular2.test Tue Sep 15 22:26:31 2015<br>
> @@ -3,8 +3,8 @@<br>
>  # RUN:   /opt:lldicf /verbose %t.obj > %t.log 2>&1<br>
>  # RUN: FileCheck %s < %t.log<br>
><br>
> -# TODO: LLD should be able to merge foo and bar.<br>
> -# CHECK-NOT: Replaced bar<br>
> +# CHECK: Selected foo<br>
> +# CHECK:   Removed bar<br>
><br>
>  ---<br>
>  header:<br>
><br>
> Modified: lld/trunk/test/COFF/icf-simple.test<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-simple.test?rev=247770&r1=247769&r2=247770&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lld/trunk/test/COFF/icf-simple.test?rev=247770&r1=247769&r2=247770&view=diff</a><br>
> ==============================================================================<br>
> --- lld/trunk/test/COFF/icf-simple.test (original)<br>
> +++ lld/trunk/test/COFF/icf-simple.test Tue Sep 15 22:26:31 2015<br>
> @@ -3,7 +3,8 @@<br>
>  # RUN:   /opt:lldicf /verbose %t.obj > %t.log 2>&1<br>
>  # RUN: FileCheck %s < %t.log<br>
><br>
> -# CHECK: Replaced bar<br>
> +# CHECK: Selected foo<br>
> +# CHECK:   Removed bar<br>
><br>
>  ---<br>
>  header:<br>
><br>
><br>
> _______________________________________________<br>
> llvm-commits mailing list<br>
> <a href="mailto:llvm-commits@lists.llvm.org">llvm-commits@lists.llvm.org</a><br>
> <a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits</a><br>
</blockquote></div>