382 changes: 382 additions & 0 deletions lld/ELF/ICF.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,382 @@
//===- ICF.cpp ------------------------------------------------------------===//
//
// The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Identical Code Folding is a feature to merge sections not by name (which
// is regular comdat handling) but by contents. If two non-writable 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.
//
// We begin by optimistically putting all sections into a single equivalence
// class. Then we apply a series of checks that split this initial
// equivalence class into more and more refined equivalence classes based on
// the properties by which a section can be distinguished.
//
// We begin by checking that the section contents and flags are the
// same. This only needs to be done once since these properties don't depend
// on the current equivalence class assignment.
//
// Then we split the equivalence classes based on checking that their
// relocations are the same, where relocation targets are compared by their
// equivalence class, not the concrete section. This may need to be done
// multiple times because as the equivalence classes are refined, two
// sections that had a relocation target in the same equivalence class may
// now target different equivalence classes, and hence these two sections
// must be put in different equivalence classes (whereas in the previous
// iteration they were not since the relocation target was the same.)
//
// Our algorithm is smart enough to merge the following mutually-recursive
// 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 ELFT> class ICF {
typedef typename ELFFile<ELFT>::Elf_Shdr Elf_Shdr;
typedef typename ELFFile<ELFT>::Elf_Sym Elf_Sym;
typedef typename ELFFile<ELFT>::uintX_t uintX_t;
typedef Elf_Rel_Impl<ELFT, false> Elf_Rel;

using Comparator = std::function<bool(const InputSection<ELFT> *,
const InputSection<ELFT> *)>;

public:
void run(SymbolTable<ELFT> *Symtab);

private:
uint64_t NextId = 1;

static void setLive(SymbolTable<ELFT> *S);
static uint64_t relSize(InputSection<ELFT> *S);
static uint64_t getHash(InputSection<ELFT> *S);
static bool isEligible(InputSectionBase<ELFT> *Sec);
static std::vector<InputSection<ELFT> *> getSections(SymbolTable<ELFT> *S);
static SymbolBody *getSymbol(const InputSection<ELFT> *Sec,
const Elf_Rel *Rel);

void segregate(InputSection<ELFT> **Begin, InputSection<ELFT> **End,
Comparator Eq);

void forEachGroup(std::vector<InputSection<ELFT> *> &V, Comparator Eq);

template <class RelTy>
static bool relocationEq(iterator_range<const RelTy *> RA,
iterator_range<const RelTy *> RB);

template <class RelTy>
static bool variableEq(const InputSection<ELFT> *A,
const InputSection<ELFT> *B,
iterator_range<const RelTy *> RA,
iterator_range<const RelTy *> RB);

static bool equalsConstant(const InputSection<ELFT> *A,
const InputSection<ELFT> *B);

static bool equalsVariable(const InputSection<ELFT> *A,
const InputSection<ELFT> *B);
};
}
}

// Returns a hash seed for relocation sections for S.
template <class ELFT> uint64_t ICF<ELFT>::relSize(InputSection<ELFT> *S) {
uint64_t Ret = 0;
for (const Elf_Shdr *H : S->RelocSections)
Ret += H->sh_size;
return Ret;
}

// Returns a hash value for S. Note that the information about
// relocation targets is not included in the hash value.
template <class ELFT> uint64_t ICF<ELFT>::getHash(InputSection<ELFT> *S) {
uint64_t Flags = S->getSectionHdr()->sh_flags;
uint64_t H = hash_combine(Flags, S->getSize());
if (S->RelocSections.empty())
return H;
return hash_combine(H, relSize(S));
}

// Returns true if Sec is subject of ICF.
template <class ELFT> bool ICF<ELFT>::isEligible(InputSectionBase<ELFT> *Sec) {
if (!Sec || Sec == InputSection<ELFT>::Discarded || !Sec->Live)
return false;
auto *S = dyn_cast<InputSection<ELFT>>(Sec);
if (!S)
return false;

// .init and .fini contains instructions that must be executed to
// initialize and finalize the process. They cannot and should not
// be merged.
StringRef Name = S->getSectionName();
if (Name == ".init" || Name == ".fini")
return false;

const Elf_Shdr &H = *S->getSectionHdr();
return (H.sh_flags & SHF_ALLOC) && (~H.sh_flags & SHF_WRITE);
}

template <class ELFT>
std::vector<InputSection<ELFT> *>
ICF<ELFT>::getSections(SymbolTable<ELFT> *Symtab) {
std::vector<InputSection<ELFT> *> V;
for (const std::unique_ptr<ObjectFile<ELFT>> &F : Symtab->getObjectFiles())
for (InputSectionBase<ELFT> *S : F->getSections())
if (isEligible(S))
V.push_back(cast<InputSection<ELFT>>(S));
return V;
}

