Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lld/ELF/Arch/AArch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class AArch64 : public TargetInfo {
uint64_t val) const override;
RelExpr adjustTlsExpr(RelType type, RelExpr expr) const override;
void relocateAlloc(InputSectionBase &sec, uint8_t *buf) const override;
void initICFSafeThunkBody(InputSection *thunk, Symbol *target) const override;
uint32_t getICFSafeThunkSize() const override;

private:
void relaxTlsGdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) const;
Expand Down Expand Up @@ -926,6 +928,18 @@ static bool needsGotForMemtag(const Relocation &rel) {
return rel.sym->isTagged() && needsGot(rel.expr);
}

static constexpr uint8_t icfSafeThunkCode[] = {0x00, 0x00, 0x00, 0x14};

void AArch64::initICFSafeThunkBody(InputSection *thunk, Symbol *target) const {
thunk->content_ = icfSafeThunkCode;
thunk->size = sizeof(icfSafeThunkCode);
thunk->relocations.push_back({R_PC, R_AARCH64_JUMP26, 0, 0, target});
}

uint32_t AArch64::getICFSafeThunkSize() const {
return sizeof(icfSafeThunkCode);
}

void AArch64::relocateAlloc(InputSectionBase &sec, uint8_t *buf) const {
uint64_t secAddr = sec.getOutputSection()->addr;
if (auto *s = dyn_cast<InputSection>(&sec))
Expand Down
2 changes: 1 addition & 1 deletion lld/ELF/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ enum class CGProfileSortKind { None, Hfsort, Cdsort };
enum class DiscardPolicy { Default, All, Locals, None };

// For --icf={none,safe,all}.
enum class ICFLevel { None, Safe, All };
enum class ICFLevel { None, Safe, SafeThunks, All };

// For --strip-{all,debug}.
enum class StripPolicy { None, All, Debug };
Expand Down
8 changes: 6 additions & 2 deletions lld/ELF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -808,11 +808,14 @@ static int getMemtagMode(Ctx &ctx, opt::InputArgList &args) {
}

static ICFLevel getICF(opt::InputArgList &args) {
auto *arg = args.getLastArg(OPT_icf_none, OPT_icf_safe, OPT_icf_all);
auto *arg = args.getLastArg(OPT_icf_none, OPT_icf_safe, OPT_icf_safe_thunks,
OPT_icf_all);
if (!arg || arg->getOption().getID() == OPT_icf_none)
return ICFLevel::None;
if (arg->getOption().getID() == OPT_icf_safe)
return ICFLevel::Safe;
if (arg->getOption().getID() == OPT_icf_safe_thunks)
return ICFLevel::SafeThunks;
return ICFLevel::All;
}

Expand Down Expand Up @@ -2474,7 +2477,8 @@ static void findKeepUniqueSections(Ctx &ctx, opt::InputArgList &args) {

// Symbols in the dynsym could be address-significant in other executables
// or DSOs, so we conservatively mark them as address-significant.
bool icfSafe = ctx.arg.icf == ICFLevel::Safe;
bool icfSafe =
(ctx.arg.icf == ICFLevel::Safe || ctx.arg.icf == ICFLevel::SafeThunks);
for (Symbol *sym : ctx.symtab->getSymbols())
if (sym->isExported)
markAddrsig(icfSafe, sym);
Expand Down
84 changes: 78 additions & 6 deletions lld/ELF/ICF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@
#include "InputFiles.h"
#include "LinkerScript.h"
#include "OutputSections.h"
#include "Relocations.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Object/ELF.h"
#include "llvm/Support/Parallel.h"
Expand Down Expand Up @@ -121,6 +123,8 @@ template <class ELFT> class ICF {

void forEachClass(llvm::function_ref<void(size_t, size_t)> fn);

void applySafeThunksToRange(size_t begin, size_t end);

Ctx &ctx;
SmallVector<InputSection *, 0> sections;

Expand Down Expand Up @@ -160,10 +164,14 @@ template <class ELFT> class ICF {
}

// Returns true if section S is subject of ICF.
static bool isEligible(InputSection *s) {
if (!s->isLive() || s->keepUnique || !(s->flags & SHF_ALLOC))
static bool isEligible(InputSection *s, bool safeThunksMode) {
if (!s->isLive() || (s->keepUnique && !safeThunksMode) ||
!(s->flags & SHF_ALLOC))
return false;

if (s->keepUnique)
return safeThunksMode && (s->flags & ELF::SHF_EXECINSTR);

// Don't merge writable sections. .data.rel.ro sections are marked as writable
// but are semantically read-only.
if ((s->flags & SHF_WRITE) && s->name != ".data.rel.ro" &&
Expand Down Expand Up @@ -459,6 +467,58 @@ static void combineRelocHashes(unsigned cnt, InputSection *isec,
isec->eqClass[(cnt + 1) % 2] = hash | (1U << 31);
}

// Given a range of identical icfInputs, replace address significant functions
// with a thunk that is just a direct branch to the first function in the
// series. This way we keep only one main body of the function but we still
// retain the address uniqueness of relevant functions by having them be a
// direct branch thunk rather than containing a full copy of the actual function
// body.
template <class ELFT>
void ICF<ELFT>::applySafeThunksToRange(size_t begin, size_t end) {
InputSection *masterIsec = sections[begin];

uint32_t thunkSize = ctx.target->getICFSafeThunkSize();
// If the functions we're dealing with are smaller than the thunk size, then
// just leave them all as-is - creating thunks would be a net loss.
if (masterIsec->getSize() <= thunkSize)
return;

// Find the symbol to create the thunk for.
Symbol *masterSym = nullptr;
for (Symbol *sym : masterIsec->file->getSymbols()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks sslllooowwww - especially if creating lots of thunks. Would be curious on how much this slows down processing.

if (auto *d = dyn_cast<Defined>(sym)) {
if (d->section == masterIsec) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is enough, we also want to check that the offset is 0 within the section - we don't want the thunk to be pointing to the middle of the function. Can we just use getEnclosingSymbol ?

masterSym = sym;
break;
}
}
}

if (!masterSym)
return;

for (size_t i = begin + 1; i < end; ++i) {
InputSection *isec = sections[i];
if (!isec->keepUnique)
break;

auto *thunk = make<InputSection>(*isec);
ctx.target->initICFSafeThunkBody(thunk, masterSym);
thunk->markLive();
auto *osec = isec->getParent();
auto *isd = cast<InputSectionDescription>(osec->commands.back());
isd->sections.push_back(thunk);
osec->commitSection(thunk);
isec->repl = thunk;
isec->markDead();

for (Symbol *sym : thunk->file->getSymbols())
if (auto *d = dyn_cast<Defined>(sym))
if (d->section == isec)
d->size = thunkSize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also want to set offset to 0.

}
}

// The main function of ICF.
template <class ELFT> void ICF<ELFT>::run() {
// Two text sections may have identical content and relocations but different
Expand All @@ -475,10 +535,11 @@ template <class ELFT> void ICF<ELFT>::run() {
[&](InputSection &s) { s.eqClass[0] = s.eqClass[1] = ++uniqueId; });

// Collect sections to merge.
bool safeThunksMode = ctx.arg.icf == ICFLevel::SafeThunks;
for (InputSectionBase *sec : ctx.inputSections) {
auto *s = dyn_cast<InputSection>(sec);
if (s && s->eqClass[0] == 0) {
if (isEligible(s))
if (isEligible(s, safeThunksMode))
sections.push_back(s);
else
// Ineligible sections are assigned unique IDs, i.e. each section
Expand Down Expand Up @@ -510,9 +571,13 @@ template <class ELFT> void ICF<ELFT>::run() {

// From now on, sections in Sections vector are ordered so that sections
// in the same equivalence class are consecutive in the vector.
llvm::stable_sort(sections, [](const InputSection *a, const InputSection *b) {
return a->eqClass[0] < b->eqClass[0];
});
llvm::stable_sort(
sections, [safeThunksMode](const InputSection *a, const InputSection *b) {
if (safeThunksMode)
if (a->eqClass[0] == b->eqClass[0])
return a->keepUnique > b->keepUnique;
return a->eqClass[0] < b->eqClass[0];
});

// Compare static contents and assign unique equivalence class IDs for each
// static content. Use a base offset for these IDs to ensure no overlap with
Expand All @@ -535,12 +600,19 @@ template <class ELFT> void ICF<ELFT>::run() {
auto print = [&ctx = ctx]() -> ELFSyncStream {
return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
};
if (safeThunksMode)
forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
applySafeThunksToRange(begin, end);
});

// Merge sections by the equivalence class.
forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
if (end - begin == 1)
return;
print() << "selected section " << sections[begin];
for (size_t i = begin + 1; i < end; ++i) {
if (safeThunksMode && sections[i]->keepUnique)
continue;
print() << " removing identical section " << sections[i];
sections[begin]->replace(sections[i]);

Expand Down
2 changes: 2 additions & 0 deletions lld/ELF/ICF.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
#ifndef LLD_ELF_ICF_H
#define LLD_ELF_ICF_H

#include "Target.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to touch this file ?

namespace lld::elf {
struct Ctx;
class TargetInfo;

template <class ELFT> void doIcf(Ctx &);
}
Expand Down
2 changes: 2 additions & 0 deletions lld/ELF/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ def icf_all: F<"icf=all">, HelpText<"Enable identical code folding">;

def icf_safe: F<"icf=safe">, HelpText<"Enable safe identical code folding">;

def icf_safe_thunks: F<"icf=safe_thunks">, HelpText<"Enable safe identical code folding with thunks">;

def icf_none: F<"icf=none">, HelpText<"Disable identical code folding (default)">;

def ignore_function_address_equality: FF<"ignore-function-address-equality">,
Expand Down
8 changes: 8 additions & 0 deletions lld/ELF/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "llvm/Object/ELF.h"
#include "llvm/Object/ELFTypes.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include <array>

Expand Down Expand Up @@ -102,6 +103,13 @@ class TargetInfo {
virtual void applyJumpInstrMod(uint8_t *loc, JumpModType type,
JumpModType val) const {}

// Initialize the body of the safe thunk in ICF for the target.
virtual void initICFSafeThunkBody(InputSection *thunk, Symbol *target) const {
llvm_unreachable("target does not support ICF safe thunks");
}
// Returns the size of the safe thunk in ICF for the target.
virtual uint32_t getICFSafeThunkSize() const { return 0; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one should also be llvm_unreachable


virtual ~TargetInfo();

// This deletes a jump insn at the end of the section if it is a fall thru to
Expand Down
Loading