#include "bolt/Passes/CMOVConversion.h"
#include "bolt/Core/BinaryBasicBlock.h"
#include "bolt/Core/BinaryContext.h"
#include "bolt/Utils/CommandLineOpts.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#define DEBUG_TYPE "cmov"
using namespace llvm;
namespace opts {
extern cl::OptionCategory BoltOptCategory;
static cl::opt<int> BiasThreshold(
"cmov-conversion-bias-threshold",
cl::desc("minimum condition bias (pct) to perform a CMOV conversion, "
"-1 to not account bias"),
cl::ReallyHidden, cl::init(1), cl::cat(BoltOptCategory));
static cl::opt<int> MispredictionThreshold(
"cmov-conversion-misprediction-threshold",
cl::desc("minimum misprediction rate (pct) to perform a CMOV conversion, "
"-1 to not account misprediction rate"),
cl::ReallyHidden, cl::init(5), cl::cat(BoltOptCategory));
static cl::opt<bool> ConvertStackMemOperand(
"cmov-conversion-convert-stack-mem-operand",
cl::desc("convert moves with stack memory operand (potentially unsafe)"),
cl::ReallyHidden, cl::init(false), cl::cat(BoltOptCategory));
static cl::opt<bool> ConvertBasePtrStackMemOperand(
"cmov-conversion-convert-rbp-stack-mem-operand",
cl::desc("convert moves with rbp stack memory operand (unsafe, must be off "
"for binaries compiled with -fomit-frame-pointer)"),
cl::ReallyHidden, cl::init(false), cl::cat(BoltOptCategory));
}
namespace llvm {
namespace bolt {
// | RHS
bool isIfThenSubgraph(const BinaryBasicBlock &LHS,
const BinaryBasicBlock &RHS) {
if (LHS.pred_size() != 2 || RHS.pred_size() != 1)
return false;
BinaryBasicBlock *Predecessor = *RHS.pred_begin();
assert(Predecessor && LHS.isPredecessor(Predecessor) && "invalid subgraph");
(void)Predecessor;
if (!LHS.isPredecessor(&RHS))
return false;
if (RHS.succ_size() != 1)
return false;
return true;
}
bool matchCFGSubgraph(BinaryBasicBlock &BB, BinaryBasicBlock *&ConditionalSucc,
BinaryBasicBlock *&UnconditionalSucc,
bool &IsConditionalTaken) {
BinaryBasicBlock *TakenSucc = BB.getConditionalSuccessor(true);
BinaryBasicBlock *FallthroughSucc = BB.getConditionalSuccessor(false);
bool IsIfThenTaken = isIfThenSubgraph(*FallthroughSucc, *TakenSucc);
bool IsIfThenFallthrough = isIfThenSubgraph(*TakenSucc, *FallthroughSucc);
if (!IsIfThenFallthrough && !IsIfThenTaken)
return false;
assert((!IsIfThenFallthrough || !IsIfThenTaken) && "Invalid subgraph");
ConditionalSucc = IsIfThenTaken ? TakenSucc : FallthroughSucc;
UnconditionalSucc = IsIfThenTaken ? FallthroughSucc : TakenSucc;
IsConditionalTaken = IsIfThenTaken;
return true;
}
bool canConvertInstructions(const BinaryContext &BC, const BinaryBasicBlock &BB,
unsigned CC) {
if (BB.empty())
return false;
const MCInst *LastInst = BB.getLastNonPseudoInstr();
if (LastInst == nullptr)
return false;
for (const MCInst &Inst : BB) {
if (BC.MIB->isPseudo(Inst))
continue;
if (&Inst == LastInst && BC.MIB->isUnconditionalBranch(Inst))
continue;
MCInst Cmov(Inst);
if (!BC.MIB->convertMoveToConditionalMove(
Cmov, CC, opts::ConvertStackMemOperand,
opts::ConvertBasePtrStackMemOperand)) {
LLVM_DEBUG({
dbgs() << BB.getName() << ": can't convert instruction ";
BC.printInstruction(dbgs(), Cmov);
});
return false;
}
}
return true;
}
void convertMoves(const BinaryContext &BC, BinaryBasicBlock &BB, unsigned CC) {
for (auto II = BB.begin(), IE = BB.end(); II != IE; ++II) {
if (BC.MIB->isPseudo(*II))
continue;
if (BC.MIB->isUnconditionalBranch(*II)) {
BB.eraseInstruction(II);
return;
}
bool Result = BC.MIB->convertMoveToConditionalMove(
*II, CC, opts::ConvertStackMemOperand,
opts::ConvertBasePtrStackMemOperand);
assert(Result && "unexpected instruction");
(void)Result;
}
}
std::pair<int, uint64_t>
calculateMispredictionRate(const BinaryBasicBlock &BB) {
uint64_t TotalExecCount = 0;
uint64_t TotalMispredictionCount = 0;
for (auto BI : BB.branch_info()) {
TotalExecCount += BI.Count;
if (BI.MispredictedCount != BinaryBasicBlock::COUNT_INFERRED)
TotalMispredictionCount += BI.MispredictedCount;
}
if (!TotalExecCount)
return {-1, TotalMispredictionCount};
return {100.0f * TotalMispredictionCount / TotalExecCount,
TotalMispredictionCount};
}
int calculateConditionBias(const BinaryBasicBlock &BB,
const BinaryBasicBlock &ConditionalSucc) {
if (auto BranchStats = BB.getBranchStats(&ConditionalSucc))
return BranchStats->first;
return -1;
}
void CMOVConversion::Stats::dumpTo(raw_ostream &OS) {
OS << "converted static " << StaticPerformed << "/" << StaticPossible
<< formatv(" ({0:P}) ", getStaticRatio())
<< "hammock(s) into CMOV sequences, with dynamic execution count "
<< DynamicPerformed << "/" << DynamicPossible
<< formatv(" ({0:P}), ", getDynamicRatio()) << "saving " << RemovedMP
<< "/" << PossibleMP << formatv(" ({0:P}) ", getMPRatio())
<< "mispredictions\n";
}
void CMOVConversion::runOnFunction(BinaryFunction &Function) {
BinaryContext &BC = Function.getBinaryContext();
bool Modified = false;
Stats Local;
for (BinaryBasicBlock *BB : post_order(&Function)) {
uint64_t BBExecCount = BB->getKnownExecutionCount();
if (BB->empty() ||
BBExecCount == 0 ||
BB->succ_size() != 2 ||
BB->hasJumpTable())
continue;
assert(BB->isValid() && "traversal internal error");
auto BranchInstrIter = BB->getLastNonPseudo();
if (BranchInstrIter == BB->rend() ||
!BC.MIB->isConditionalBranch(*BranchInstrIter))
continue;
BinaryBasicBlock *ConditionalSucc, *UnconditionalSucc;
bool IsConditionalTaken;
if (!matchCFGSubgraph(*BB, ConditionalSucc, UnconditionalSucc,
IsConditionalTaken)) {
LLVM_DEBUG(dbgs() << BB->getName() << ": couldn't match hammock\n");
continue;
}
unsigned CC = BC.MIB->getCondCode(*BranchInstrIter);
if (!IsConditionalTaken)
CC = BC.MIB->getInvertedCondCode(CC);
if (!canConvertInstructions(BC, *ConditionalSucc, CC))
continue;
int ConditionBias = calculateConditionBias(*BB, *ConditionalSucc);
int MispredictionRate = 0;
uint64_t MispredictionCount = 0;
std::tie(MispredictionRate, MispredictionCount) =
calculateMispredictionRate(*BB);
Local.StaticPossible++;
Local.DynamicPossible += BBExecCount;
Local.PossibleMP += MispredictionCount;
if (ConditionBias < opts::BiasThreshold) {
LLVM_DEBUG(dbgs() << BB->getName() << "->" << ConditionalSucc->getName()
<< " bias = " << ConditionBias
<< ", less than threshold " << opts::BiasThreshold
<< '\n');
continue;
}
if (MispredictionRate < opts::MispredictionThreshold) {
LLVM_DEBUG(dbgs() << BB->getName() << " misprediction rate = "
<< MispredictionRate << ", less than threshold "
<< opts::MispredictionThreshold << '\n');
continue;
}
BB->eraseInstruction(std::prev(BranchInstrIter.base()));
BB->removeAllSuccessors();
convertMoves(BC, *ConditionalSucc, CC);
BB->addInstructions(ConditionalSucc->begin(), ConditionalSucc->end());
ConditionalSucc->markValid(false);
BB->addInstructions(UnconditionalSucc->begin(), UnconditionalSucc->end());
UnconditionalSucc->moveAllSuccessorsTo(BB);
UnconditionalSucc->markValid(false);
Local.StaticPerformed++;
Local.DynamicPerformed += BBExecCount;
Local.RemovedMP += MispredictionCount;
Modified = true;
}
if (Modified)
Function.eraseInvalidBBs();
if (opts::Verbosity > 1) {
BC.outs() << "BOLT-INFO: CMOVConversion: " << Function << ", ";
Local.dumpTo(BC.outs());
}
Global = Global + Local;
}
Error CMOVConversion::runOnFunctions(BinaryContext &BC) {
for (auto &It : BC.getBinaryFunctions()) {
BinaryFunction &Function = It.second;
if (!shouldOptimize(Function))
continue;
runOnFunction(Function);
}
BC.outs() << "BOLT-INFO: CMOVConversion total: ";
Global.dumpTo(BC.outs());
return Error::success();
}
}
}