Skip to content

Commit 076bd22

Browse files
authored
[BOLT] Add structure of CDSplit to SplitFunctions (llvm#73430)
This commit establishes the general structure of the CDSplit strategy in SplitFunctions without incorporating the exact splitting logic. With -split-functions -split-strategy=cdsplit, the SplitFunctions pass will run twice: the first time is before function reordering and functions are hot-cold split; the second time is after function reordering and functions are hot-warm-cold split based on the fixed function ordering. Currently, all functions are hot-warm split after the entry block in the second splitting pass. Subsequent commits will introduce the precise splitting logic. NFC.
1 parent 69b0cb9 commit 076bd22

File tree

5 files changed

+93
-9
lines changed

5 files changed

+93
-9
lines changed

bolt/include/bolt/Core/BinaryContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,9 @@ class BinaryContext {
611611
/// Indicates if the binary contains split functions.
612612
bool HasSplitFunctions{false};
613613

614+
/// Indicates if the function ordering of the binary is finalized.
615+
bool HasFinalizedFunctionOrder{false};
616+
614617
/// Is the binary always loaded at a fixed address. Shared objects and
615618
/// position-independent executables (PIEs) are examples of binaries that
616619
/// will have HasFixedLoadAddress set to false.

bolt/include/bolt/Passes/SplitFunctions.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ enum SplitFunctionsStrategy : char {
2323
/// Split each function into a hot and cold fragment using profiling
2424
/// information.
2525
Profile2 = 0,
26+
/// Split each function into a hot, warm, and cold fragment using
27+
/// profiling information.
28+
CDSplit,
2629
/// Split each function into a hot and cold fragment at a randomly chosen
2730
/// split point (ignoring any available profiling information).
2831
Random2,
@@ -40,7 +43,7 @@ class SplitStrategy {
4043

4144
virtual ~SplitStrategy() = default;
4245
virtual bool canSplit(const BinaryFunction &BF) = 0;
43-
virtual bool keepEmpty() = 0;
46+
virtual bool compactFragments() = 0;
4447
virtual void fragment(const BlockIt Start, const BlockIt End) = 0;
4548
};
4649

bolt/lib/Passes/ReorderFunctions.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ void ReorderFunctions::runOnFunctions(BinaryContext &BC) {
427427

428428
reorder(std::move(Clusters), BFs);
429429

430+
BC.HasFinalizedFunctionOrder = true;
431+
430432
std::unique_ptr<std::ofstream> FuncsFile;
431433
if (!opts::GenerateFunctionOrderFile.empty()) {
432434
FuncsFile = std::make_unique<std::ofstream>(opts::GenerateFunctionOrderFile,

bolt/lib/Passes/SplitFunctions.cpp

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ static cl::opt<SplitFunctionsStrategy> SplitStrategy(
9292
cl::values(clEnumValN(SplitFunctionsStrategy::Profile2, "profile2",
9393
"split each function into a hot and cold fragment "
9494
"using profiling information")),
95+
cl::values(clEnumValN(SplitFunctionsStrategy::CDSplit, "cdsplit",
96+
"split each function into a hot, warm, and cold "
97+
"fragment using profiling information")),
9598
cl::values(clEnumValN(
9699
SplitFunctionsStrategy::Random2, "random2",
97100
"split each function into a hot and cold fragment at a randomly chosen "
@@ -126,7 +129,7 @@ struct SplitProfile2 final : public SplitStrategy {
126129
return BF.hasValidProfile() && hasFullProfile(BF) && !allBlocksCold(BF);
127130
}
128131

129-
bool keepEmpty() override { return false; }
132+
bool compactFragments() override { return true; }
130133

131134
void fragment(const BlockIt Start, const BlockIt End) override {
132135
for (BinaryBasicBlock *const BB : llvm::make_range(Start, End)) {
@@ -136,14 +139,59 @@ struct SplitProfile2 final : public SplitStrategy {
136139
}
137140
};
138141

142+
struct SplitCacheDirected final : public SplitStrategy {
143+
using BasicBlockOrder = BinaryFunction::BasicBlockOrderType;
144+
145+
bool canSplit(const BinaryFunction &BF) override {
146+
return BF.hasValidProfile() && hasFullProfile(BF) && !allBlocksCold(BF);
147+
}
148+
149+
// When some functions are hot-warm split and others are hot-warm-cold split,
150+
// we do not want to change the fragment numbers of the blocks in the hot-warm
151+
// split functions.
152+
bool compactFragments() override { return false; }
153+
154+
void fragment(const BlockIt Start, const BlockIt End) override {
155+
BasicBlockOrder BlockOrder(Start, End);
156+
BinaryFunction &BF = *BlockOrder.front()->getFunction();
157+
158+
size_t BestSplitIndex = findSplitIndex(BF, BlockOrder);
159+
160+
// Assign fragments based on the computed best split index.
161+
// All basic blocks with index up to the best split index become hot.
162+
// All remaining blocks are warm / cold depending on if count is
163+
// greater than 0 or not.
164+
FragmentNum Main(0);
165+
FragmentNum Cold(1);
166+
FragmentNum Warm(2);
167+
for (size_t Index = 0; Index < BlockOrder.size(); Index++) {
168+
BinaryBasicBlock *BB = BlockOrder[Index];
169+
if (Index <= BestSplitIndex)
170+
BB->setFragmentNum(Main);
171+
else
172+
BB->setFragmentNum(BB->getKnownExecutionCount() > 0 ? Warm : Cold);
173+
}
174+
}
175+
176+
private:
177+
/// Find the best index for splitting. The returned value is the index of the
178+
/// last hot basic block. Hence, "no splitting" is equivalent to returning the
179+
/// value which is one less than the size of the function.
180+
size_t findSplitIndex(const BinaryFunction &BF,
181+
const BasicBlockOrder &BlockOrder) {
182+
// Placeholder: hot-warm split after entry block.
183+
return 0;
184+
}
185+
};
186+
139187
struct SplitRandom2 final : public SplitStrategy {
140188
std::minstd_rand0 Gen;
141189

142190
SplitRandom2() : Gen(opts::RandomSeed.getValue()) {}
143191

144192
bool canSplit(const BinaryFunction &BF) override { return true; }
145193

146-
bool keepEmpty() override { return false; }
194+
bool compactFragments() override { return true; }
147195

148196
void fragment(const BlockIt Start, const BlockIt End) override {
149197
using DiffT = typename std::iterator_traits<BlockIt>::difference_type;
@@ -170,7 +218,7 @@ struct SplitRandomN final : public SplitStrategy {
170218

171219
bool canSplit(const BinaryFunction &BF) override { return true; }
172220

173-
bool keepEmpty() override { return false; }
221+
bool compactFragments() override { return true; }
174222

175223
void fragment(const BlockIt Start, const BlockIt End) override {
176224
using DiffT = typename std::iterator_traits<BlockIt>::difference_type;
@@ -217,10 +265,10 @@ struct SplitRandomN final : public SplitStrategy {
217265
struct SplitAll final : public SplitStrategy {
218266
bool canSplit(const BinaryFunction &BF) override { return true; }
219267

220-
bool keepEmpty() override {
268+
bool compactFragments() override {
221269
// Keeping empty fragments allows us to test, that empty fragments do not
222270
// generate symbols.
223-
return true;
271+
return false;
224272
}
225273

226274
void fragment(const BlockIt Start, const BlockIt End) override {
@@ -246,10 +294,26 @@ void SplitFunctions::runOnFunctions(BinaryContext &BC) {
246294
if (!opts::SplitFunctions)
247295
return;
248296

297+
// If split strategy is not CDSplit, then a second run of the pass is not
298+
// needed after function reordering.
299+
if (BC.HasFinalizedFunctionOrder &&
300+
opts::SplitStrategy != SplitFunctionsStrategy::CDSplit)
301+
return;
302+
249303
std::unique_ptr<SplitStrategy> Strategy;
250304
bool ForceSequential = false;
251305

252306
switch (opts::SplitStrategy) {
307+
case SplitFunctionsStrategy::CDSplit:
308+
// CDSplit runs two splitting passes: hot-cold splitting (SplitPrfoile2)
309+
// before function reordering and hot-warm-cold splitting
310+
// (SplitCacheDirected) after function reordering.
311+
if (BC.HasFinalizedFunctionOrder)
312+
Strategy = std::make_unique<SplitCacheDirected>();
313+
else
314+
Strategy = std::make_unique<SplitProfile2>();
315+
opts::AggressiveSplitting = true;
316+
break;
253317
case SplitFunctionsStrategy::Profile2:
254318
Strategy = std::make_unique<SplitProfile2>();
255319
break;
@@ -382,7 +446,7 @@ void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
382446
CurrentFragment = BB->getFragmentNum();
383447
}
384448

385-
if (!S.keepEmpty()) {
449+
if (S.compactFragments()) {
386450
FragmentNum CurrentFragment = FragmentNum::main();
387451
FragmentNum NewFragment = FragmentNum::main();
388452
for (BinaryBasicBlock *const BB : NewLayout) {
@@ -394,7 +458,7 @@ void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
394458
}
395459
}
396460

397-
BF.getLayout().update(NewLayout);
461+
const bool LayoutUpdated = BF.getLayout().update(NewLayout);
398462

399463
// For shared objects, invoke instructions and corresponding landing pads
400464
// have to be placed in the same fragment. When we split them, create
@@ -404,7 +468,7 @@ void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
404468
Trampolines = createEHTrampolines(BF);
405469

406470
// Check the new size to see if it's worth splitting the function.
407-
if (BC.isX86() && BF.isSplit()) {
471+
if (BC.isX86() && LayoutUpdated) {
408472
std::tie(HotSize, ColdSize) = BC.calculateEmittedSize(BF);
409473
LLVM_DEBUG(dbgs() << "Estimated size for function " << BF
410474
<< " post-split is <0x" << Twine::utohexstr(HotSize)
@@ -431,6 +495,11 @@ void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
431495
SplitBytesCold += ColdSize;
432496
}
433497
}
498+
499+
// Fix branches if the splitting decision of the pass after function
500+
// reordering is different from that of the pass before function reordering.
501+
if (LayoutUpdated && BC.HasFinalizedFunctionOrder)
502+
BF.fixBranches();
434503
}
435504

436505
SplitFunctions::TrampolineSetType

bolt/lib/Rewrite/BinaryPassManager.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,13 @@ void BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) {
430430
Manager.registerPass(
431431
std::make_unique<ReorderFunctions>(PrintReorderedFunctions));
432432

433+
// This is the second run of the SplitFunctions pass required by certain
434+
// splitting strategies (e.g. cdsplit). Running the SplitFunctions pass again
435+
// after ReorderFunctions allows the finalized function order to be utilized
436+
// to make more sophisticated splitting decisions, like hot-warm-cold
437+
// splitting.
438+
Manager.registerPass(std::make_unique<SplitFunctions>(PrintSplit));
439+
433440
// Print final dyno stats right while CFG and instruction analysis are intact.
434441
Manager.registerPass(
435442
std::make_unique<DynoStatsPrintPass>(

0 commit comments

Comments
 (0)