Skip to content

Commit af4668c

Browse files
committed
[lld][elf] add safe-thunks in ELF
1 parent 0d8d354 commit af4668c

File tree

8 files changed

+508
-9
lines changed

8 files changed

+508
-9
lines changed

lld/ELF/Arch/AArch64.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class AArch64 : public TargetInfo {
8383
uint64_t val) const override;
8484
RelExpr adjustTlsExpr(RelType type, RelExpr expr) const override;
8585
void relocateAlloc(InputSectionBase &sec, uint8_t *buf) const override;
86+
void initICFSafeThunkBody(InputSection *thunk, Symbol *target) const override;
87+
uint32_t getICFSafeThunkSize() const override;
8688

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

931+
static constexpr uint8_t icfSafeThunkCode[] = {0x00, 0x00, 0x00, 0x14};
932+
933+
void AArch64::initICFSafeThunkBody(InputSection *thunk, Symbol *target) const {
934+
thunk->content_ = icfSafeThunkCode;
935+
thunk->size = sizeof(icfSafeThunkCode);
936+
thunk->relocations.push_back({R_PC, R_AARCH64_JUMP26, 0, 0, target});
937+
}
938+
939+
uint32_t AArch64::getICFSafeThunkSize() const {
940+
return sizeof(icfSafeThunkCode);
941+
}
942+
929943
void AArch64::relocateAlloc(InputSectionBase &sec, uint8_t *buf) const {
930944
uint64_t secAddr = sec.getOutputSection()->addr;
931945
if (auto *s = dyn_cast<InputSection>(&sec))

lld/ELF/Config.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ enum class CGProfileSortKind { None, Hfsort, Cdsort };
9797
enum class DiscardPolicy { Default, All, Locals, None };
9898

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

102102
// For --strip-{all,debug}.
103103
enum class StripPolicy { None, All, Debug };

lld/ELF/Driver.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,11 +808,14 @@ static int getMemtagMode(Ctx &ctx, opt::InputArgList &args) {
808808
}
809809

810810
static ICFLevel getICF(opt::InputArgList &args) {
811-
auto *arg = args.getLastArg(OPT_icf_none, OPT_icf_safe, OPT_icf_all);
811+
auto *arg = args.getLastArg(OPT_icf_none, OPT_icf_safe, OPT_icf_safe_thunks,
812+
OPT_icf_all);
812813
if (!arg || arg->getOption().getID() == OPT_icf_none)
813814
return ICFLevel::None;
814815
if (arg->getOption().getID() == OPT_icf_safe)
815816
return ICFLevel::Safe;
817+
if (arg->getOption().getID() == OPT_icf_safe_thunks)
818+
return ICFLevel::SafeThunks;
816819
return ICFLevel::All;
817820
}
818821

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

24752478
// Symbols in the dynsym could be address-significant in other executables
24762479
// or DSOs, so we conservatively mark them as address-significant.
2477-
bool icfSafe = ctx.arg.icf == ICFLevel::Safe;
2480+
bool icfSafe =
2481+
(ctx.arg.icf == ICFLevel::Safe || ctx.arg.icf == ICFLevel::SafeThunks);
24782482
for (Symbol *sym : ctx.symtab->getSymbols())
24792483
if (sym->isExported)
24802484
markAddrsig(icfSafe, sym);

lld/ELF/ICF.cpp

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@
7777
#include "InputFiles.h"
7878
#include "LinkerScript.h"
7979
#include "OutputSections.h"
80+
#include "Relocations.h"
8081
#include "SymbolTable.h"
8182
#include "Symbols.h"
8283
#include "SyntheticSections.h"
84+
#include "llvm/ADT/ArrayRef.h"
8385
#include "llvm/BinaryFormat/ELF.h"
8486
#include "llvm/Object/ELF.h"
8587
#include "llvm/Support/Parallel.h"
@@ -121,6 +123,8 @@ template <class ELFT> class ICF {
121123

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

126+
void applySafeThunksToRange(size_t begin, size_t end);
127+
124128
Ctx &ctx;
125129
SmallVector<InputSection *, 0> sections;
126130

@@ -160,10 +164,14 @@ template <class ELFT> class ICF {
160164
}
161165

162166
// Returns true if section S is subject of ICF.
163-
static bool isEligible(InputSection *s) {
164-
if (!s->isLive() || s->keepUnique || !(s->flags & SHF_ALLOC))
167+
static bool isEligible(InputSection *s, bool safeThunksMode) {
168+
if (!s->isLive() || (s->keepUnique && !safeThunksMode) ||
169+
!(s->flags & SHF_ALLOC))
165170
return false;
166171

172+
if (s->keepUnique)
173+
return safeThunksMode && (s->flags & ELF::SHF_EXECINSTR);
174+
167175
// Don't merge writable sections. .data.rel.ro sections are marked as writable
168176
// but are semantically read-only.
169177
if ((s->flags & SHF_WRITE) && s->name != ".data.rel.ro" &&
@@ -459,6 +467,58 @@ static void combineRelocHashes(unsigned cnt, InputSection *isec,
459467
isec->eqClass[(cnt + 1) % 2] = hash | (1U << 31);
460468
}
461469

470+
// Given a range of identical icfInputs, replace address significant functions
471+
// with a thunk that is just a direct branch to the first function in the
472+
// series. This way we keep only one main body of the function but we still
473+
// retain the address uniqueness of relevant functions by having them be a
474+
// direct branch thunk rather than containing a full copy of the actual function
475+
// body.
476+
template <class ELFT>
477+
void ICF<ELFT>::applySafeThunksToRange(size_t begin, size_t end) {
478+
InputSection *masterIsec = sections[begin];
479+
480+
uint32_t thunkSize = ctx.target->getICFSafeThunkSize();
481+
// If the functions we're dealing with are smaller than the thunk size, then
482+
// just leave them all as-is - creating thunks would be a net loss.
483+
if (masterIsec->getSize() <= thunkSize)
484+
return;
485+
486+
// Find the symbol to create the thunk for.
487+
Symbol *masterSym = nullptr;
488+
for (Symbol *sym : masterIsec->file->getSymbols()) {
489+
if (auto *d = dyn_cast<Defined>(sym)) {
490+
if (d->section == masterIsec) {
491+
masterSym = sym;
492+
break;
493+
}
494+
}
495+
}
496+
497+
if (!masterSym)
498+
return;
499+
500+
for (size_t i = begin + 1; i < end; ++i) {
501+
InputSection *isec = sections[i];
502+
if (!isec->keepUnique)
503+
break;
504+
505+
auto *thunk = make<InputSection>(*isec);
506+
ctx.target->initICFSafeThunkBody(thunk, masterSym);
507+
thunk->markLive();
508+
auto *osec = isec->getParent();
509+
auto *isd = cast<InputSectionDescription>(osec->commands.back());
510+
isd->sections.push_back(thunk);
511+
osec->commitSection(thunk);
512+
isec->repl = thunk;
513+
isec->markDead();
514+
515+
for (Symbol *sym : thunk->file->getSymbols())
516+
if (auto *d = dyn_cast<Defined>(sym))
517+
if (d->section == isec)
518+
d->size = thunkSize;
519+
}
520+
}
521+
462522
// The main function of ICF.
463523
template <class ELFT> void ICF<ELFT>::run() {
464524
// Two text sections may have identical content and relocations but different
@@ -475,10 +535,11 @@ template <class ELFT> void ICF<ELFT>::run() {
475535
[&](InputSection &s) { s.eqClass[0] = s.eqClass[1] = ++uniqueId; });
476536

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

511572
// From now on, sections in Sections vector are ordered so that sections
512573
// in the same equivalence class are consecutive in the vector.
513-
llvm::stable_sort(sections, [](const InputSection *a, const InputSection *b) {
514-
return a->eqClass[0] < b->eqClass[0];
515-
});
574+
llvm::stable_sort(
575+
sections, [safeThunksMode](const InputSection *a, const InputSection *b) {
576+
if (safeThunksMode)
577+
if (a->eqClass[0] == b->eqClass[0])
578+
return a->keepUnique > b->keepUnique;
579+
return a->eqClass[0] < b->eqClass[0];
580+
});
516581

517582
// Compare static contents and assign unique equivalence class IDs for each
518583
// static content. Use a base offset for these IDs to ensure no overlap with
@@ -535,12 +600,19 @@ template <class ELFT> void ICF<ELFT>::run() {
535600
auto print = [&ctx = ctx]() -> ELFSyncStream {
536601
return {ctx, ctx.arg.printIcfSections ? DiagLevel::Msg : DiagLevel::None};
537602
};
603+
if (safeThunksMode)
604+
forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
605+
applySafeThunksToRange(begin, end);
606+
});
607+
538608
// Merge sections by the equivalence class.
539609
forEachClassRange(0, sections.size(), [&](size_t begin, size_t end) {
540610
if (end - begin == 1)
541611
return;
542612
print() << "selected section " << sections[begin];
543613
for (size_t i = begin + 1; i < end; ++i) {
614+
if (safeThunksMode && sections[i]->keepUnique)
615+
continue;
544616
print() << " removing identical section " << sections[i];
545617
sections[begin]->replace(sections[i]);
546618

lld/ELF/ICF.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
#ifndef LLD_ELF_ICF_H
1010
#define LLD_ELF_ICF_H
1111

12+
#include "Target.h"
1213
namespace lld::elf {
1314
struct Ctx;
15+
class TargetInfo;
1416

1517
template <class ELFT> void doIcf(Ctx &);
1618
}

lld/ELF/Options.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ def icf_all: F<"icf=all">, HelpText<"Enable identical code folding">;
293293

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

296+
def icf_safe_thunks: F<"icf=safe_thunks">, HelpText<"Enable safe identical code folding with thunks">;
297+
296298
def icf_none: F<"icf=none">, HelpText<"Disable identical code folding (default)">;
297299

298300
def ignore_function_address_equality: FF<"ignore-function-address-equality">,

lld/ELF/Target.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "llvm/Object/ELF.h"
1717
#include "llvm/Object/ELFTypes.h"
1818
#include "llvm/Support/Compiler.h"
19+
#include "llvm/Support/ErrorHandling.h"
1920
#include "llvm/Support/MathExtras.h"
2021
#include <array>
2122

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

106+
// Initialize the body of the safe thunk in ICF for the target.
107+
virtual void initICFSafeThunkBody(InputSection *thunk, Symbol *target) const {
108+
llvm_unreachable("target does not support ICF safe thunks");
109+
}
110+
// Returns the size of the safe thunk in ICF for the target.
111+
virtual uint32_t getICFSafeThunkSize() const { return 0; }
112+
105113
virtual ~TargetInfo();
106114

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

0 commit comments

Comments
 (0)