template <class ELFT>
SymbolBody *ICF<ELFT>::getSymbol(const InputSection<ELFT> *Sec,
const Elf_Rel *Rel) {
uint32_t SymIdx = Rel->getSymbol(Config->Mips64EL);
return Sec->File->getSymbolBody(SymIdx);
}

// All sections between Begin and End must have the same group ID before
// you call this function. This function compare sections between Begin
// and End using Eq and assign new group IDs for new groups.
template <class ELFT>
void ICF<ELFT>::segregate(InputSection<ELFT> **Begin, InputSection<ELFT> **End,
Comparator Eq) {
// This loop rearranges [Begin, End) so that all sections that are
// equal in terms of Eq are contiguous. The algorithm is quadratic in
// the worst case, but that is not an issue in practice because the
// number of distinct sections in [Begin, End) is usually very small.
InputSection<ELFT> **I = Begin;
for (;;) {
InputSection<ELFT> *Head = *I;
auto Bound = std::stable_partition(
I + 1, End, [&](InputSection<ELFT> *S) { return Eq(Head, S); });
if (Bound == End)
return;
uint64_t Id = NextId++;
for (; I != Bound; ++I)
(*I)->GroupId = Id;
}
}

template <class ELFT>
void ICF<ELFT>::forEachGroup(std::vector<InputSection<ELFT> *> &V,
Comparator Eq) {
for (auto I = V.begin(), E = V.end(); I != E;) {
InputSection<ELFT> *Head = *I;
auto Bound = std::find_if(I + 1, E, [&](InputSection<ELFT> *S) {
return S->GroupId != Head->GroupId;
});
segregate(&*I, &*Bound, Eq);
I = Bound;
}
}

// Compare two lists of relocations.
template <class ELFT>
template <class RelTy>
bool ICF<ELFT>::relocationEq(iterator_range<const RelTy *> RelsA,
iterator_range<const RelTy *> 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<ELFT>(*IA) != getAddend<ELFT>(*IB))
return false;
return true;
}

// Compare "non-moving" part of two InputSections, namely everything
// except relocation targets.
template <class ELFT>
bool ICF<ELFT>::equalsConstant(const InputSection<ELFT> *A,
const InputSection<ELFT> *B) {
if (A->RelocSections.size() != B->RelocSections.size())
return false;

for (size_t I = 0, E = A->RelocSections.size(); I != E; ++I) {
const Elf_Shdr *RA = A->RelocSections[I];
const Elf_Shdr *RB = B->RelocSections[I];
ELFFile<ELFT> &FileA = A->File->getObj();
ELFFile<ELFT> &FileB = B->File->getObj();
if (RA->sh_type == SHT_RELA) {
if (!relocationEq(FileA.relas(RA), FileB.relas(RB)))
return false;
} else {
if (!relocationEq(FileA.rels(RA), FileB.rels(RB)))
return false;
}
}

return A->getSectionHdr()->sh_flags == B->getSectionHdr()->sh_flags &&
A->getSize() == B->getSize() &&
A->getSectionData() == B->getSectionData();
}

template <class ELFT>
template <class RelTy>
bool ICF<ELFT>::variableEq(const InputSection<ELFT> *A,
const InputSection<ELFT> *B,
iterator_range<const RelTy *> RelsA,
iterator_range<const RelTy *> RelsB) {
const RelTy *IA = RelsA.begin();
const RelTy *EA = RelsA.end();
const RelTy *IB = RelsB.begin();
for (; IA != EA; ++IA, ++IB) {
// If both IA and BA are pointing to the same local symbol,
// this "if" condition must be true.
if (A->File == B->File &&
IA->getSymbol(Config->Mips64EL) == IB->getSymbol(Config->Mips64EL))
continue;

// Otherwise, IA and BA must be pointing to the global symbols.
SymbolBody *SA = getSymbol(A, (const Elf_Rel *)IA);
SymbolBody *SB = getSymbol(B, (const Elf_Rel *)IB);
if (!SA || !SB)
return false;

// The global symbols should be simply the same.
if (SA->repl() == SB->repl())
continue;

// Or, the symbols should be pointing to the same section
// in terms of the group ID.
auto *DA = dyn_cast<DefinedRegular<ELFT>>(SA->repl());
auto *DB = dyn_cast<DefinedRegular<ELFT>>(SB->repl());
if (!DA || !DB)
return false;
if (DA->Sym.st_value != DB->Sym.st_value)
return false;
InputSection<ELFT> *X = dyn_cast<InputSection<ELFT>>(DA->Section);
InputSection<ELFT> *Y = dyn_cast<InputSection<ELFT>>(DB->Section);
if (X && Y && X->GroupId && X->GroupId == Y->GroupId)
continue;
return false;
}
return true;
}

