Skip to content

Commit

Permalink
[BOLT] Add support for Linux kernel static keys jump table (#86090)
Browse files Browse the repository at this point in the history
Runtime code modification used by static keys is the most ubiquitous
self-modifying feature of the Linux kernel. The idea is to to eliminate
the condition check and associated conditional jump on a hot path if
that condition (based on a boolean value of a static key) does not
change often. Whenever they condition changes, the kernel runtime
modifies all code paths associated with that key flipping the code
between nop and (unconditional) jump.
  • Loading branch information
maksfb committed Mar 21, 2024
1 parent 2ab106c commit 6b1cf00
Show file tree
Hide file tree
Showing 9 changed files with 547 additions and 3 deletions.
1 change: 1 addition & 0 deletions bolt/include/bolt/Core/MCPlus.h
Expand Up @@ -73,6 +73,7 @@ class MCAnnotation {
kOffset, /// Offset in the function.
kLabel, /// MCSymbol pointing to this instruction.
kSize, /// Size of the instruction.
kDynamicBranch, /// Jit instruction patched at runtime.
kGeneric /// First generic annotation.
};

Expand Down
17 changes: 17 additions & 0 deletions bolt/include/bolt/Core/MCPlusBuilder.h
Expand Up @@ -1199,6 +1199,16 @@ class MCPlusBuilder {
/// Set instruction size.
void setSize(MCInst &Inst, uint32_t Size) const;

/// Check if the branch instruction could be modified at runtime.
bool isDynamicBranch(const MCInst &Inst) const;

/// Return ID for runtime-modifiable instruction.
std::optional<uint32_t> getDynamicBranchID(const MCInst &Inst) const;

/// Mark instruction as a dynamic branch, i.e. a branch that can be
/// overwritten at runtime.
void setDynamicBranch(MCInst &Inst, uint32_t ID) const;

/// Return MCSymbol that represents a target of this instruction at a given
/// operand number \p OpNum. If there's no symbol associated with
/// the operand - return nullptr.
Expand Down Expand Up @@ -1688,6 +1698,13 @@ class MCPlusBuilder {
llvm_unreachable("not implemented");
}

/// Create long conditional branch with a target-specific conditional code
/// \p CC.
virtual void createLongCondBranch(MCInst &Inst, const MCSymbol *Target,
unsigned CC, MCContext *Ctx) const {
llvm_unreachable("not implemented");
}

/// Reverses the branch condition in Inst and update its taken target to TBB.
///
/// Returns true on success.
Expand Down
8 changes: 7 additions & 1 deletion bolt/lib/Core/BinaryContext.cpp
Expand Up @@ -1939,7 +1939,13 @@ void BinaryContext::printInstruction(raw_ostream &OS, const MCInst &Instruction,
OS << Endl;
return;
}
InstPrinter->printInst(&Instruction, 0, "", *STI, OS);
if (std::optional<uint32_t> DynamicID =
MIB->getDynamicBranchID(Instruction)) {
OS << "\tjit\t" << MIB->getTargetSymbol(Instruction)->getName()
<< " # ID: " << DynamicID;
} else {
InstPrinter->printInst(&Instruction, 0, "", *STI, OS);
}
if (MIB->isCall(Instruction)) {
if (MIB->isTailCall(Instruction))
OS << " # TAILCALL ";
Expand Down
17 changes: 16 additions & 1 deletion bolt/lib/Core/BinaryFunction.cpp
Expand Up @@ -3350,6 +3350,16 @@ void BinaryFunction::fixBranches() {

// Eliminate unnecessary conditional branch.
if (TSuccessor == FSuccessor) {
// FIXME: at the moment, we cannot safely remove static key branches.
if (MIB->isDynamicBranch(*CondBranch)) {
if (opts::Verbosity) {
BC.outs()
<< "BOLT-INFO: unable to remove redundant dynamic branch in "
<< *this << '\n';
}
continue;
}

BB->removeDuplicateConditionalSuccessor(CondBranch);
if (TSuccessor != NextBB)
BB->addBranchInstruction(TSuccessor);
Expand All @@ -3358,8 +3368,13 @@ void BinaryFunction::fixBranches() {

// Reverse branch condition and swap successors.
auto swapSuccessors = [&]() {
if (MIB->isUnsupportedBranch(*CondBranch))
if (MIB->isUnsupportedBranch(*CondBranch)) {
if (opts::Verbosity) {
BC.outs() << "BOLT-INFO: unable to swap successors in " << *this
<< '\n';
}
return false;
}
std::swap(TSuccessor, FSuccessor);
BB->swapConditionalSuccessors();
auto L = BC.scopeLock();
Expand Down
22 changes: 22 additions & 0 deletions bolt/lib/Core/MCPlusBuilder.cpp
Expand Up @@ -303,6 +303,28 @@ void MCPlusBuilder::setSize(MCInst &Inst, uint32_t Size) const {
setAnnotationOpValue(Inst, MCAnnotation::kSize, Size);
}

bool MCPlusBuilder::isDynamicBranch(const MCInst &Inst) const {
if (!hasAnnotation(Inst, MCAnnotation::kDynamicBranch))
return false;
assert(isBranch(Inst) && "Branch expected.");
return true;
}

std::optional<uint32_t>
MCPlusBuilder::getDynamicBranchID(const MCInst &Inst) const {
if (std::optional<int64_t> Value =
getAnnotationOpValue(Inst, MCAnnotation::kDynamicBranch)) {
assert(isBranch(Inst) && "Branch expected.");
return static_cast<uint32_t>(*Value);
}
return std::nullopt;
}

void MCPlusBuilder::setDynamicBranch(MCInst &Inst, uint32_t ID) const {
assert(isBranch(Inst) && "Branch expected.");
setAnnotationOpValue(Inst, MCAnnotation::kDynamicBranch, ID);
}

bool MCPlusBuilder::hasAnnotation(const MCInst &Inst, unsigned Index) const {
return (bool)getAnnotationOpValue(Inst, Index);
}
Expand Down
20 changes: 19 additions & 1 deletion bolt/lib/Passes/BinaryPasses.cpp
Expand Up @@ -107,6 +107,12 @@ static cl::opt<unsigned>
cl::desc("print statistics about basic block ordering"),
cl::init(0), cl::cat(BoltOptCategory));

static cl::opt<bool> PrintLargeFunctions(
"print-large-functions",
cl::desc("print functions that could not be overwritten due to excessive "
"size"),
cl::init(false), cl::cat(BoltOptCategory));

static cl::list<bolt::DynoStats::Category>
PrintSortedBy("print-sorted-by", cl::CommaSeparated,
cl::desc("print functions sorted by order of dyno stats"),
Expand Down Expand Up @@ -570,8 +576,12 @@ Error CheckLargeFunctions::runOnFunctions(BinaryContext &BC) {
uint64_t HotSize, ColdSize;
std::tie(HotSize, ColdSize) =
BC.calculateEmittedSize(BF, /*FixBranches=*/false);
if (HotSize > BF.getMaxSize())
if (HotSize > BF.getMaxSize()) {
if (opts::PrintLargeFunctions)
BC.outs() << "BOLT-INFO: " << BF << " size exceeds allocated space by "
<< (HotSize - BF.getMaxSize()) << " bytes\n";
BF.setSimple(false);
}
};

ParallelUtilities::PredicateTy SkipFunc = [&](const BinaryFunction &BF) {
Expand Down Expand Up @@ -852,6 +862,10 @@ uint64_t SimplifyConditionalTailCalls::fixTailCalls(BinaryFunction &BF) {
assert(Result && "internal error analyzing conditional branch");
assert(CondBranch && "conditional branch expected");

// Skip dynamic branches for now.
if (BF.getBinaryContext().MIB->isDynamicBranch(*CondBranch))
continue;

// It's possible that PredBB is also a successor to BB that may have
// been processed by a previous iteration of the SCTC loop, in which
// case it may have been marked invalid. We should skip rewriting in
Expand Down Expand Up @@ -1012,6 +1026,10 @@ uint64_t ShortenInstructions::shortenInstructions(BinaryFunction &Function) {
const BinaryContext &BC = Function.getBinaryContext();
for (BinaryBasicBlock &BB : Function) {
for (MCInst &Inst : BB) {
// Skip shortening instructions with Size annotation.
if (BC.MIB->getSize(Inst))
continue;

MCInst OriginalInst;
if (opts::Verbosity > 2)
OriginalInst = Inst;
Expand Down

0 comments on commit 6b1cf00

Please sign in to comment.