Skip to content

Commit 3fdab98

Browse files
committed
[OSSALifetimeCompletion] Handle available boundary
Not every block in a region which begins with the non-lifetime-ending boundary of a value and ending with unreachable-terminated blocks has the value available. If the unreachable-terminated blocks in this boundary are not available, it is incorrect to insert destroys of the value in them: it is an overconsume on some paths. Previously, however, destroys were simply being inserted at the unreachable. Here, this is fixed by finding the boundary of availability within that region and inserting destroys before the terminators of the blocks on that boundary. rdar://116255254
1 parent 61dbf7e commit 3fdab98

File tree

4 files changed

+243
-16
lines changed

4 files changed

+243
-16
lines changed

include/swift/SIL/OSSALifetimeCompletion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class OSSALifetimeCompletion {
8787

8888
static void visitUnreachableLifetimeEnds(
8989
SILValue value, const SSAPrunedLiveness &liveness,
90-
llvm::function_ref<void(UnreachableInst *)> visit);
90+
llvm::function_ref<void(SILInstruction *)> visit);
9191

9292
protected:
9393
bool analyzeAndUpdateLifetime(SILValue value, bool forceBoundaryCompletion);

lib/SIL/Utils/OSSALifetimeCompletion.cpp

Lines changed: 189 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@
5151

5252
#include "swift/SIL/OSSALifetimeCompletion.h"
5353
#include "swift/SIL/SILBuilder.h"
54+
#include "swift/SIL/SILFunction.h"
5455
#include "swift/SIL/SILInstruction.h"
5556
#include "swift/SIL/Test.h"
57+
#include "llvm/ADT/STLExtras.h"
5658

5759
using namespace swift;
5860

@@ -99,40 +101,214 @@ static bool endLifetimeAtBoundary(SILValue value,
99101
return changed;
100102
}
101103

