Skip to content

Commit 1f3f830

Browse files
committed
[SILOptimizer]: slow OSSA lifetime canonicalization mitigation
OSSA lifetime canonicalization can take a very long time in certain cases in which there are large basic blocks. to mitigate this, add logic to skip walking the liveness boundary for extending liveness to dead ends when there aren't any dead ends in the function. Updates `DeadEndBlocks` with a new `isEmpty` method and cache to determine if there are any dead-end blocks in a given function.
1 parent f91b4b0 commit 1f3f830

File tree

5 files changed

+90
-1
lines changed

5 files changed

+90
-1
lines changed

include/swift/SIL/BasicBlockUtils.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ class DeadEndBlocks {
6868
const SILFunction *f;
6969
bool didComputeValue = false;
7070

71+
/// When non-null, indicates whether dead-end blocks are present
72+
/// in the current function.
73+
std::optional<bool> hasAnyDeadEnds = std::nullopt;
74+
7175
void compute();
7276

7377
public:
@@ -85,6 +89,17 @@ class DeadEndBlocks {
8589
return reachableBlocks.count(block) == 0;
8690
}
8791

92+
/// Returns true iff none of the function's blocks is a dead-end.
93+
/// Note: The underlying value is lazily computed & cached.
94+
bool isEmpty() {
95+
if (!hasAnyDeadEnds.has_value()) {
96+
hasAnyDeadEnds = llvm::any_of(
97+
*f, [this](const SILBasicBlock &BB) { return isDeadEnd(&BB); });
98+
}
99+
100+
return !hasAnyDeadEnds.value();
101+
}
102+
88103
/// Return true if this dead end blocks has computed its internal cache yet.
89104
///
90105
/// Used to determine if we need to verify a DeadEndBlocks.

include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,10 @@ class CanonicalizeOSSALifetime final {
475475
return !endingLifetimeAtExplicitEnds();
476476
}
477477

478+
bool hasAnyDeadEnds() const {
479+
return !deadEndBlocksAnalysis->get(function)->isEmpty();
480+
}
481+
478482
bool respectsDeinitBarriers() const {
479483
if (!currentDef->isLexical())
480484
return false;

lib/SIL/Utils/BasicBlockUtils.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,16 @@ static FunctionTest DeadEndBlocksTest("dead_end_blocks", [](auto &function,
446446
}
447447
#endif
448448
});
449+
450+
// Arguments:
451+
// - none
452+
// Dumps:
453+
// - message
454+
static FunctionTest HasAnyDeadEndBlocksTest(
455+
"has_any_dead_ends", [](auto &function, auto &arguments, auto &test) {
456+
auto deb = test.getDeadEndBlocks();
457+
llvm::outs() << (deb->isEmpty() ? "no dead ends\n" : "has dead ends\n");
458+
});
449459
} // end namespace swift::test
450460

451461
//===----------------------------------------------------------------------===//

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@ bool CanonicalizeOSSALifetime::computeLiveness() {
14061406
clear();
14071407
return false;
14081408
}
1409-
if (respectsDeadEnds()) {
1409+
if (respectsDeadEnds() && hasAnyDeadEnds()) {
14101410
if (respectsDeinitBarriers()) {
14111411
extendLexicalLivenessToDeadEnds();
14121412
}

test/SILOptimizer/dead_end_blocks.sil

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,64 @@ exit:
4747
return %retval : $()
4848
}
4949

50+
// no dead ends - simple return
51+
// CHECK-LABEL: begin running test {{.*}} on simple_function: has_any_dead_ends
52+
// CHECK: no dead ends
53+
// CHECK-LABEL: end running test {{.*}} on simple_function: has_any_dead_ends
54+
sil @simple_function : $@convention(thin) () -> () {
55+
entry:
56+
specify_test "has_any_dead_ends"
57+
%retval = tuple ()
58+
return %retval : $()
59+
}
60+
61+
// dead ends - unreachable blocks
62+
// CHECK-LABEL: begin running test {{.*}} on function_with_dead_ends: has_any_dead_ends
63+
// CHECK: has dead ends
64+
// CHECK-LABEL: end running test {{.*}} on function_with_dead_ends: has_any_dead_ends
65+
sil @function_with_dead_ends : $@convention(thin) () -> () {
66+
entry:
67+
specify_test "has_any_dead_ends"
68+
cond_br undef, die, exit
69+
70+
die:
71+
unreachable
72+
73+
exit:
74+
%retval = tuple ()
75+
return %retval : $()
76+
}
5077

78+
// dead ends – infinite loop
79+
// CHECK-LABEL: begin running test {{.*}} on function_with_loop: has_any_dead_ends
80+
// CHECK: has dead ends
81+
// CHECK-LABEL: end running test {{.*}} on function_with_loop: has_any_dead_ends
82+
sil @function_with_loop : $@convention(thin) () -> () {
83+
entry:
84+
specify_test "has_any_dead_ends"
85+
cond_br undef, exit, loop
86+
87+
loop:
88+
br loop
89+
90+
exit:
91+
%retval = tuple ()
92+
return %retval : $()
93+
}
94+
95+
// no dead ends – conditional branches but all paths return
96+
// CHECK-LABEL: begin running test {{.*}} on branching_no_dead_ends: has_any_dead_ends
97+
// CHECK: no dead ends
98+
// CHECK-LABEL: end running test {{.*}} on branching_no_dead_ends: has_any_dead_ends
99+
sil @branching_no_dead_ends : $@convention(thin) () -> () {
100+
entry:
101+
specify_test "has_any_dead_ends"
102+
cond_br undef, then, else
103+
104+
then:
105+
br else
106+
107+
else:
108+
%retval2 = tuple ()
109+
return %retval2 : $()
110+
}

0 commit comments

Comments
 (0)