#include "bolt/Passes/SplitFunctions.h"
#include "bolt/Core/BinaryBasicBlock.h"
#include "bolt/Core/BinaryFunction.h"
#include "bolt/Core/FunctionLayout.h"
#include "bolt/Core/ParallelUtilities.h"
#include "bolt/Utils/CommandLineOpts.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FormatVariadic.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#define DEBUG_TYPE "bolt-opts"
using namespace llvm;
using namespace bolt;
namespace opts {
extern cl::OptionCategory BoltOptCategory;
extern cl::opt<bool> SplitEH;
extern cl::opt<unsigned> ExecutionCountThreshold;
extern cl::opt<uint32_t> RandomSeed;
cl::opt<bool> AggressiveSplitting(
"split-all-cold", cl::desc("outline as many cold basic blocks as possible"),
cl::cat(BoltOptCategory));
static cl::opt<unsigned> SplitAlignThreshold(
"split-align-threshold",
cl::desc("when deciding to split a function, apply this alignment "
"while doing the size comparison (see -split-threshold). "
"Default value: 2."),
cl::init(2),
cl::Hidden, cl::cat(BoltOptCategory));
cl::opt<bool, false, DeprecatedSplitFunctionOptionParser>
SplitFunctions("split-functions",
cl::desc("split functions into fragments"),
cl::cat(BoltOptCategory));
static cl::opt<unsigned> SplitThreshold(
"split-threshold",
cl::desc("split function only if its main size is reduced by more than "
"given amount of bytes. Default value: 0, i.e. split iff the "
"size is reduced. Note that on some architectures the size can "
"increase after splitting."),
cl::init(0), cl::Hidden, cl::cat(BoltOptCategory));
static cl::opt<SplitFunctionsStrategy> SplitStrategy(
"split-strategy", cl::init(SplitFunctionsStrategy::Profile2),
cl::values(clEnumValN(SplitFunctionsStrategy::Profile2, "profile2",
"split each function into a hot and cold fragment "
"using profiling information")),
cl::values(clEnumValN(
SplitFunctionsStrategy::Random2, "random2",
"split each function into a hot and cold fragment at a randomly chosen "
"split point (ignoring any available profiling information)")),
cl::values(clEnumValN(
SplitFunctionsStrategy::RandomN, "randomN",
"split each function into N fragments at a randomly chosen split "
"points (ignoring any available profiling information)")),
cl::values(clEnumValN(
SplitFunctionsStrategy::All, "all",
"split all basic blocks of each function into fragments such that each "
"fragment contains exactly a single basic block")),
cl::desc("strategy used to partition blocks into fragments"),
cl::cat(BoltOptCategory));
}
namespace {
bool hasFullProfile(const BinaryFunction &BF) {
return llvm::all_of(BF.blocks(), [](const BinaryBasicBlock &BB) {
return BB.getExecutionCount() != BinaryBasicBlock::COUNT_NO_PROFILE;
});
}
bool allBlocksCold(const BinaryFunction &BF) {
return llvm::all_of(BF.blocks(), [](const BinaryBasicBlock &BB) {
return BB.getExecutionCount() == 0;
});
}
struct SplitProfile2 final : public SplitStrategy {
bool canSplit(const BinaryFunction &BF) override {
return BF.hasValidProfile() && hasFullProfile(BF) && !allBlocksCold(BF);
}
bool keepEmpty() override { return false; }
void fragment(const BlockIt Start, const BlockIt End) override {
for (BinaryBasicBlock *const BB : llvm::make_range(Start, End)) {
if (BB->getExecutionCount() == 0)
BB->setFragmentNum(FragmentNum::cold());
}
}
};
struct SplitRandom2 final : public SplitStrategy {
std::minstd_rand0 Gen;
SplitRandom2() : Gen(opts::RandomSeed.getValue()) {}
bool canSplit(const BinaryFunction &BF) override { return true; }
bool keepEmpty() override { return false; }
void fragment(const BlockIt Start, const BlockIt End) override {
using DiffT = typename std::iterator_traits<BlockIt>::difference_type;
const DiffT NumBlocks = End - Start;
assert(NumBlocks > 0 && "Cannot fragment empty function");
const auto LastSplitPoint = std::max<DiffT>(NumBlocks - 1, 1);
std::uniform_int_distribution<DiffT> Dist(1, LastSplitPoint);
const DiffT SplitPoint = Dist(Gen);
for (BinaryBasicBlock *BB : llvm::make_range(Start + SplitPoint, End))
BB->setFragmentNum(FragmentNum::cold());
LLVM_DEBUG(dbgs() << formatv("BOLT-DEBUG: randomly chose last {0} (out of "
"{1} possible) blocks to split\n",
NumBlocks - SplitPoint, End - Start));
}
};
struct SplitRandomN final : public SplitStrategy {
std::minstd_rand0 Gen;
SplitRandomN() : Gen(opts::RandomSeed.getValue()) {}
bool canSplit(const BinaryFunction &BF) override { return true; }
bool keepEmpty() override { return false; }
void fragment(const BlockIt Start, const BlockIt End) override {
using DiffT = typename std::iterator_traits<BlockIt>::difference_type;
const DiffT NumBlocks = End - Start;
assert(NumBlocks > 0 && "Cannot fragment empty function");
const DiffT MaximumSplits = NumBlocks - 1;
const auto MinimumSplits = std::min<DiffT>(MaximumSplits, 1);
std::uniform_int_distribution<DiffT> Dist(MinimumSplits, MaximumSplits);
const DiffT NumSplits = Dist(Gen);
SmallVector<unsigned, 0> Lottery(MaximumSplits);
std::iota(Lottery.begin(), Lottery.end(), 1u);
std::shuffle(Lottery.begin(), Lottery.end(), Gen);
Lottery.resize(NumSplits);
llvm::sort(Lottery);
Lottery.push_back(NumBlocks);
unsigned LotteryIndex = 0;
unsigned BBPos = 0;
for (BinaryBasicBlock *const BB : make_range(Start, End)) {
if (BBPos >= Lottery[LotteryIndex])
++LotteryIndex;
BB->setFragmentNum(FragmentNum(LotteryIndex));
++BBPos;
}
}
};
struct SplitAll final : public SplitStrategy {
bool canSplit(const BinaryFunction &BF) override { return true; }
bool keepEmpty() override {
return true;
}
void fragment(const BlockIt Start, const BlockIt End) override {
unsigned Fragment = 0;
for (BinaryBasicBlock *const BB : llvm::make_range(Start, End))
BB->setFragmentNum(FragmentNum(Fragment++));
}
};
}
namespace llvm {
namespace bolt {
bool SplitFunctions::shouldOptimize(const BinaryFunction &BF) const {
if (BF.getKnownExecutionCount() < opts::ExecutionCountThreshold)
return false;
return BinaryFunctionPass::shouldOptimize(BF);
}
void SplitFunctions::runOnFunctions(BinaryContext &BC) {
if (!opts::SplitFunctions)
return;
std::unique_ptr<SplitStrategy> Strategy;
bool ForceSequential = false;
switch (opts::SplitStrategy) {
case SplitFunctionsStrategy::Profile2:
Strategy = std::make_unique<SplitProfile2>();
break;
case SplitFunctionsStrategy::Random2:
Strategy = std::make_unique<SplitRandom2>();
ForceSequential = true;
break;
case SplitFunctionsStrategy::RandomN:
Strategy = std::make_unique<SplitRandomN>();
ForceSequential = true;
break;
case SplitFunctionsStrategy::All:
Strategy = std::make_unique<SplitAll>();
break;
}
ParallelUtilities::PredicateTy SkipFunc = [&](const BinaryFunction &BF) {
return !shouldOptimize(BF);
};
ParallelUtilities::runOnEachFunction(
BC, ParallelUtilities::SchedulingPolicy::SP_BB_LINEAR,
[&](BinaryFunction &BF) { splitFunction(BF, *Strategy); }, SkipFunc,
"SplitFunctions", ForceSequential);
if (SplitBytesHot + SplitBytesCold > 0)
outs() << "BOLT-INFO: splitting separates " << SplitBytesHot
<< " hot bytes from " << SplitBytesCold << " cold bytes "
<< format("(%.2lf%% of split functions is hot).\n",
100.0 * SplitBytesHot / (SplitBytesHot + SplitBytesCold));
}
void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
if (BF.empty())
return;
if (!S.canSplit(BF))
return;
FunctionLayout &Layout = BF.getLayout();
BinaryFunction::BasicBlockOrderType PreSplitLayout(Layout.block_begin(),
Layout.block_end());
BinaryContext &BC = BF.getBinaryContext();
size_t OriginalHotSize;
size_t HotSize;
size_t ColdSize;
if (BC.isX86()) {
std::tie(OriginalHotSize, ColdSize) = BC.calculateEmittedSize(BF);
LLVM_DEBUG(dbgs() << "Estimated size for function " << BF
<< " pre-split is <0x"
<< Twine::utohexstr(OriginalHotSize) << ", 0x"
<< Twine::utohexstr(ColdSize) << ">\n");
}
BinaryFunction::BasicBlockOrderType NewLayout(Layout.block_begin(),
Layout.block_end());
NewLayout.front()->setCanOutline(false);
for (BinaryBasicBlock *const BB : NewLayout) {
if (!BB->canOutline())
continue;
if (BC.isAArch64() && BB->isEntryPoint()) {
BB->setCanOutline(false);
continue;
}
if (BF.hasEHRanges() && !opts::SplitEH) {
if (BB->isLandingPad()) {
BB->setCanOutline(false);
continue;
}
for (MCInst &Instr : *BB) {
if (BC.MIB->isInvoke(Instr)) {
BB->setCanOutline(false);
break;
}
}
}
}
BF.getLayout().updateLayoutIndices();
S.fragment(NewLayout.begin(), NewLayout.end());
for (BinaryBasicBlock *const BB : NewLayout) {
if (!BB->canOutline())
BB->setFragmentNum(FragmentNum::main());
}
if (opts::AggressiveSplitting) {
llvm::stable_sort(NewLayout, [&](const BinaryBasicBlock *const A,
const BinaryBasicBlock *const B) {
return A->getFragmentNum() < B->getFragmentNum();
});
} else if (BF.hasEHRanges() && !opts::SplitEH) {
auto FirstLP = NewLayout.begin();
while ((*FirstLP)->isLandingPad())
++FirstLP;
std::stable_sort(FirstLP, NewLayout.end(),
[&](BinaryBasicBlock *A, BinaryBasicBlock *B) {
return A->getFragmentNum() < B->getFragmentNum();
});
}
FragmentNum CurrentFragment = NewLayout.back()->getFragmentNum();
for (BinaryBasicBlock *const BB : reverse(NewLayout)) {
if (BB->getFragmentNum() > CurrentFragment)
BB->setFragmentNum(CurrentFragment);
CurrentFragment = BB->getFragmentNum();
}
if (!S.keepEmpty()) {
FragmentNum CurrentFragment = FragmentNum::main();
FragmentNum NewFragment = FragmentNum::main();
for (BinaryBasicBlock *const BB : NewLayout) {
if (BB->getFragmentNum() > CurrentFragment) {
CurrentFragment = BB->getFragmentNum();
NewFragment = FragmentNum(NewFragment.get() + 1);
}
BB->setFragmentNum(NewFragment);
}
}
BF.getLayout().update(NewLayout);
TrampolineSetType Trampolines;
if (!BC.HasFixedLoadAddress && BF.hasEHRanges() && BF.isSplit())
Trampolines = createEHTrampolines(BF);
if (BC.isX86() && BF.isSplit()) {
std::tie(HotSize, ColdSize) = BC.calculateEmittedSize(BF);
LLVM_DEBUG(dbgs() << "Estimated size for function " << BF
<< " post-split is <0x" << Twine::utohexstr(HotSize)
<< ", 0x" << Twine::utohexstr(ColdSize) << ">\n");
if (alignTo(OriginalHotSize, opts::SplitAlignThreshold) <=
alignTo(HotSize, opts::SplitAlignThreshold) + opts::SplitThreshold) {
if (opts::Verbosity >= 2) {
outs() << "BOLT-INFO: Reversing splitting of function "
<< formatv("{0}:\n {1:x}, {2:x} -> {3:x}\n", BF, HotSize,
ColdSize, OriginalHotSize);
}
if (PreSplitLayout.size() != BF.size())
PreSplitLayout = mergeEHTrampolines(BF, PreSplitLayout, Trampolines);
for (BinaryBasicBlock &BB : BF)
BB.setFragmentNum(FragmentNum::main());
BF.getLayout().update(PreSplitLayout);
} else {
SplitBytesHot += HotSize;
SplitBytesCold += ColdSize;
}
}
}
SplitFunctions::TrampolineSetType
SplitFunctions::createEHTrampolines(BinaryFunction &BF) const {
const auto &MIB = BF.getBinaryContext().MIB;
TrampolineSetType LPTrampolines;
std::vector<BinaryBasicBlock *> Blocks(BF.pbegin(), BF.pend());
for (BinaryBasicBlock *BB : Blocks) {
for (MCInst &Instr : *BB) {
const std::optional<MCPlus::MCLandingPad> EHInfo = MIB->getEHInfo(Instr);
if (!EHInfo || !EHInfo->first)
continue;
const MCSymbol *LPLabel = EHInfo->first;
BinaryBasicBlock *LPBlock = BF.getBasicBlockForLabel(LPLabel);
if (BB->getFragmentNum() == LPBlock->getFragmentNum())
continue;
const MCSymbol *TrampolineLabel = nullptr;
const TrampolineKey Key(BB->getFragmentNum(), LPLabel);
auto Iter = LPTrampolines.find(Key);
if (Iter != LPTrampolines.end()) {
TrampolineLabel = Iter->second;
} else {
BinaryBasicBlock *TrampolineBB = BF.addBasicBlock();
TrampolineBB->setFragmentNum(BB->getFragmentNum());
TrampolineBB->setExecutionCount(LPBlock->getExecutionCount());
TrampolineBB->addSuccessor(LPBlock, TrampolineBB->getExecutionCount());
TrampolineBB->setCFIState(LPBlock->getCFIState());
TrampolineLabel = TrampolineBB->getLabel();
LPTrampolines.insert(std::make_pair(Key, TrampolineLabel));
}
MIB->updateEHInfo(Instr,
MCPlus::MCLandingPad(TrampolineLabel, EHInfo->second));
}
}
if (LPTrampolines.empty())
return LPTrampolines;
BinaryFunction::BasicBlockOrderType NewLayout(BF.getLayout().block_begin(),
BF.getLayout().block_end());
stable_sort(NewLayout, [&](BinaryBasicBlock *A, BinaryBasicBlock *B) {
return A->getFragmentNum() < B->getFragmentNum();
});
BF.getLayout().update(NewLayout);
BF.fixBranches();
BF.recomputeLandingPads();
return LPTrampolines;
}
SplitFunctions::BasicBlockOrderType SplitFunctions::mergeEHTrampolines(
BinaryFunction &BF, SplitFunctions::BasicBlockOrderType &Layout,
const SplitFunctions::TrampolineSetType &Trampolines) const {
DenseMap<const MCSymbol *, SmallVector<const MCSymbol *, 0>>
IncomingTrampolines;
for (const auto &Entry : Trampolines) {
IncomingTrampolines[Entry.getFirst().Target].emplace_back(
Entry.getSecond());
}
BasicBlockOrderType MergedLayout;
for (BinaryBasicBlock *BB : Layout) {
auto Iter = IncomingTrampolines.find(BB->getLabel());
if (Iter != IncomingTrampolines.end()) {
for (const MCSymbol *const Trampoline : Iter->getSecond()) {
BinaryBasicBlock *LPBlock = BF.getBasicBlockForLabel(Trampoline);
assert(LPBlock && "Could not find matching landing pad block.");
MergedLayout.push_back(LPBlock);
}
}
MergedLayout.push_back(BB);
}
return MergedLayout;
}
}
}