102-
void OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
103-
SILValue value, const SSAPrunedLiveness &liveness,
104-
llvm::function_ref<void(UnreachableInst *)> visit) {
104+
namespace {
105+
/// Implements OSSALifetimeCompletion::visitUnreachableLifetimeEnds. Finds
106+
/// positions as near as possible to unreachables at which `value`'s lifetime
107+
/// ends.
108+
///
109+
/// Finding these positions is a three step process:
110+
/// 1) computeRegion: Forward CFG walk from non-lifetime-ending boundary to find
111+
/// the dead-end region in which the value might be available.
112+
/// 2) propagateAvailability: Iterative dataflow within the region to determine
113+
/// which blocks are available.
114+
/// 3) visitAvailabilityBoundary: Visits the final blocks in the region where
115+
/// the value is available--these are the blocks
116+
/// without successors or with at least one
117+
/// unavailable successor.
118+
class VisitUnreachableLifetimeEnds {
119+
/// The `value` whose unreachable lifetime ends are to be visited.
120+
SILValue value;
121+
122+
/// The non-lifetime-ending boundary of `value`.
123+
BasicBlockSet starts;
124+
/// The region between (inclusive) the `starts` and the unreachable blocks.
125+
BasicBlockSetVector region;
126+
127+
public:
128+
VisitUnreachableLifetimeEnds(SILValue value)
129+
: value(value), starts(value->getFunction()),
130+
region(value->getFunction()) {}
131+
132+
/// Region discovery.
133+
///
134+
/// Forward CFG walk from non-lifetime-ending boundary to unreachable
135+
/// instructions.
136+
void computeRegion(const SSAPrunedLiveness &liveness);
137+
138+
struct Result;
139+
140+
/// Iterative dataflow to determine availability for each block in `region`.
141+
void propagateAvailablity(Result &result);
142+
143+
/// Visit the terminators of blocks on the boundary of availability.
144+
void
145+
visitAvailabilityBoundary(Result const &result,
146+
llvm::function_ref<void(SILInstruction *)> visit);
147+
148+
struct State {
149+
enum class Value : uint8_t {
150+
Unavailable = 0,
151+
Available,
152+
Unknown,
153+
};
154+
Value value;
155+
156+
State(Value value) : value(value){};
157+
operator Value() const { return value; }
158+
State meet(State const other) const {
159+
return *this < other ? *this : other;
160+
}
161+
162+
static State Unavailable() { return {Value::Unavailable}; }
163+
static State Available() { return {Value::Available}; }
164+
static State Unknown() { return {Value::Unknown}; }
165+
};
166+
167+
struct Result {
168+
BasicBlockBitfield states;
169+
170+
Result(SILFunction *function) : states(function, 2) {}
171+
172+
State getState(SILBasicBlock *block) const {
173+
return {(State::Value)states.get(block)};
174+
}
175+
176+
void setState(SILBasicBlock *block, State newState) {
177+
states.set(block, (unsigned)newState.value);
178+
}
179+
180+
bool meetInPlaceOverPredecessors(SILBasicBlock *block) {
181+
auto oldState = getState(block);
182+
auto state = oldState;
183+
for (auto *predecessor : block->getPredecessorBlocks()) {
184+
state = state.meet(getState(predecessor));
185+
}
186+
setState(block, state);
187+
return state != oldState;
188+
}
189+
};
190+
};
191+
192+
void VisitUnreachableLifetimeEnds::computeRegion(
193+
const SSAPrunedLiveness &liveness) {
194+
// Find the non-lifetime-ending boundary of `value`.
105195
PrunedLivenessBoundary boundary;
106196
liveness.computeBoundary(boundary);
107197

108-
BasicBlockWorklist deadEndBlocks(value->getFunction());
109198
for (SILInstruction *lastUser : boundary.lastUsers) {
110199
if (liveness.isInterestingUser(lastUser)
111200
!= PrunedLiveness::LifetimeEndingUse) {
112-
deadEndBlocks.push(lastUser->getParent());
201+
region.insert(lastUser->getParent());
202+
starts.insert(lastUser->getParent());
113203
}
114204
}
115205
for (SILBasicBlock *edge : boundary.boundaryEdges) {
116-
deadEndBlocks.push(edge);
206+
region.insert(edge);
207+
starts.insert(edge);
117208
}
118209
for (SILNode *deadDef : boundary.deadDefs) {
119-
deadEndBlocks.push(deadDef->getParentBlock());
210+
region.insert(deadDef->getParentBlock());
211+
starts.insert(deadDef->getParentBlock());
120212
}
121-
// Forward CFG walk from the non-lifetime-ending boundary to the unreachable
122-
// instructions.
123-
while (auto *block = deadEndBlocks.pop()) {
213+
214+
// Forward walk to find the region in which `value` might be available.
215+
BasicBlockWorklist regionWorklist(value->getFunction());
216+
// Start the forward walk from the non-lifetime-ending boundary.
217+
for (auto *start : region) {
218+
regionWorklist.push(start);
219+
}
220+
while (auto *block = regionWorklist.pop()) {
124221
if (block->succ_empty()) {
125222
// This assert will fail unless there are already lifetime-ending
126223
// instruction on all paths to normal function exits.
127-
auto *unreachable = cast<UnreachableInst>(block->getTerminator());
128-
visit(unreachable);
224+
assert(isa<UnreachableInst>(block->getTerminator()));
225+
}
226+
for (auto *successor : block->getSuccessorBlocks()) {
227+
regionWorklist.pushIfNotVisited(successor);
228+
region.insert(successor);
129229
}
230+
}
231+
}
232+
233+
void VisitUnreachableLifetimeEnds::propagateAvailablity(Result &result) {
234+
// Initialize per-block state.
235+
// - all blocks outside of the region are ::Unavailable (automatically
236+
// initialized)
237+
// - non-initial in-region blocks are Unknown
238+
// - start blocks are ::Available
239+
for (auto *block : region) {
240+
if (starts.contains(block))
241+
result.setState(block, State::Available());
242+
else
243+
result.setState(block, State::Unknown());
244+
}
245+
246+
BasicBlockWorklist worklist(value->getFunction());
247+
248+
// Initialize worklist with all participating blocks.
249+
//
250+
// Only perform dataflow in the non-initial region. Every initial block is
251+
// by definition ::Available.
252+
for (auto *block : region) {
253+
if (starts.contains(block))
254+
continue;
255+
worklist.push(block);
256+
}
257+
258+
// Iterate over blocks which are successors of blocks whose state changed.
259+
while (auto *block = worklist.popAndForget()) {
260+
// Only propagate availability in non-initial, in-region blocks.
261+
if (!region.contains(block) || starts.contains(block))
262+
continue;
263+
auto changed = result.meetInPlaceOverPredecessors(block);
264+
if (!changed) {
265+
continue;
266+
}
267+
// The state has changed. Propagate the new state into successors.
130268
for (auto *successor : block->getSuccessorBlocks()) {
131-
deadEndBlocks.pushIfNotVisited(successor);
269+
worklist.pushIfNotVisited(successor);
132270
}
133271
}
134272
}
135273

274+
void VisitUnreachableLifetimeEnds::visitAvailabilityBoundary(
275+
Result const &result, llvm::function_ref<void(SILInstruction *)> visit) {
276+
for (auto *block : region) {
277+
auto available = result.getState(block) == State::Available();
278+
if (!available) {
279+
continue;
280+
}
281+
auto hasUnreachableSuccessor = [&]() {
282+
// Use a lambda to avoid checking if possible.
283+
return llvm::any_of(block->getSuccessorBlocks(), [&result](auto *block) {
284+
return result.getState(block) == State::Unavailable();
285+
});
286+
};
287+
if (!block->succ_empty() && !hasUnreachableSuccessor()) {
288+
continue;
289+
}
290+
assert(hasUnreachableSuccessor() ||
291+
isa<UnreachableInst>(block->getTerminator()));
292+
visit(block->getTerminator());
293+
}
294+
}
295+
} // end anonymous namespace
296+
297+
void OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
298+
SILValue value, const SSAPrunedLiveness &liveness,
299+
llvm::function_ref<void(SILInstruction *)> visit) {
300+
301+
VisitUnreachableLifetimeEnds visitor(value);
302+
303+
visitor.computeRegion(liveness);
304+
305+
VisitUnreachableLifetimeEnds::Result result(value->getFunction());
306+
307+
visitor.propagateAvailablity(result);
308+
309+
visitor.visitAvailabilityBoundary(result, visit);
310+
}
311+
136312
static bool endLifetimeAtUnreachableBlocks(SILValue value,
137313
const SSAPrunedLiveness &liveness) {
138314
bool changed = false;

test/SILOptimizer/mem2reg_lifetime.sil

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,8 +1858,7 @@ right:
18581858
// CHECK: {{bb[^,]+}}([[INSTANCE:%[^,]+]] : @owned $C):
18591859
// CHECK: cond_br undef, [[BB1:bb[0-9]+]], [[BB4:bb[0-9]+]]
18601860
// CHECK: [[BB1]]:
1861-
// CHECK: [[COPY:%[^,]+]] = copy_value [[INSTANCE]]
1862-
// CHECK: [[LIFETIME_OWNED:%[^,]+]] = move_value [lexical] [[COPY]]
1861+
// CHECK: [[LIFETIME_OWNED:%[^,]+]] = move_value [lexical] [[INSTANCE]]
18631862
// CHECK: cond_br undef, [[BB2:bb[0-9]+]], [[BB3:bb[0-9]+]]
18641863
// CHECK: [[BB2]]:
18651864
// CHECK: destroy_value [[LIFETIME_OWNED]]

test/SILOptimizer/ossa_lifetime_completion.sil

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,55 @@ bb0(%0 : @guaranteed $C):
212212
return %12 : $()
213213
}
214214

215+
// Insert destroy on availability boundary of `value` within the region after
216+
// the non-lifetime-ending boundary of `value`. Namely, in to_die_11.
217+
// CHECK-LABEL: sil [ossa] @availability_boundary_1 : {{.*}} {
218+
// CHECK: [[VALUE:%[^,]+]] = move_value [lexical]
219+
// CHECK: br [[CONDITION_1:bb[0-9]+]]
220+
// CHECK: [[CONDITION_1]]:
221+
// CHECK: cond_br undef, [[CONDITION_2:bb[0-9]+]], [[TO_DIE_1:bb[0-9]+]]
222+
// CHECK: [[CONDITION_2]]:
223+
// CHECK: destroy_value [[VALUE]] : $C
224+
// CHECK: cond_br undef, [[EXIT:bb[0-9]+]], [[TO_DIE_2:bb[0-9]+]]
225+
// CHECK: [[TO_DIE_1]]:
226+
// CHECK: br [[TO_DIE_11:bb[0-9]+]]
227+
// CHECK: [[TO_DIE_11]]:
228+
// CHECK: destroy_value [[VALUE]] : $C
229+
// CHECK: br [[DIE:bb[0-9]+]]
230+
// CHECK: [[TO_DIE_2]]:
231+
// CHECK: br [[DIE]]
232+
// CHECK: [[DIE]]:
233+
// CHECK: unreachable
234+
// CHECK: [[EXIT]]:
235+
// CHECK-LABEL: } // end sil function 'availability_boundary_1'
236+
sil [ossa] @availability_boundary_1 : $@convention(thin) () -> () {
237+
entry:
238+
%value = apply undef() : $@convention(thin) () -> @owned C
239+
%lexical = move_value [lexical] %value : $C // required (for lexicality)
240+
specify_test "ossa-lifetime-completion %lexical"
241+
br condition_1
242+
243+
condition_1:
244+
cond_br undef, condition_2, to_die_1
245+
246+
condition_2:
247+
destroy_value %lexical : $C
248+
cond_br undef, exit, to_die_2
249+
250+
to_die_1:
251+
br to_die_11
252+
253+
to_die_11:
254+
// End lifetime here.
255+
br die
256+
257+
to_die_2:
258+
br die
259+
260+
die:
261+
unreachable
262+
263+
exit:
264+
%retval = tuple ()
265+
return %retval : $()
266+
}

0 commit comments

Comments
 (0)