Index: ELF/CMakeLists.txt =================================================================== --- ELF/CMakeLists.txt +++ ELF/CMakeLists.txt @@ -6,6 +6,7 @@ Driver.cpp DriverUtils.cpp Error.cpp + ICF.cpp InputFiles.cpp InputSection.cpp LinkerScript.cpp Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -63,6 +63,7 @@ bool ExportDynamic; bool GcSections; bool GnuHash = false; + bool ICF; bool Mips64EL = false; bool NoInhibitExec; bool NoUndefined; Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -10,6 +10,7 @@ #include "Driver.h" #include "Config.h" #include "Error.h" +#include "ICF.h" #include "InputFiles.h" #include "LinkerScript.h" #include "SymbolTable.h" @@ -219,6 +220,7 @@ Config->EnableNewDtags = !Args.hasArg(OPT_disable_new_dtags); Config->ExportDynamic = Args.hasArg(OPT_export_dynamic); Config->GcSections = Args.hasArg(OPT_gc_sections); + Config->ICF = Args.hasArg(OPT_icf); Config->NoInhibitExec = Args.hasArg(OPT_noinhibit_exec); Config->NoUndefined = Args.hasArg(OPT_no_undefined); Config->PrintGcSections = Args.hasArg(OPT_print_gc_sections); @@ -373,5 +375,7 @@ Symtab.scanShlibUndefined(); if (Config->GcSections) markLive(&Symtab); + if (Config->ICF) + doIcf(&Symtab); writeResult(&Symtab); } Index: ELF/ICF.h =================================================================== --- /dev/null +++ ELF/ICF.h @@ -0,0 +1,22 @@ +//===- ICF.h --------------------------------------------------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_ELF_ICF_H +#define LLD_ELF_ICF_H + +namespace lld { +namespace elf2 { + +template class SymbolTable; + +template void doIcf(SymbolTable *); +} +} + +#endif Index: ELF/ICF.cpp =================================================================== --- /dev/null +++ ELF/ICF.cpp @@ -0,0 +1,353 @@ +//===- ICF.cpp ------------------------------------------------------------===// +// +// The LLVM Linker +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// 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 +// identical subgraphs as possible if we consider sections as vertices and +// relocations as edges. It may sound simple, but it is a bit more +// complicated than you might think. The order of processing sections +// matters because merging two sections can make other sections, whose +// relocations now point to the same section, mergeable. Graphs may contain +// cycles. We need a sophisticated algorithm to do this properly and +// efficiently. +// +// What we do in this file is this. We split sections into groups. Sections +// in the same group are considered identical. +// +// First, all sections are grouped by their "constant" values. Constant +// values are values that are never changed by ICF, such as section contents, +// section name, number of relocations, type and offset of each relocation, +// etc. Because we do not care about some relocation targets in this step, +// two sections in the same group may not be 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. +// +// Our algorithm is smart enough to merge the following two functions. +// +// void foo() { bar(); } +// void bar() { foo(); } +// +// This algorithm is so-called "optimistic" algorithm described in +// http://research.google.com/pubs/pub36912.html. (Note that what GNU +// gold implemented is different from the optimistic algorithm.) +// +//===----------------------------------------------------------------------===// + +#include "ICF.h" +#include "Config.h" +#include "OutputSections.h" +#include "SymbolTable.h" + +#include "llvm/ADT/Hashing.h" +#include "llvm/Object/ELF.h" +#include "llvm/Support/ELF.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lld; +using namespace lld::elf2; +using namespace llvm; +using namespace llvm::ELF; +using namespace llvm::object; + +namespace lld { +namespace elf2 { +template class ICF { + typedef typename ELFFile::Elf_Shdr Elf_Shdr; + typedef typename ELFFile::Elf_Sym Elf_Sym; + typedef typename ELFFile::uintX_t uintX_t; + + using Comparator = std::function *, + const InputSection *)>; + +public: + void run(SymbolTable *Symtab); + +private: + uint64_t NextId = 1; + + static void setLive(SymbolTable *S); + static uint64_t numRels(InputSection *S); + static uint64_t getHash(InputSection *S); + static bool isEligible(InputSectionBase *Sec); + static std::vector *> getSections(SymbolTable *S); + + bool partition(InputSection **Begin, InputSection **End, + Comparator Eq); + + bool forEachGroup(std::vector *> &V, Comparator Eq); + + template + static bool constantEq(iterator_range RA, + iterator_range RB); + + template + static bool variableEq(const InputSection *A, + const InputSection *B, + iterator_range RA, + iterator_range RB); + + static bool equalsConstant(const InputSection *A, + const InputSection *B); + + static bool equalsVariable(const InputSection *A, + const InputSection *B); +}; +} +} + +// Returns the number of relocations that S has. +template uint64_t ICF::numRels(InputSection *S) { + ELFFile &EObj = S->File->getObj(); + const Elf_Shdr *RelSec = S->RelocSections[0]; + + if (RelSec->sh_type == SHT_RELA) { + auto Rels = EObj.relas(RelSec); + return Rels.end() - Rels.begin(); + } + auto Rels = EObj.rels(RelSec); + return Rels.end() - Rels.begin(); +} + +// Returns a hash value for S. Note that relocation information is not +// included in the hash value. +template uint64_t ICF::getHash(InputSection *S) { + uint64_t H = hash_combine((uint64_t)S->getSectionHdr()->sh_flags, + S->getSize(), S->getAlign()); + if (S->RelocSections.empty()) + return H; + return hash_combine(H, numRels(S)); +} + +// Returns true if Sec is subject of ICF. +template bool ICF::isEligible(InputSectionBase *Sec) { + if (!Sec || Sec == &InputSection::Discarded || !Sec->Live) + return false; + auto *S = dyn_cast>(Sec); + if (!S) + return false; + if (S->RelocSections.size() > 1) + // Technically, nothing wrong with sections with more than one relocation + // sections, but we don't bother handling such unusual ones. + return false; + const Elf_Shdr &H = *S->getSectionHdr(); + return H.sh_type == SHT_PROGBITS && + (H.sh_flags & SHF_ALLOC) && + (~H.sh_flags & SHF_WRITE); +} + +template +std::vector *> +ICF::getSections(SymbolTable *Symtab) { + std::vector *> V; + for (const std::unique_ptr> &F : Symtab->getObjectFiles()) + for (InputSectionBase *S : F->getSections()) + if (isEligible(S)) + V.push_back(cast>(S)); + return V; +} + +// Compare sections between Begin and End using Eq and assign new group IDs +// for new groups. +template +bool ICF::partition(InputSection **Begin, InputSection **End, + Comparator Eq) { + bool Ret = false; + InputSection **I = Begin; + for (;;) { + InputSection *Head = *I; + auto Bound = std::partition( + I + 1, End, [&](InputSection *S) { return Eq(Head, S); }); + if (Bound == End) + return Ret; + uint64_t Id = NextId++; + for (; I != Bound; ++I) + (*I)->GroupId = Id; + Ret = true; + } +} + +template +bool ICF::forEachGroup(std::vector *> &V, + Comparator Eq) { + bool Ret = false; + for (auto I = V.begin(), E = V.end(); I != E;) { + InputSection *Head = *I; + auto Bound = std::find_if(I + 1, E, [&](InputSection *S) { + return S->GroupId != Head->GroupId; + }); + if (partition(&*I, &*Bound, Eq)) + Ret = true; + I = Bound; + } + return Ret; +} + +// Compare two list of relocations. +template +template +bool ICF::constantEq(iterator_range RelsA, + iterator_range RelsB) { + const RelTy *IA = RelsA.begin(); + const RelTy *EA = RelsA.end(); + const RelTy *IB = RelsB.begin(); + const RelTy *EB = RelsB.end(); + if (EA - IA != EB - IB) + return false; + for (; IA != EA; ++IA, ++IB) + if (IA->r_offset != IB->r_offset || + IA->getType(Config->Mips64EL) != IB->getType(Config->Mips64EL) || + getAddend(*IA) != getAddend(*IB)) + return false; + return true; +} + +// Compare "non-moving" part of two InputSections, namely everything +// except relocations. +template +bool ICF::equalsConstant(const InputSection *A, + const InputSection *B) { + if (A->RelocSections.size() != B->RelocSections.size()) + return false; + + if (!A->RelocSections.empty()) { + const Elf_Shdr *RA = A->RelocSections[0]; + const Elf_Shdr *RB = B->RelocSections[0]; + if (RA->sh_type != RB->sh_type) + return false; + ELFFile &FileA = A->File->getObj(); + ELFFile &FileB = B->File->getObj(); + if (RA->sh_type == SHT_RELA) { + if (!constantEq(FileA.relas(RA), FileB.relas(RB))) + return false; + } else { + if (!constantEq(FileA.rels(RA), FileB.rels(RB))) + return false; + } + } + + return A->getSectionHdr()->sh_flags == B->getSectionHdr()->sh_flags && + A->getSize() == B->getSize() && + A->getAlign() == B->getAlign() && + A->getSectionData() == B->getSectionData(); +} + +template +template +bool ICF::variableEq(const InputSection *A, + const InputSection *B, + iterator_range RelsA, + iterator_range RelsB) { + const RelTy *IA = RelsA.begin(); + const RelTy *EA = RelsA.end(); + const RelTy *IB = RelsB.begin(); + const RelTy *EB = RelsB.end(); + if (EA - IA != EB - IB) + return false; + for (; IA != EA; ++IA, ++IB) { + InputSectionBase *SA = A->getRelocTarget(*IA); + InputSectionBase *SB = B->getRelocTarget(*IB); + if (SA == SB) + continue; + if (!SA || !SB) + return false; + InputSection *X = dyn_cast>(SA); + InputSection *Y = dyn_cast>(SB); + if (!X || !Y || X->GroupId != Y->GroupId) + return false; + } + return true; +} + +// Compare "moving" part of two InputSections, namely relocations. +template +bool ICF::equalsVariable(const InputSection *A, + const InputSection *B) { + if (A->RelocSections.empty()) + return true; + + const Elf_Shdr *RA = A->RelocSections[0]; + const Elf_Shdr *RB = B->RelocSections[0]; + ELFFile &FileA = A->File->getObj(); + ELFFile &FileB = B->File->getObj(); + if (RA->sh_type == SHT_RELA) { + if (!variableEq(A, B, FileA.relas(RA), FileB.relas(RB))) + return false; + } else { + if (!variableEq(A, B, FileA.rels(RA), FileB.rels(RB))) + return false; + } + return true; +} + +// The main function of ICF. +template void ICF::run(SymbolTable *Symtab) { + // Initially, we use hash values as section group IDs. Therefore, + // if two sections have the same ID, they are likely (but not + // guaranteed) to have the same static contents in terms of ICF. + std::vector *> V = getSections(Symtab); + for (InputSection *S : V) + // Set MSB on to avoid collisions with serial group IDs + S->GroupId = getHash(S) | (uint64_t(1) << 63); + + // From now on, sections in Chunks are ordered so that sections in + // the same group are consecutive in the vector. + std::sort(V.begin(), V.end(), + [](InputSection *A, InputSection *B) { + return A->GroupId < B->GroupId; + }); + + // Compare static contents and assign unique IDs for each static content. + forEachGroup(V, equalsConstant); + + // Split groups by comparing relocations until we get a convergence. + int Cnt = 1; + while (forEachGroup(V, equalsVariable)) + ++Cnt; + if (Config->Verbose) + llvm::outs() << "ICF needed " << Cnt << " iterations.\n"; + + // Merge sections in the same group. + for (auto I = V.begin(), E = V.end(); I != E;) { + InputSection *Head = *I++; + auto Bound = std::find_if(I, E, [&](InputSection *S) { + return Head->GroupId != S->GroupId; + }); + if (I == Bound) + continue; + if (Config->Verbose) + llvm::outs() << "Selected " << Head->getSectionName() << "\n"; + while (I != Bound) { + InputSection *S = *I++; + if (Config->Verbose) + llvm::outs() << " Removed " << S->getSectionName() << "\n"; + Head->replace(S); + } + } +} + +// ICF entry point function. +template void elf2::doIcf(SymbolTable *Symtab) { + ICF().run(Symtab); +} + +template void elf2::doIcf(SymbolTable *); +template void elf2::doIcf(SymbolTable *); +template void elf2::doIcf(SymbolTable *); +template void elf2::doIcf(SymbolTable *); Index: ELF/InputFiles.cpp =================================================================== --- ELF/InputFiles.cpp +++ ELF/InputFiles.cpp @@ -288,7 +288,7 @@ return nullptr; if (Index >= Sections.size() || !Sections[Index]) fatal("Invalid section index"); - return Sections[Index]; + return Sections[Index]->Repl; } template Index: ELF/InputSection.h =================================================================== --- ELF/InputSection.h +++ ELF/InputSection.h @@ -17,6 +17,7 @@ namespace lld { namespace elf2 { +template class ICF; template class ObjectFile; template class OutputSection; template class OutputSectionBase; @@ -43,9 +44,14 @@ OutputSectionBase *OutSec = nullptr; // Used for garbage collection. - // Live bit makes sense only when Config->GcSections is true. - bool isLive() const { return !Config->GcSections || Live; } - bool Live = false; + bool Live; + + // This pointer points to the "real" instance of this instnace. + // Usually Repl == this. However, if ICF merges two sections, + // Repl pointer of one section points to another section. So, + // if you need to get a pointer to this instnace, do not use + // this but instead this->Repl. + InputSectionBase *Repl; // Returns the size of this section (even if this is a common or BSS.) size_t getSize() const { return Header->sh_size; } @@ -57,7 +63,7 @@ ObjectFile *getFile() const { return File; } // The writer sets and uses the addresses. - uintX_t getAlign() { + uintX_t getAlign() const { // The ELF spec states that a value of 0 means the section has no alignment // constraits. return std::max(Header->sh_addralign, 1); @@ -72,8 +78,8 @@ ArrayRef getSectionData() const; // Returns a section that Rel is pointing to. Used by the garbage collector. - InputSectionBase *getRelocTarget(const Elf_Rel &Rel); - InputSectionBase *getRelocTarget(const Elf_Rela &Rel); + InputSectionBase *getRelocTarget(const Elf_Rel &Rel) const; + InputSectionBase *getRelocTarget(const Elf_Rela &Rel) const; template using RelIteratorRange = @@ -145,6 +151,7 @@ // This corresponds to a non SHF_MERGE section of an input file. template class InputSection : public InputSectionBase { + friend ICF; typedef InputSectionBase Base; typedef typename llvm::object::ELFFile::Elf_Shdr Elf_Shdr; typedef typename llvm::object::ELFFile::Elf_Rela Elf_Rela; @@ -167,6 +174,12 @@ uint64_t OutSecOff = 0; static bool classof(const InputSectionBase *S); + + // Called by ICF to merge two input sections. + void replace(InputSection *Other); + + // Used by ICF. + uint64_t GroupId = 0; }; // MIPS .reginfo section provides information on the registers used by the code Index: ELF/InputSection.cpp =================================================================== --- ELF/InputSection.cpp +++ ELF/InputSection.cpp @@ -25,7 +25,11 @@ InputSectionBase::InputSectionBase(ObjectFile *File, const Elf_Shdr *Header, Kind SectionKind) - : Header(Header), File(File), SectionKind(SectionKind) {} + : Header(Header), File(File), SectionKind(SectionKind), Repl(this) { + // The garbage collector sets sections' Live bits. + // If GC is disabled, all sections are considered live by default. + Live = Config && !Config->GcSections; +} template StringRef InputSectionBase::getSectionName() const { ErrorOr Name = File->getObj().getSectionName(this->Header); @@ -68,12 +72,12 @@ // Returns a section that Rel relocation is pointing to. template InputSectionBase * -InputSectionBase::getRelocTarget(const Elf_Rel &Rel) { +InputSectionBase::getRelocTarget(const Elf_Rel &Rel) const { // Global symbol uint32_t SymIndex = Rel.getSymbol(Config->Mips64EL); if (SymbolBody *B = File->getSymbolBody(SymIndex)) if (auto *D = dyn_cast>(B->repl())) - return D->Section; + return D->Section->Repl; // Local symbol if (const Elf_Sym *Sym = File->getLocalSymbol(SymIndex)) if (InputSectionBase *Sec = File->getSection(*Sym)) @@ -83,7 +87,7 @@ template InputSectionBase * -InputSectionBase::getRelocTarget(const Elf_Rela &Rel) { +InputSectionBase::getRelocTarget(const Elf_Rela &Rel) const { return getRelocTarget(reinterpret_cast(Rel)); } @@ -262,6 +266,12 @@ } template +void InputSection::replace(InputSection *Other) { + Other->Repl = this->Repl; + Other->Live = false; +} + +template SplitInputSection::SplitInputSection( ObjectFile *File, const Elf_Shdr *Header, typename InputSectionBase::Kind SectionKind) Index: ELF/Options.td =================================================================== --- ELF/Options.td +++ ELF/Options.td @@ -57,6 +57,9 @@ def hash_style : Separate<["--", "-"], "hash-style">, HelpText<"Specify hash style (sysv, gnu or both)">; +def icf : Flag<["--"], "icf=all">, + HelpText<"Enable Identical Comdat Folding.">; + def gc_sections : Flag<["--"], "gc-sections">, HelpText<"Enable garbage collection of unused sections">; Index: ELF/OutputSections.cpp =================================================================== --- ELF/OutputSections.cpp +++ ELF/OutputSections.cpp @@ -734,6 +734,7 @@ template void OutputSection::addSection(InputSectionBase *C) { + assert(C->Live); auto *S = cast>(C); Sections.push_back(S); S->OutSec = this; @@ -885,7 +886,7 @@ // the group are not allowed. Unfortunately .eh_frame breaks that rule // and must be treated specially. For now we just replace the symbol with // 0. - if (Section == &InputSection::Discarded || !Section->isLive()) + if (Section == &InputSection::Discarded || !Section->Live) return Addend; uintX_t Offset = Sym->st_value; @@ -1148,7 +1149,7 @@ if (!HasReloc) fatal("FDE doesn't reference another section"); InputSectionBase *Target = S->getRelocTarget(*RelI); - if (Target != &InputSection::Discarded && Target->isLive()) { + if (Target != &InputSection::Discarded && Target->Live) { uint32_t CieOffset = Offset + 4 - ID; auto I = OffsetToIndex.find(CieOffset); if (I == OffsetToIndex.end()) Index: ELF/Symbols.h =================================================================== --- ELF/Symbols.h +++ ELF/Symbols.h @@ -221,16 +221,27 @@ DefinedRegular(StringRef N, const Elf_Sym &Sym, InputSectionBase *Section) : DefinedElf(SymbolBody::DefinedRegularKind, N, Sym), - Section(Section) {} + Section(Section ? Section->Repl : NullInputSection) {} static bool classof(const SymbolBody *S) { return S->kind() == SymbolBody::DefinedRegularKind; } - // If this is null, the symbol is absolute. - InputSectionBase *Section; + // The input section this symbol belongs to. Notice that this is + // a reference to a pointer. We are using two levels of indirections + // because of ICF. If ICF decides two sections need to be merged, it + // manipulates this Section pointers so that they point to the same + // section. This is a bit tricky, so be careful to not be confused. + // If this is null, the symbol is an absolute symbol. + InputSectionBase *&Section; + +private: + static InputSectionBase *NullInputSection; }; +template +InputSectionBase *DefinedRegular::NullInputSection; + // DefinedSynthetic is a class to represent linker-generated ELF symbols. // The difference from the regular symbol is that DefinedSynthetic symbols // don't belong to any input files or sections. Thus, its constructor Index: ELF/Symbols.cpp =================================================================== --- ELF/Symbols.cpp +++ ELF/Symbols.cpp @@ -42,6 +42,7 @@ // This is an absolute symbol. if (!SC) return D->Sym.st_value; + assert(SC->Live); // Symbol offsets for AMDGPU need to be the offset in bytes of the symbol // from the beginning of the section. Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -549,7 +549,7 @@ continue; if (Sym.st_shndx != SHN_ABS) { InputSectionBase *Section = F->getSection(Sym); - if (!Section->isLive()) + if (!Section->Live) continue; } ++Out::SymTab->NumLocals; @@ -744,7 +744,7 @@ template void reportDiscarded(InputSectionBase *IS, const std::unique_ptr> &File) { - if (!Config->PrintGcSections || !IS || IS->isLive()) + if (!Config->PrintGcSections || !IS || IS->Live) return; llvm::errs() << "removing unused section from '" << IS->getSectionName() << "' in file '" << File->getName() << "'\n"; @@ -752,7 +752,7 @@ template bool Writer::isDiscarded(InputSectionBase *S) const { - return !S || !S->isLive() || S == &InputSection::Discarded || + return !S || !S->Live || S == &InputSection::Discarded || Script->isDiscarded(S); } @@ -786,7 +786,7 @@ if (&D->Sym == &ElfSym::Ignored) return false; // Exclude symbols pointing to garbage-collected sections. - if (D->Section && !D->Section->isLive()) + if (D->Section && !D->Section->Live) return false; } return true; Index: test/ELF/Inputs/icf2.s =================================================================== --- /dev/null +++ test/ELF/Inputs/icf2.s @@ -0,0 +1,5 @@ +.globl f1, f2 +.section .text.f2, "ax" +f2: + mov $60, %rdi + call f1 Index: test/ELF/icf1.s =================================================================== --- /dev/null +++ test/ELF/icf1.s @@ -0,0 +1,23 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t +# RUN: ld.lld %t -o %t2 --icf=all --verbose | FileCheck %s + +# CHECK: Selected .text.f1 +# CHECK: Removed .text.f2 + +.globl _start, f1, f2 +_start: + ret + +.section .text.f1, "ax" +f1: + mov $60, %rax + mov $42, %rdi + syscall + +.section .text.f2, "ax" +f2: + mov $60, %rax + mov $42, %rdi + syscall Index: test/ELF/icf2.s =================================================================== --- /dev/null +++ test/ELF/icf2.s @@ -0,0 +1,17 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t1 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %p/Inputs/icf2.s -o %t2 +# RUN: ld.lld %t1 %t2 -o %t --icf=all --verbose | FileCheck %s + +# CHECK: Selected .text.f1 +# CHECK: Removed .text.f2 + +.globl _start, f1, f2 +_start: + ret + +.section .text.f1, "ax" +f1: + mov $60, %rdi + call f2 Index: test/ELF/icf3.s =================================================================== --- /dev/null +++ test/ELF/icf3.s @@ -0,0 +1,19 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t1 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %p/Inputs/icf2.s -o %t2 +# RUN: ld.lld %t1 %t2 -o %t --icf=all --verbose | FileCheck %s + +# CHECK-NOT: Selected .text.f1 +# CHECK-NOT: Selected .text.f2 + +.globl _start, f1, f2 +_start: + ret + +# This section is not mergeable because the content is different from f2. +.section .text.f1, "ax" +f1: + mov $60, %rdi + call f2 + mov $0, %rax Index: test/ELF/icf4.s =================================================================== --- /dev/null +++ test/ELF/icf4.s @@ -0,0 +1,19 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t +# RUN: ld.lld %t -o %t --icf=all --verbose | FileCheck %s + +# CHECK-NOT: Selected .text.f1 +# CHECK-NOT: Selected .text.f2 + +.globl _start, f1, f2 +_start: + ret + +.section .text.f1, "ax" +f1: + mov $1, %rax + +.section .text.f2, "ax" +f2: + mov $0, %rax Index: test/ELF/icf5.s =================================================================== --- /dev/null +++ test/ELF/icf5.s @@ -0,0 +1,19 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t +# RUN: ld.lld %t -o %t --icf=all --verbose | FileCheck %s + +# CHECK-NOT: Selected .text.f1 +# CHECK-NOT: Selected .text.f2 + +.globl _start, f1, f2 +_start: + ret + +.section .text.f1, "ax" +f1: + mov $0, %rax + +.section .text.f2, "awx" +f2: + mov $0, %rax