Skip to content

Commit a57e246

Browse files
committed
ColdBlockInfo: consider post-dominating coldness
A block that is post-dominated by a cold exit block is itself cold. The most basic kind of cold-exit is one that terminates the program. We can also consider a call to any `Never` returning function as a cold-exit, though the block(s) leading up to that call may be executed frequently because of concurrency. For now, I'm ignoring this case. To help propagate this exit-block coldness, without rewriting the algorithm to be something more sophisticated, I've simply added an analysis stage that seeds the ColdBlockInfo with blocks that are definitely cold due to these cold-exits.
1 parent 0ff67cc commit a57e246

File tree

11 files changed

+265
-33
lines changed

11 files changed

+265
-33
lines changed

include/swift/AST/SILOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class SILOptions {
122122
/// within the optimizer. This influences static branch prediction.
123123
bool EnableThrowsPrediction = false;
124124

125+
/// Controls whether to say that blocks ending in an 'unreachable' are cold.
126+
bool EnableNoReturnCold = false;
127+
125128
/// Should we run any SIL performance optimizations
126129
///
127130
/// Useful when you want to enable -O LLVM opts but not -O SIL opts.

include/swift/Option/FrontendOptions.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,11 @@ def enable_throws_prediction : Flag<["-"], "enable-throws-prediction">,
545545
def disable_throws_prediction : Flag<["-"], "disable-throws-prediction">,
546546
HelpText<"Disables optimization assumption that functions rarely throw errors.">;
547547

548+
def enable_noreturn_prediction : Flag<["-"], "enable-noreturn-prediction">,
549+
HelpText<"Enables optimization assumption that calls to no-return functions are cold.">;
550+
def disable_noreturn_prediction : Flag<["-"], "disable-noreturn-prediction">,
551+
HelpText<"Disables optimization assumption that calls to no-return functions are cold.">;
552+
548553
def disable_access_control : Flag<["-"], "disable-access-control">,
549554
HelpText<"Don't respect access control restrictions">;
550555
def enable_access_control : Flag<["-"], "enable-access-control">,

include/swift/SILOptimizer/Analysis/ColdBlockInfo.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
namespace swift {
2020
class DominanceAnalysis;
21+
class PostDominanceAnalysis;
2122
class SILBasicBlock;
2223

2324
/// Cache a set of basic blocks that have been determined to be cold or hot.
@@ -26,6 +27,7 @@ class SILBasicBlock;
2627
/// across passes.
2728
class ColdBlockInfo {
2829
DominanceAnalysis *DA;
30+
PostDominanceAnalysis *PDA;
2931

3032
/// Each block in this map has been determined to be either cold or hot.
3133
llvm::DenseMap<const SILBasicBlock*, bool> ColdBlockMap;
@@ -45,6 +47,9 @@ class ColdBlockInfo {
4547
RecursionDepthLimit = 3
4648
};
4749

50+
// \returns none if there is no known temperature for the block.
51+
std::optional<bool> queryIsCold(const SILBasicBlock *BB);
52+
4853
BranchHint getBranchHint(SILValue Cond, int recursionDepth);
4954

5055
bool isSlowPath(const SILBasicBlock *FromBB, const SILBasicBlock *ToBB,
@@ -54,7 +59,11 @@ class ColdBlockInfo {
5459
int recursionDepth);
5560

5661
public:
57-
ColdBlockInfo(DominanceAnalysis *DA): DA(DA) {}
62+
ColdBlockInfo(DominanceAnalysis *DA, PostDominanceAnalysis *PDA);
63+
64+
/// Pre-seed the info with information starting at the exits of the function.
65+
/// Recommended prior to querying if blocks are cold in a function.
66+
void analyzeExits(SILFunction *Fn);
5867

5968
bool isSlowPath(const SILBasicBlock *FromBB, const SILBasicBlock *ToBB) {
6069
return isSlowPath(FromBB, ToBB, 0);

include/swift/SILOptimizer/Utils/PerformanceInlinerUtils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ class ShortestPathAnalysis {
429429
return;
430430

431431
BlockInfoStorage.resize(numBlocks);
432+
CBI.analyzeExits(F);
432433

433434
// First step: compute the length of the blocks.
434435
unsigned BlockIdx = 0;

lib/DriverTool/sil_opt_main.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,10 @@ struct SILOptOptions {
293293
EnableThrowsPrediction = llvm::cl::opt<bool>("enable-throws-prediction",
294294
llvm::cl::desc("Enables optimization assumption that functions rarely throw errors."));
295295

296+
llvm::cl::opt<bool>
297+
EnableNoReturnCold = llvm::cl::opt<bool>("enable-noreturn-prediction",
298+
llvm::cl::desc("Enables optimization assumption that calls to no-return functions are cold."));
299+
296300
llvm::cl::opt<bool>
297301
EnableMoveInoutStackProtection = llvm::cl::opt<bool>("enable-move-inout-stack-protector",
298302
llvm::cl::desc("Enable the stack protector by moving values to temporaries."));
@@ -852,6 +856,7 @@ int sil_opt_main(ArrayRef<const char *> argv, void *MainAddr) {
852856
SILOpts.EnableSpeculativeDevirtualization = options.EnableSpeculativeDevirtualization;
853857
SILOpts.EnableAsyncDemotion = options.EnableAsyncDemotion;
854858
SILOpts.EnableThrowsPrediction = options.EnableThrowsPrediction;
859+
SILOpts.EnableNoReturnCold = options.EnableNoReturnCold;
855860
SILOpts.IgnoreAlwaysInline = options.IgnoreAlwaysInline;
856861
SILOpts.EnableOSSAModules = options.EnableOSSAModules;
857862
SILOpts.EnableSILOpaqueValues = options.EnableSILOpaqueValues;

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2661,6 +2661,9 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
26612661
Opts.EnableThrowsPrediction = Args.hasFlag(
26622662
OPT_enable_throws_prediction, OPT_disable_throws_prediction,
26632663
Opts.EnableThrowsPrediction);
2664+
Opts.EnableNoReturnCold = Args.hasFlag(
2665+
OPT_enable_noreturn_prediction, OPT_disable_noreturn_prediction,
2666+
Opts.EnableNoReturnCold);
26642667
Opts.EnableActorDataRaceChecks |= Args.hasFlag(
26652668
OPT_enable_actor_data_race_checks,
26662669
OPT_disable_actor_data_race_checks, /*default=*/false);

lib/SILOptimizer/Analysis/ColdBlockInfo.cpp

Lines changed: 124 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,25 @@
1414
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
1515
#include "swift/SIL/SILArgument.h"
1616
#include "swift/AST/SemanticAttrs.h"
17+
#include "swift/SIL/SILModule.h"
1718

1819
#define DEBUG_TYPE "cold-block-info"
1920

2021
using namespace swift;
2122

23+
ColdBlockInfo::ColdBlockInfo(DominanceAnalysis *DA,
24+
PostDominanceAnalysis *PDA) : DA(DA), PDA(PDA) {
25+
LLVM_DEBUG(llvm::dbgs() << "ColdBlockInfo: constructed\n");
26+
}
27+
28+
std::optional<bool> ColdBlockInfo::queryIsCold(const SILBasicBlock *BB) {
29+
auto result = ColdBlockMap.find(BB);
30+
if (result != ColdBlockMap.end()) {
31+
return result->getSecond();
32+
}
33+
return std::nullopt;
34+
}
35+
2236
/// Peek through an extract of Bool.value.
2337
static SILValue getCondition(SILValue C) {
2438
if (auto *SEI = dyn_cast<StructExtractInst>(C)) {
@@ -29,6 +43,42 @@ static SILValue getCondition(SILValue C) {
2943
return C;
3044
}
3145

46+
/// A cold terminator is one where it's unlikely to be reached, which are
47+
/// function exits that are less-common. A cold terminator implies a cold block.
48+
/// - 'unreachable', as it's never executed.
49+
/// - 'throw', if throws prediction is enabled.
50+
static bool isColdTerminator(const TermInst *term) {
51+
switch (term->getTermKind()) {
52+
case TermKind::AwaitAsyncContinuationInst:
53+
case TermKind::BranchInst:
54+
case TermKind::CondBranchInst:
55+
case TermKind::SwitchValueInst:
56+
case TermKind::SwitchEnumInst:
57+
case TermKind::SwitchEnumAddrInst:
58+
case TermKind::DynamicMethodBranchInst:
59+
case TermKind::CheckedCastBranchInst:
60+
case TermKind::CheckedCastAddrBranchInst:
61+
case TermKind::TryApplyInst:
62+
case TermKind::YieldInst:
63+
case TermKind::ReturnInst:
64+
return false;
65+
case TermKind::ThrowInst:
66+
case TermKind::ThrowAddrInst:
67+
case TermKind::UnwindInst:
68+
return term->getModule().getOptions().EnableThrowsPrediction;
69+
case TermKind::UnreachableInst:
70+
// For now, assume it's always cold, since it's executed once.
71+
// Not all functions in the stdlib are properly annotated as a
72+
// "known program termination point", so we don't use
73+
// ApplySite::isCalleeKnownProgramTerminationPoint.
74+
return term->getModule().getOptions().EnableNoReturnCold;
75+
}
76+
}
77+
static bool hasColdTerminator(const SILBasicBlock *bb) {
78+
assert(bb);
79+
return isColdTerminator(bb->getTerminator());
80+
}
81+
3282
/// \return a BranchHint if this conditional's likely value can be inferred.
3383
ColdBlockInfo::BranchHint ColdBlockInfo::getBranchHint(SILValue Cond,
3484
int recursionDepth) {
@@ -135,6 +185,36 @@ static std::optional<bool> isColdProfiledEdge(const SILBasicBlock *FromBB,
135185
return takenProbability < WARM_EDGE_MINIMUM;
136186
}
137187

188+
void ColdBlockInfo::analyzeExits(SILFunction *Fn) {
189+
assert(Fn);
190+
LLVM_DEBUG(llvm::dbgs()
191+
<< "ColdBlockInfo: analyzeExits of " << Fn->getName() << "\n");
192+
193+
PostDominanceInfo *info = PDA->get(Fn);
194+
auto *node = info->getRootNode();
195+
assert(info->isVirtualRoot(node));
196+
197+
SmallVector<SILBasicBlock *, 8> scratch;
198+
for (auto exitNode : node->children()) {
199+
auto exitBB = exitNode->getBlock();
200+
201+
if (!hasColdTerminator(exitBB))
202+
continue;
203+
204+
ASSERT(exitBB->getNumSuccessors() == 0 && "crucial assumption must hold");
205+
206+
// Mark all blocks post-dominated by this cold-exit block as cold.
207+
info->getDescendants(exitBB, scratch);
208+
for (auto dominatedBB : scratch) {
209+
ColdBlockMap[dominatedBB] = true;
210+
LLVM_DEBUG(llvm::dbgs()
211+
<< "ColdBlockInfo(" << Fn->getName()
212+
<< "): analyzeExits says bb"
213+
<< dominatedBB->getDebugID() << " is cold\n");
214+
}
215+
}
216+
}
217+
138218
/// \return true if the CFG path FromBB->*->ToBB is either
139219
/// 1. directly gated by a _slowPath branch hint.
140220
/// 2. has a low probability of being taken according to profile information.
@@ -147,6 +227,8 @@ bool ColdBlockInfo::isSlowPath(const SILBasicBlock *FromBB,
147227
(void)DT;
148228
#endif
149229

230+
assert(!queryIsCold(ToBB) && "already known temperature?");
231+
150232
// Is this the edge FromBB->ToBB?
151233
bool isDirectEdge = false;
152234
for (auto *succ : FromBB->getSuccessorBlocks()) {
@@ -173,20 +255,28 @@ bool ColdBlockInfo::isSlowPath(const SILBasicBlock *FromBB,
173255
return ToBB == CBI->getTrueBB();
174256
}
175257
}
176-
}
177258

178-
// Should we give-up here?
179-
if (recursionDepth > RecursionDepthLimit)
180-
return false;
259+
} else if (recursionDepth <= RecursionDepthLimit) {
260+
// If ToBB only has cold predecessors, then the path to it is cold.
261+
bool coldPredecessors =
262+
llvm::all_of(ToBB->getPredecessorBlocks(), [&](auto *predBB) {
263+
return isCold(predBB, recursionDepth+1);
264+
});
265+
266+
if (coldPredecessors)
267+
return true;
181268

182-
for (auto predBB : ToBB->getPredecessorBlocks()) {
183-
if (!isCold(predBB, recursionDepth + 1))
184-
return false;
269+
// If FromBB only has cold successors, then the path from it is cold.
270+
bool coldSuccessors =
271+
llvm::all_of(FromBB->getSuccessorBlocks(), [&](auto *succBB) {
272+
return isCold(succBB, recursionDepth+1);
273+
});
274+
275+
if (coldSuccessors)
276+
return true;
185277
}
186278

187-
// All predecessors of ToBB are cold, so that implies all paths
188-
// FromBB->*->ToBB are "slow".
189-
return true;
279+
return false; // No reason to believe the path is slow.
190280
}
191281

192282
/// \return true if the given block is dominated by a _slowPath branch hint.
@@ -212,6 +302,23 @@ bool ColdBlockInfo::isCold(const SILBasicBlock *BB, int recursionDepth) {
212302
DomChain.push_back(BB);
213303
bool IsCold = false;
214304
Node = Node->getIDom();
305+
306+
// Special case: the entry block
307+
if (BB->isEntry()) {
308+
assert(!Node && "entry is dominated?");
309+
// If the entry block's successors are all cold, then it's cold.
310+
if (BB->getNumSuccessors() > 0) {
311+
IsCold = llvm::all_of(BB->getSuccessorBlocks(), [&](auto *succBB) {
312+
return isCold(succBB, recursionDepth);
313+
});
314+
} else {
315+
// Without any successors, we can't infer the entry block is cold, unless
316+
// if it exits in an unusual way.
317+
IsCold = hasColdTerminator(BB);
318+
}
319+
}
320+
321+
// Check the blocks dominating BB to see if they're cold.
215322
while (Node) {
216323
auto fromBB = Node->getBlock();
217324
auto toBB = DomChain.back();
@@ -220,10 +327,9 @@ bool ColdBlockInfo::isCold(const SILBasicBlock *BB, int recursionDepth) {
220327
break;
221328
}
222329

223-
// If a dominator of BB is cold, then BB is cold.
224-
auto I = ColdBlockMap.find(fromBB);
225-
if (I != ColdBlockMap.end()) {
226-
IsCold = I->second;
330+
// If a dominator of BB has a temperature, propagate it.
331+
if (auto temp = queryIsCold(fromBB)) {
332+
IsCold = *temp;
227333
break;
228334
}
229335

@@ -234,12 +340,10 @@ bool ColdBlockInfo::isCold(const SILBasicBlock *BB, int recursionDepth) {
234340

235341
for (auto *ChainBB : DomChain) {
236342
ColdBlockMap[ChainBB] = IsCold;
237-
238-
if (IsCold) {
239-
LLVM_DEBUG(llvm::dbgs()
240-
<< "ColdBlockInfo(" << BB->getParent()->getName() << "): "
241-
<< "bb" << ChainBB->getDebugID() << " is cold\n");
242-
}
343+
LLVM_DEBUG(llvm::dbgs()
344+
<< "ColdBlockInfo(" << BB->getParent()->getName() << "): "
345+
<< "bb" << ChainBB->getDebugID()
346+
<< " is " << (IsCold ? "cold\n" : "warm\n"));
243347
}
244348

245349
return IsCold;

lib/SILOptimizer/IPO/CapturePropagation.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ bool CapturePropagation::optimizePartialApply(PartialApplyInst *PAI) {
590590

591591
void CapturePropagation::run() {
592592
DominanceAnalysis *DA = PM->getAnalysis<DominanceAnalysis>();
593+
PostDominanceAnalysis *PDA = PM->getAnalysis<PostDominanceAnalysis>();
593594
auto *F = getFunction();
594595
bool HasChanged = false;
595596

@@ -598,7 +599,8 @@ void CapturePropagation::run() {
598599
return;
599600

600601
// Cache cold blocks per function.
601-
ColdBlockInfo ColdBlocks(DA);
602+
ColdBlockInfo ColdBlocks(DA, PDA);
603+
ColdBlocks.analyzeExits(F);
602604
for (auto &BB : *F) {
603605
if (ColdBlocks.isCold(&BB))
604606
continue;

lib/SILOptimizer/LoopTransforms/COWArrayOpt.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,13 @@ class COWArrayOpt {
154154
// analyzing.
155155
SILValue CurrentArrayAddr;
156156
public:
157-
COWArrayOpt(RCIdentityFunctionInfo *RCIA, SILLoop *L, DominanceAnalysis *DA)
157+
COWArrayOpt(RCIdentityFunctionInfo *RCIA, SILLoop *L, DominanceAnalysis *DA,
158+
PostDominanceAnalysis *PDA)
158159
: RCIA(RCIA), Function(L->getHeader()->getParent()), Loop(L),
159160
Preheader(L->getLoopPreheader()), DomTree(DA->get(Function)),
160-
ColdBlocks(DA), CachedSafeLoop(false, false), ReachingBlocks(Function) {}
161+
ColdBlocks(DA, PDA), CachedSafeLoop(false, false), ReachingBlocks(Function) {
162+
ColdBlocks.analyzeExits(Function);
163+
}
161164

162165
bool run();
163166

@@ -1063,6 +1066,7 @@ class COWArrayOptPass : public SILFunctionTransform {
10631066
<< getFunction()->getName() << "\n");
10641067

10651068
auto *DA = PM->getAnalysis<DominanceAnalysis>();
1069+
auto *PDA = PM->getAnalysis<PostDominanceAnalysis>();
10661070
auto *LA = PM->getAnalysis<SILLoopAnalysis>();
10671071
auto *RCIA =
10681072
PM->getAnalysis<RCIdentityAnalysis>()->get(getFunction());
@@ -1084,7 +1088,7 @@ class COWArrayOptPass : public SILFunctionTransform {
10841088

10851089
bool HasChanged = false;
10861090
for (auto *L : Loops)
1087-
HasChanged |= COWArrayOpt(RCIA, L, DA).run();
1091+
HasChanged |= COWArrayOpt(RCIA, L, DA, PDA).run();
10881092

10891093
if (HasChanged)
10901094
invalidateAnalysis(SILAnalysis::InvalidationKind::CallsAndInstructions);

0 commit comments

Comments
 (0)