// Compare "moving" part of two InputSections, namely relocation targets.
template <class ELFT>
bool ICF<ELFT>::equalsVariable(const InputSection<ELFT> *A,
const InputSection<ELFT> *B) {
for (size_t I = 0, E = A->RelocSections.size(); I != E; ++I) {
const Elf_Shdr *RA = A->RelocSections[I];
const Elf_Shdr *RB = B->RelocSections[I];
ELFFile<ELFT> &FileA = A->File->getObj();
ELFFile<ELFT> &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 <class ELFT> void ICF<ELFT>::run(SymbolTable<ELFT> *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<InputSection<ELFT> *> V = getSections(Symtab);
for (InputSection<ELFT> *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 V are ordered so that sections in
// the same group are consecutive in the vector.
std::stable_sort(V.begin(), V.end(),
[](InputSection<ELFT> *A, InputSection<ELFT> *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;
for (;;) {
++Cnt;
uint64_t Id = NextId;
forEachGroup(V, equalsVariable);
if (Id == NextId)
break;
}
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<ELFT> *Head = *I++;
auto Bound = std::find_if(I, E, [&](InputSection<ELFT> *S) {
return Head->GroupId != S->GroupId;
});
if (I == Bound)
continue;
if (Config->Verbose)
llvm::outs() << "Selected " << Head->getSectionName() << "\n";
while (I != Bound) {
InputSection<ELFT> *S = *I++;
if (Config->Verbose)
llvm::outs() << " Removed " << S->getSectionName() << "\n";
Head->replace(S);
}
}
}

// ICF entry point function.
template <class ELFT> void elf2::doIcf(SymbolTable<ELFT> *Symtab) {
ICF<ELFT>().run(Symtab);
}

template void elf2::doIcf(SymbolTable<ELF32LE> *);
template void elf2::doIcf(SymbolTable<ELF32BE> *);
template void elf2::doIcf(SymbolTable<ELF64LE> *);
template void elf2::doIcf(SymbolTable<ELF64BE> *);
22 changes: 22 additions & 0 deletions lld/ELF/ICF.h
Original file line number Diff line number Diff line change
@@ -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 ELFT> class SymbolTable;

template <class ELFT> void doIcf(SymbolTable<ELFT> *);
}
}

#endif
5 changes: 4 additions & 1 deletion lld/ELF/InputFiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,10 @@ elf2::ObjectFile<ELFT>::getSection(const Elf_Sym &Sym) const {
return nullptr;
if (Index >= Sections.size() || !Sections[Index])
fatal("Invalid section index");
return Sections[Index];
InputSectionBase<ELFT> *S = Sections[Index];
if (S == InputSectionBase<ELFT>::Discarded)
return S;
return S->Repl;
}

template <class ELFT>
Expand Down
11 changes: 9 additions & 2 deletions lld/ELF/InputSection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ template <class ELFT>
InputSectionBase<ELFT>::InputSectionBase(ObjectFile<ELFT> *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->GcSections;
Expand Down Expand Up @@ -81,7 +81,7 @@ InputSectionBase<ELFT>::getRelocTarget(const Elf_Rel &Rel) const {
uint32_t SymIndex = Rel.getSymbol(Config->Mips64EL);
if (SymbolBody *B = File->getSymbolBody(SymIndex))
if (auto *D = dyn_cast<DefinedRegular<ELFT>>(B->repl()))
return D->Section;
return D->Section->Repl;
// Local symbol
if (const Elf_Sym *Sym = File->getLocalSymbol(SymIndex))
if (InputSectionBase<ELFT> *Sec = File->getSection(*Sym))
Expand Down Expand Up @@ -318,6 +318,13 @@ template <class ELFT> void InputSection<ELFT>::writeTo(uint8_t *Buf) {
}
}

template <class ELFT>
void InputSection<ELFT>::replace(InputSection<ELFT> *Other) {
this->Align = std::max(this->Align, Other->Align);
Other->Repl = this->Repl;
Other->Live = false;
}

template <class ELFT>
SplitInputSection<ELFT>::SplitInputSection(
ObjectFile<ELFT> *File, const Elf_Shdr *Header,
Expand Down
15 changes: 15 additions & 0 deletions lld/ELF/InputSection.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
namespace lld {
namespace elf2 {

template <class ELFT> class ICF;
template <class ELFT> class ObjectFile;
template <class ELFT> class OutputSection;
template <class ELFT> class OutputSectionBase;
Expand Down Expand Up @@ -47,6 +48,13 @@ template <class ELFT> class InputSectionBase {
// Used for garbage collection.
bool Live = false;

// This pointer points to the "real" instance of this instance.
// 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 instance, do not use
// this but instead this->Repl.
InputSectionBase<ELFT> *Repl;

// Returns the size of this section (even if this is a common or BSS.)
size_t getSize() const { return Header->sh_size; }

Expand Down Expand Up @@ -136,6 +144,7 @@ template <class ELFT> class EHInputSection : public SplitInputSection<ELFT> {

// This corresponds to a non SHF_MERGE section of an input file.
template <class ELFT> class InputSection : public InputSectionBase<ELFT> {
friend ICF<ELFT>;
typedef InputSectionBase<ELFT> Base;
typedef typename llvm::object::ELFFile<ELFT>::Elf_Shdr Elf_Shdr;
typedef typename llvm::object::ELFFile<ELFT>::Elf_Rela Elf_Rela;
Expand Down Expand Up @@ -168,6 +177,12 @@ template <class ELFT> class InputSection : public InputSectionBase<ELFT> {

template <bool isRela>
void copyRelocations(uint8_t *Buf, RelIteratorRange<isRela> Rels);

// Called by ICF to merge two input sections.
void replace(InputSection<ELFT> *Other);

// Used by ICF.
uint64_t GroupId = 0;
};

// MIPS .reginfo section provides information on the registers used by the code
Expand Down
3 changes: 3 additions & 0 deletions lld/ELF/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ def fini : Separate<["-"], "fini">, MetaVarName<"<symbol>">,
def hash_style : Separate<["--", "-"], "hash-style">,
HelpText<"Specify hash style (sysv, gnu or both)">;

def icf : Flag<["--"], "icf=all">,
HelpText<"Enable Identical Code Folding.">;

def gc_sections : Flag<["--"], "gc-sections">,
HelpText<"Enable garbage collection of unused sections">;

Expand Down
1 change: 1 addition & 0 deletions lld/ELF/OutputSections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,7 @@ template <class ELFT> void OutputSection<ELFT>::finalize() {

template <class ELFT>
void OutputSection<ELFT>::addSection(InputSectionBase<ELFT> *C) {
assert(C->Live);
auto *S = cast<InputSection<ELFT>>(C);
Sections.push_back(S);
S->OutSec = this;
Expand Down
1 change: 1 addition & 0 deletions lld/ELF/Symbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ typename ELFFile<ELFT>::uintX_t SymbolBody::getVA() const {
// 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.
Expand Down
17 changes: 14 additions & 3 deletions lld/ELF/Symbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,27 @@ template <class ELFT> class DefinedRegular : public DefinedElf<ELFT> {
DefinedRegular(StringRef N, const Elf_Sym &Sym,
InputSectionBase<ELFT> *Section)
: DefinedElf<ELFT>(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<ELFT> *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<ELFT> *&Section;

private:
static InputSectionBase<ELFT> *NullInputSection;
};

template <class ELFT>
InputSectionBase<ELFT> *DefinedRegular<ELFT>::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
Expand Down
5 changes: 5 additions & 0 deletions lld/test/ELF/Inputs/icf2.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.globl f1, f2
.section .text.f2, "ax"
f2:
mov $60, %rdi
call f1
23 changes: 23 additions & 0 deletions lld/test/ELF/icf1.s
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions lld/test/ELF/icf2.s
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions lld/test/ELF/icf3.s
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions lld/test/ELF/icf4.s
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions lld/test/ELF/icf5.s
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions lld/test/ELF/icf6.s
Original file line number Diff line number Diff line change
@@ -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-NOT: Selected .text.f1
# CHECK-NOT: Selected .text.f2

.globl _start, f1, f2
_start:
ret

.section .init, "ax"
f1:
mov $60, %rax
mov $42, %rdi
syscall

.section .fini, "ax"
f2:
mov $60, %rax
mov $42, %rdi
syscall
29 changes: 29 additions & 0 deletions lld/test/ELF/icf7.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 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
# RUN: llvm-objdump -t %t2 | FileCheck -check-prefix=ALIGN %s

# CHECK: Selected .text.f1
# CHECK: Removed .text.f2

# ALIGN: 0000000000011000 .text 00000000 _start
# ALIGN: 0000000000011100 .text 00000000 f1

.globl _start, f1, f2
_start:
ret

.section .text.f1, "ax"
.align 1
f1:
mov $60, %rax
mov $42, %rdi
syscall

.section .text.f2, "ax"
.align 256
f2:
mov $60, %rax
mov $42, %rdi
syscall