Skip to content

[BOLT] Add support for safe-icf #116275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 44 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
03ca42c
safe-icf
ayermolo Nov 12, 2024
237c02e
Changed icf option
ayermolo Nov 15, 2024
d794fb0
addressed comments
ayermolo Nov 15, 2024
6e853ca
Added four tests for pic/no-pic mode. Tests for global const function…
ayermolo Nov 16, 2024
e36ec02
switched to enum version of icf compile option
ayermolo Nov 16, 2024
b83c335
simplified debug print, removed shared func
ayermolo Nov 20, 2024
39c7902
updated tests
ayermolo Nov 20, 2024
39586b3
addressed comments
ayermolo Nov 20, 2024
debeb6f
updated icf option descriptions
ayermolo Nov 20, 2024
429075e
moved getRelocation* to Relocs.cpp
ayermolo Nov 20, 2024
29cc466
moved code around
ayermolo Nov 20, 2024
bf3bf2b
simplified assembly
ayermolo Nov 22, 2024
e867900
changed comment
ayermolo Nov 22, 2024
ab51077
consolidate main/helper assembly under main. Next step move to test t…
ayermolo Nov 25, 2024
8fe4b3c
move assembly into test
ayermolo Nov 26, 2024
dc41c58
removed some lines from assembly
ayermolo Nov 26, 2024
6287a7f
removed comments, changed demangled names for key functions
ayermolo Nov 26, 2024
c6a1965
removed .text and .file
ayermolo Nov 26, 2024
d393b5e
Changed schedule strategy, cleaned up tests some more, and removed so…
ayermolo Nov 30, 2024
4ace9bd
moved reloc checking code, and changed elf object from assert to retu…
ayermolo Nov 30, 2024
09bc3fa
fixing formatting that was messed up due to VSC format on save gettin…
ayermolo Nov 30, 2024
3912b85
some cleanup after code changes to address comments
ayermolo Dec 1, 2024
769cf24
clang-format
ayermolo Dec 1, 2024
d4f81c9
removed some redundant tests, re-added on more targetted one that jus…
ayermolo Dec 4, 2024
ec54f24
removed one more test I missed
ayermolo Dec 4, 2024
13bcc22
Changed so that general data sections are processed. Moved ICF option…
ayermolo Dec 5, 2024
5110f74
Added vtable filtering, and check fo X86 architecture
ayermolo Dec 6, 2024
d524ed5
moved check as first one
ayermolo Dec 6, 2024
77ae0a2
fix formatter
ayermolo Dec 6, 2024
d03ff1d
minor nits
ayermolo Dec 10, 2024
38758ee
Removed skip and moved processInstructionForFuncReferences, nits
ayermolo Dec 10, 2024
bfb1dc6
refactor bit vector
ayermolo Dec 11, 2024
417196c
nit
ayermolo Dec 11, 2024
bbf015f
changed to using BinarySections, cleaned up code that is no longer ne…
ayermolo Dec 13, 2024
1ef1d27
clang-format
ayermolo Dec 13, 2024
f1813b3
more cleanup
ayermolo Dec 13, 2024
eb882e5
addressed comments
ayermolo Dec 13, 2024
f6dcb90
added comment for static relocs
ayermolo Dec 13, 2024
0070ceb
Added deprecation warning, switched to SparseBitVector
ayermolo Dec 14, 2024
0f96524
changed comments
ayermolo Dec 16, 2024
a9cd467
more cleanup
ayermolo Dec 16, 2024
6a89d5c
addressed comments
ayermolo Dec 17, 2024
645aac2
small change to comment
ayermolo Dec 17, 2024
23ea76b
changed comments for HasAddressTaken
ayermolo Dec 17, 2024
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
19 changes: 19 additions & 0 deletions bolt/include/bolt/Core/BinaryContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,23 @@ class BinaryContext {
void deregisterSectionName(const BinarySection &Section);

public:
enum class ICFLevel {
None,
Safe,
All,
};
static Expected<std::unique_ptr<BinaryContext>>
createBinaryContext(Triple TheTriple, StringRef InputFileName,
SubtargetFeatures *Features, bool IsPIC,
std::unique_ptr<DWARFContext> DwCtx,
JournalingStreams Logger);

/// Returns addend of a relocation.
static int64_t getRelocationAddend(const ELFObjectFileBase *Obj,
const RelocationRef &Rel);
/// Returns symbol of a relocation.
static uint32_t getRelocationSymbol(const ELFObjectFileBase *Obj,
const RelocationRef &Rel);
/// Superset of compiler units that will contain overwritten code that needs
/// new debug info. In a few cases, functions may end up not being
/// overwritten, but it is okay to re-generate debug info for them.
Expand Down Expand Up @@ -660,6 +671,9 @@ class BinaryContext {

std::unique_ptr<MCAsmBackend> MAB;

/// ICF level to use for this binary.
ICFLevel CurrICFLevel{ICFLevel::None};

/// Allows BOLT to print to log whenever it is necessary (with or without
/// const references)
mutable JournalingStreams Logger;
Expand Down Expand Up @@ -1350,6 +1364,9 @@ class BinaryContext {
return Code.size();
}

/// Processes .text section to identify function references.
void processInstructionForFuncReferences(const MCInst &Inst);

/// Compute the native code size for a range of instructions.
/// Note: this can be imprecise wrt the final binary since happening prior to
/// relaxation, as well as wrt the original binary because of opcode
Expand Down Expand Up @@ -1476,6 +1493,8 @@ class BinaryContext {
return *IOAddressMap;
}

ICFLevel getICFLevel() const { return CurrICFLevel; }

raw_ostream &outs() const { return Logger.Out; }

raw_ostream &errs() const { return Logger.Err; }
Expand Down
9 changes: 9 additions & 0 deletions bolt/include/bolt/Core/BinaryFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,9 @@ class BinaryFunction {
/// Function order for streaming into the destination binary.
uint32_t Index{-1U};

/// Indicates if the function is safe to fold.
bool IsSafeToICF{true};

/// Get basic block index assuming it belongs to this function.
unsigned getIndex(const BinaryBasicBlock *BB) const {
assert(BB->getIndex() < BasicBlocks.size());
Expand Down Expand Up @@ -817,6 +820,12 @@ class BinaryFunction {
return nullptr;
}

/// Indicates if the function is safe to fold.
bool isSafeToICF() const { return IsSafeToICF; }

/// Sets the function is not safe to fold.
void setUnsafeICF() { IsSafeToICF = false; }

/// Returns the raw binary encoding of this function.
ErrorOr<ArrayRef<uint8_t>> getData() const;

Expand Down
11 changes: 11 additions & 0 deletions bolt/include/bolt/Passes/IdenticalCodeFolding.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class IdenticalCodeFolding : public BinaryFunctionPass {
return false;
if (BF.hasSDTMarker())
return false;
if (!BF.isSafeToICF())
return false;
return BinaryFunctionPass::shouldOptimize(BF);
}

Expand All @@ -36,6 +38,15 @@ class IdenticalCodeFolding : public BinaryFunctionPass {

const char *getName() const override { return "identical-code-folding"; }
Error runOnFunctions(BinaryContext &BC) override;

private:
/// Analyses .text section and relocations and marks functions that are not
/// safe to fold.
Error markFunctionsUnsafeToFold(BinaryContext &BC);
/// Processes relocations in the .data section to identify function
/// references.
void processDataRelocations(BinaryContext &BC,
const SectionRef &SecRefRelData);
};

} // namespace bolt
Expand Down
91 changes: 91 additions & 0 deletions bolt/lib/Core/BinaryContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,67 @@ cl::opt<std::string> CompDirOverride(
"location, which is used with DW_AT_dwo_name to construct a path "
"to *.dwo files."),
cl::Hidden, cl::init(""), cl::cat(BoltCategory));

cl::opt<bolt::BinaryContext::ICFLevel> ICF(
"icf", cl::desc("fold functions with identical code"),
cl::init(bolt::BinaryContext::ICFLevel::None),
cl::values(
clEnumValN(bolt::BinaryContext::ICFLevel::All, "all", "enable ICF"),
clEnumValN(bolt::BinaryContext::ICFLevel::All, "", "enable ICF"),
clEnumValN(bolt::BinaryContext::ICFLevel::None, "none", "disable ICF"),
clEnumValN(bolt::BinaryContext::ICFLevel::Safe, "safe",
"enable safe ICF")),
cl::ZeroOrMore, cl::ValueOptional, cl::cat(BoltOptCategory));
} // namespace opts

namespace {
template <typename ELFT>
int64_t getRelocationAddend(const ELFObjectFile<ELFT> *Obj,
const RelocationRef &RelRef) {
using ELFShdrTy = typename ELFT::Shdr;
using Elf_Rela = typename ELFT::Rela;
int64_t Addend = 0;
const ELFFile<ELFT> &EF = Obj->getELFFile();
DataRefImpl Rel = RelRef.getRawDataRefImpl();
const ELFShdrTy *RelocationSection = cantFail(EF.getSection(Rel.d.a));
switch (RelocationSection->sh_type) {
default:
llvm_unreachable("unexpected relocation section type");
case ELF::SHT_REL:
break;
case ELF::SHT_RELA: {
const Elf_Rela *RelA = Obj->getRela(Rel);
Addend = RelA->r_addend;
break;
}
}

return Addend;
}

template <typename ELFT>
uint32_t getRelocationSymbol(const ELFObjectFile<ELFT> *Obj,
const RelocationRef &RelRef) {
using ELFShdrTy = typename ELFT::Shdr;
uint32_t Symbol = 0;
const ELFFile<ELFT> &EF = Obj->getELFFile();
DataRefImpl Rel = RelRef.getRawDataRefImpl();
const ELFShdrTy *RelocationSection = cantFail(EF.getSection(Rel.d.a));
switch (RelocationSection->sh_type) {
default:
llvm_unreachable("unexpected relocation section type");
case ELF::SHT_REL:
Symbol = Obj->getRel(Rel)->getSymbol(EF.isMips64EL());
break;
case ELF::SHT_RELA:
Symbol = Obj->getRela(Rel)->getSymbol(EF.isMips64EL());
break;
}

return Symbol;
}
} // anonymous namespace

namespace llvm {
namespace bolt {

Expand Down Expand Up @@ -144,6 +203,7 @@ BinaryContext::BinaryContext(std::unique_ptr<MCContext> Ctx,
Logger(Logger), InitialDynoStats(isAArch64()) {
RegularPageSize = isAArch64() ? RegularPageSizeAArch64 : RegularPageSizeX86;
PageAlign = opts::NoHugePages ? RegularPageSize : HugePageSize;
CurrICFLevel = opts::ICF;
}

BinaryContext::~BinaryContext() {
Expand All @@ -156,6 +216,16 @@ BinaryContext::~BinaryContext() {
clearBinaryData();
}

uint32_t BinaryContext::getRelocationSymbol(const ELFObjectFileBase *Obj,
const RelocationRef &Rel) {
return ::getRelocationSymbol(cast<ELF64LEObjectFile>(Obj), Rel);
}

int64_t BinaryContext::getRelocationAddend(const ELFObjectFileBase *Obj,
const RelocationRef &Rel) {
return ::getRelocationAddend(cast<ELF64LEObjectFile>(Obj), Rel);
}

/// Create BinaryContext for a given architecture \p ArchName and
/// triple \p TripleName.
Expected<std::unique_ptr<BinaryContext>> BinaryContext::createBinaryContext(
Expand Down Expand Up @@ -1945,6 +2015,27 @@ static void printDebugInfo(raw_ostream &OS, const MCInst &Instruction,
OS << " discriminator:" << Row.Discriminator;
}

static bool skipInstruction(const MCInst &Inst, const BinaryContext &BC) {
const bool IsX86 = BC.isX86();
return (BC.MIB->isPseudo(Inst) || BC.MIB->isUnconditionalBranch(Inst) ||
(IsX86 && BC.MIB->isConditionalBranch(Inst)) ||
BC.MIB->isCall(Inst) || BC.MIB->isBranch(Inst));
}
void BinaryContext::processInstructionForFuncReferences(const MCInst &Inst) {
if (CurrICFLevel != ICFLevel::Safe || skipInstruction(Inst, *this))
return;
for (const MCOperand &Op : MCPlus::primeOperands(Inst)) {
if (!Op.isExpr())
continue;
const MCExpr &Expr = *Op.getExpr();
if (Expr.getKind() == MCExpr::SymbolRef) {
const MCSymbol &Symbol = cast<MCSymbolRefExpr>(Expr).getSymbol();
if (BinaryFunction *BF = getFunctionForSymbol(&Symbol))
BF->setUnsafeICF();
}
}
}

void BinaryContext::printInstruction(raw_ostream &OS, const MCInst &Instruction,
uint64_t Offset,
const BinaryFunction *Function,
Expand Down
2 changes: 2 additions & 0 deletions bolt/lib/Core/BinaryFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,8 @@ bool BinaryFunction::scanExternalRefs() {
if (!BC.HasRelocations)
continue;

BC.processInstructionForFuncReferences(Instruction);

if (BranchTargetSymbol) {
BC.MIB->replaceBranchTarget(Instruction, BranchTargetSymbol,
Emitter.LocalCtx.get());
Expand Down
70 changes: 68 additions & 2 deletions bolt/lib/Passes/IdenticalCodeFolding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
//===----------------------------------------------------------------------===//

#include "bolt/Passes/IdenticalCodeFolding.h"
#include "bolt/Core/BinaryContext.h"
#include "bolt/Core/HashUtilities.h"
#include "bolt/Core/ParallelUtilities.h"
#include "bolt/Rewrite/RewriteInstance.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ThreadPool.h"
Expand All @@ -31,6 +33,7 @@ using namespace bolt;
namespace opts {

extern cl::OptionCategory BoltOptCategory;
extern cl::opt<unsigned> Verbosity;

static cl::opt<bool>
ICFUseDFS("icf-dfs", cl::desc("use DFS ordering when using -icf option"),
Expand Down Expand Up @@ -341,6 +344,65 @@ typedef std::unordered_map<BinaryFunction *, std::vector<BinaryFunction *>,
namespace llvm {
namespace bolt {

void IdenticalCodeFolding::processDataRelocations(
BinaryContext &BC, const SectionRef &SecRefRelData) {
for (const RelocationRef &Rel : SecRefRelData.relocations()) {
symbol_iterator SymbolIter = Rel.getSymbol();
const ObjectFile *OwningObj = Rel.getObject();
assert(SymbolIter != OwningObj->symbol_end() &&
"relocation Symbol expected");
const SymbolRef &Symbol = *SymbolIter;
const uint64_t SymbolAddress = cantFail(Symbol.getAddress());
const ELFObjectFileBase *ELFObj = dyn_cast<ELFObjectFileBase>(OwningObj);
if (!ELFObj)
llvm_unreachable("Only ELFObjectFileBase is supported");
const int64_t Addend = BinaryContext::getRelocationAddend(ELFObj, Rel);
BinaryFunction *BF = BC.getBinaryFunctionAtAddress(SymbolAddress + Addend);
if (!BF)
continue;
BF->setUnsafeICF();
}
}

Error IdenticalCodeFolding::markFunctionsUnsafeToFold(BinaryContext &BC) {
Error ErrorStatus = Error::success();
ErrorOr<BinarySection &> SecRelData = BC.getUniqueSectionByName(".rela.data");
if (!BC.HasRelocations)
ErrorStatus = joinErrors(
std::move(ErrorStatus),
createFatalBOLTError(Twine("BOLT-ERROR: Binary built without "
"relocations. Safe ICF is not supported")));
if (ErrorStatus)
return ErrorStatus;
if (SecRelData) {
SectionRef SecRefRelData = SecRelData->getSectionRef();
processDataRelocations(BC, SecRefRelData);
}

ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) {
if (BF.getState() == BinaryFunction::State::CFG) {
for (const BinaryBasicBlock *BB : BF.getLayout().blocks())
for (const MCInst &Inst : *BB)
BC.processInstructionForFuncReferences(Inst);
}
};
ParallelUtilities::PredicateTy SkipFunc =
[&](const BinaryFunction &BF) -> bool { return (bool)ErrorStatus; };
ParallelUtilities::runOnEachFunction(
BC, ParallelUtilities::SchedulingPolicy::SP_TRIVIAL, WorkFun, SkipFunc,
"markUnsafe", /*ForceSequential*/ false, 2);

LLVM_DEBUG({
for (auto &BFIter : BC.getBinaryFunctions()) {
if (BFIter.second.isSafeToICF())
continue;
dbgs() << "BOLT-DEBUG: skipping function " << BFIter.second.getOneName()
<< '\n';
}
});
return ErrorStatus;
}

Error IdenticalCodeFolding::runOnFunctions(BinaryContext &BC) {
const size_t OriginalFunctionCount = BC.getBinaryFunctions().size();
uint64_t NumFunctionsFolded = 0;
Expand Down Expand Up @@ -385,7 +447,7 @@ Error IdenticalCodeFolding::runOnFunctions(BinaryContext &BC) {
"ICF breakdown", opts::TimeICF);
for (auto &BFI : BC.getBinaryFunctions()) {
BinaryFunction &BF = BFI.second;
if (!this->shouldOptimize(BF))
if (!shouldOptimize(BF))
continue;
CongruentBuckets[&BF].emplace(&BF);
}
Expand Down Expand Up @@ -475,7 +537,11 @@ Error IdenticalCodeFolding::runOnFunctions(BinaryContext &BC) {

LLVM_DEBUG(SinglePass.stopTimer());
};

if (BC.getICFLevel() == BinaryContext::ICFLevel::Safe) {
if (Error Err = markFunctionsUnsafeToFold(BC)) {
return Err;
}
}
hashFunctions();
createCongruentBuckets();

Expand Down
7 changes: 2 additions & 5 deletions bolt/lib/Rewrite/BinaryPassManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ static cl::opt<bool>
cl::desc("eliminate unreachable code"), cl::init(true),
cl::cat(BoltOptCategory));

cl::opt<bool> ICF("icf", cl::desc("fold functions with identical code"),
cl::cat(BoltOptCategory));

static cl::opt<bool> JTFootprintReductionFlag(
"jt-footprint-reduction",
cl::desc("make jump tables size smaller at the cost of using more "
Expand Down Expand Up @@ -398,7 +395,7 @@ Error BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) {
opts::StripRepRet);

Manager.registerPass(std::make_unique<IdenticalCodeFolding>(PrintICF),
opts::ICF);
BC.getICFLevel() != BinaryContext::ICFLevel::None);

Manager.registerPass(
std::make_unique<SpecializeMemcpy1>(NeverPrint, opts::SpecializeMemcpy1),
Expand All @@ -423,7 +420,7 @@ Error BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) {
Manager.registerPass(std::make_unique<Inliner>(PrintInline));

Manager.registerPass(std::make_unique<IdenticalCodeFolding>(PrintICF),
opts::ICF);
BC.getICFLevel() != BinaryContext::ICFLevel::None);

Manager.registerPass(std::make_unique<PLTCall>(PrintPLT));

Expand Down
3 changes: 2 additions & 1 deletion bolt/lib/Rewrite/BoltDiff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace opts {
extern cl::OptionCategory BoltDiffCategory;
extern cl::opt<bool> NeverPrint;
extern cl::opt<bool> ICF;
extern cl::opt<bool> SafeICF;

static cl::opt<bool> IgnoreLTOSuffix(
"ignore-lto-suffix",
Expand Down Expand Up @@ -697,7 +698,7 @@ void RewriteInstance::compare(RewriteInstance &RI2) {
}

// Pre-pass ICF
if (opts::ICF) {
if (BC->getICFLevel() != BinaryContext::ICFLevel::None) {
IdenticalCodeFolding ICF(opts::NeverPrint);
outs() << "BOLT-DIFF: Starting ICF pass for binary 1";
BC->logBOLTErrorsAndQuitOnFatal(ICF.runOnFunctions(*BC));
Expand Down
Loading
Loading