Skip to content

Commit d5aafbc

Browse files
Merge pull request #73368 from nate-chandler/lifetime-completion/20240501/1
[NFC] LifetimeCompletion: Clarified API and documentation.
2 parents b702759 + 2ca9951 commit d5aafbc

File tree

5 files changed

+105
-54
lines changed

5 files changed

+105
-54
lines changed

include/swift/SIL/OSSALifetimeCompletion.h

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,54 @@ class OSSALifetimeCompletion {
5050
OSSALifetimeCompletion(SILFunction *function, const DominanceInfo *domInfo)
5151
: domInfo(domInfo), completedValues(function) {}
5252

53+
// The kind of boundary at which to complete the lifetime.
54+
//
55+
// Liveness: "As early as possible." Consume the value after the last
56+
// non-consuming uses.
57+
// Availability: "As late as possible." Consume the value in the last blocks
58+
// beyond the non-consuming uses in which the value has been
59+
// consumed on no incoming paths.
60+
struct Boundary {
61+
enum Value : uint8_t {
62+
Liveness,
63+
Availability,
64+
};
65+
Value value;
66+
67+
Boundary(Value value) : value(value){};
68+
operator Value() const { return value; }
69+
70+
static std::optional<Boundary> getForcingLiveness(bool force) {
71+
if (!force)
72+
return {};
73+
return {Liveness};
74+
}
75+
76+
bool isLiveness() { return value == Liveness; }
77+
bool isAvailable() { return !isLiveness(); }
78+
};
79+
5380
/// Insert a lifetime-ending instruction on every path to complete the OSSA
54-
/// lifetime of \p value. Lifetime completion is only relevant for owned
81+
/// lifetime of \p value along \p boundary.
82+
///
83+
/// If \p boundary is not specified, the following boundary will be used:
84+
/// \p value is lexical -> Boundary::Availability
85+
/// \p value is non-lexical -> Boundary::Liveness
86+
///
87+
/// Lifetime completion is only relevant for owned
5588
/// values or borrow introducers.
56-
/// For lexical values lifetime is completed at unreachable instructions.
57-
/// For non-lexical values lifetime is completed at the lifetime boundary.
58-
/// When \p forceBoundaryCompletion is true, the client is able to guarantee
59-
/// that lifetime completion of lexical values at the lifetime boundary is
60-
/// sufficient.
61-
/// Currently \p forceBoundaryCompletion is used by mem2reg and temprvalueopt
62-
/// to complete lexical enum values on trivial paths.
89+
///
90+
/// Currently \p boundary == {Boundary::Availability} is used by Mem2Reg and
91+
/// TempRValueOpt and PredicatbleMemOpt to complete lexical enum values on
92+
/// trivial paths.
93+
///
6394
/// Returns true if any new instructions were created to complete the
6495
/// lifetime.
6596
///
6697
/// TODO: We also need to complete scoped addresses (e.g. store_borrow)!
6798
LifetimeCompletion
68-
completeOSSALifetime(SILValue value, bool forceBoundaryCompletion = false) {
99+
completeOSSALifetime(SILValue value,
100+
std::optional<Boundary> maybeBoundary = std::nullopt) {
69101
if (value->getOwnershipKind() == OwnershipKind::None)
70102
return LifetimeCompletion::NoLifetime;
71103

@@ -80,7 +112,10 @@ class OSSALifetimeCompletion {
80112
if (!completedValues.insert(value))
81113
return LifetimeCompletion::AlreadyComplete;
82114

83-
return analyzeAndUpdateLifetime(value, forceBoundaryCompletion)
115+
Boundary boundary = maybeBoundary.value_or(
116+
value->isLexical() ? Boundary::Availability : Boundary::Liveness);
117+
118+
return analyzeAndUpdateLifetime(value, boundary)
84119
? LifetimeCompletion::WasCompleted
85120
: LifetimeCompletion::AlreadyComplete;
86121
}
@@ -90,7 +125,7 @@ class OSSALifetimeCompletion {
90125
llvm::function_ref<void(SILInstruction *)> visit);
91126

92127
protected:
93-
bool analyzeAndUpdateLifetime(SILValue value, bool forceBoundaryCompletion);
128+
bool analyzeAndUpdateLifetime(SILValue value, Boundary boundary);
94129
};
95130

96131
//===----------------------------------------------------------------------===//

lib/SIL/Utils/OSSALifetimeCompletion.cpp

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ static SILInstruction *endOSSALifetime(SILValue value, SILBuilder &builder) {
7070
return builder.createEndBorrow(loc, value);
7171
}
7272

73-
static bool endLifetimeAtBoundary(SILValue value,
74-
const SSAPrunedLiveness &liveness) {
73+
static bool endLifetimeAtLivenessBoundary(SILValue value,
74+
const SSAPrunedLiveness &liveness) {
7575
PrunedLivenessBoundary boundary;
7676
liveness.computeBoundary(boundary);
7777

@@ -149,7 +149,7 @@ class VisitUnreachableLifetimeEnds {
149149
llvm::function_ref<void(SILInstruction *)> visit);
150150

151151
struct State {
152-
enum class Value : uint8_t {
152+
enum Value : uint8_t {
153153
Unavailable = 0,
154154
Available,
155155
Unknown,
@@ -161,10 +161,6 @@ class VisitUnreachableLifetimeEnds {
161161
State meet(State const other) const {
162162
return *this < other ? *this : other;
163163
}
164-
165-
static State Unavailable() { return {Value::Unavailable}; }
166-
static State Available() { return {Value::Available}; }
167-
static State Unknown() { return {Value::Unknown}; }
168164
};
169165

170166
struct Result {
@@ -197,36 +193,47 @@ class VisitUnreachableLifetimeEnds {
197193

198194
void VisitUnreachableLifetimeEnds::computeRegion(
199195
const SSAPrunedLiveness &liveness) {
200-
// Find the non-lifetime-ending boundary of `value`.
196+
// (1) Compute the complete liveness boundary.
201197
PrunedLivenessBoundary boundary;
202198
liveness.computeBoundary(boundary);
203199

200+
// Used in the forward walk below (3).
201+
BasicBlockWorklist regionWorklist(value->getFunction());
202+
203+
// (2) Collect the non-lifetime-ending liveness boundary. This is the
204+
// portion of `boundary` consisting of:
205+
// - non-lifetime-ending instructions (their parent blocks)
206+
// - boundary edges
207+
// - dead defs (their parent blocks)
208+
auto collect = [&](SILBasicBlock *block) {
209+
// `region` consists of the non-lifetime-ending boundary and all its
210+
// iterative successors.
211+
region.insert(block);
212+
// `starts` just consists of the blocks in the non-lifetime-ending
213+
// boundary.
214+
starts.insert(block);
215+
// The forward walk begins from the non-lifetime-ending boundary.
216+
regionWorklist.push(block);
217+
};
218+
204219
for (SILInstruction *lastUser : boundary.lastUsers) {
205220
if (liveness.isInterestingUser(lastUser)
206221
!= PrunedLiveness::LifetimeEndingUse) {
207-
region.insert(lastUser->getParent());
208-
starts.insert(lastUser->getParent());
222+
collect(lastUser->getParent());
209223
}
210224
}
211225
for (SILBasicBlock *edge : boundary.boundaryEdges) {
212-
region.insert(edge);
213-
starts.insert(edge);
226+
collect(edge);
214227
}
215228
for (SILNode *deadDef : boundary.deadDefs) {
216-
region.insert(deadDef->getParentBlock());
217-
starts.insert(deadDef->getParentBlock());
229+
collect(deadDef->getParentBlock());
218230
}
219231

220-
// Forward walk to find the region in which `value` might be available.
221-
BasicBlockWorklist regionWorklist(value->getFunction());
222-
// Start the forward walk from the non-lifetime-ending boundary.
223-
for (auto *start : region) {
224-
regionWorklist.push(start);
225-
}
232+
// (3) Forward walk to find the region in which `value` might be available.
226233
while (auto *block = regionWorklist.pop()) {
227234
if (block->succ_empty()) {
228-
// This assert will fail unless there are already lifetime-ending
229-
// instruction on all paths to normal function exits.
235+
// This assert will fail unless there is already a lifetime-ending
236+
// instruction on each path to normal function exits.
230237
assert(isa<UnreachableInst>(block->getTerminator()));
231238
}
232239
for (auto *successor : block->getSuccessorBlocks()) {
@@ -244,9 +251,9 @@ void VisitUnreachableLifetimeEnds::propagateAvailablity(Result &result) {
244251
// - start blocks are ::Available
245252
for (auto *block : region) {
246253
if (starts.contains(block))
247-
result.setState(block, State::Available());
254+
result.setState(block, State::Available);
248255
else
249-
result.setState(block, State::Unknown());
256+
result.setState(block, State::Unknown);
250257
}
251258

252259
BasicBlockWorklist worklist(value->getFunction());
@@ -280,14 +287,14 @@ void VisitUnreachableLifetimeEnds::propagateAvailablity(Result &result) {
280287
void VisitUnreachableLifetimeEnds::visitAvailabilityBoundary(
281288
Result const &result, llvm::function_ref<void(SILInstruction *)> visit) {
282289
for (auto *block : region) {
283-
auto available = result.getState(block) == State::Available();
290+
auto available = result.getState(block) == State::Available;
284291
if (!available) {
285292
continue;
286293
}
287294
auto hasUnreachableSuccessor = [&]() {
288295
// Use a lambda to avoid checking if possible.
289296
return llvm::any_of(block->getSuccessorBlocks(), [&result](auto *block) {
290-
return result.getState(block) == State::Unavailable();
297+
return result.getState(block) == State::Unavailable;
291298
});
292299
};
293300
if (!block->succ_empty() && !hasUnreachableSuccessor()) {
@@ -315,8 +322,9 @@ void OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
315322
visitor.visitAvailabilityBoundary(result, visit);
316323
}
317324

318-
static bool endLifetimeAtUnreachableBlocks(SILValue value,
319-
const SSAPrunedLiveness &liveness) {
325+
static bool
326+
endLifetimeAtAvailabilityBoundary(SILValue value,
327+
const SSAPrunedLiveness &liveness) {
320328
bool changed = false;
321329
OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
322330
value, liveness, [&](auto *unreachable) {
@@ -330,12 +338,8 @@ static bool endLifetimeAtUnreachableBlocks(SILValue value,
330338
/// End the lifetime of \p value at unreachable instructions.
331339
///
332340
/// Returns true if any new instructions were created to complete the lifetime.
333-
///
334-
/// This is only meant to cleanup lifetimes that lead to dead-end blocks. After
335-
/// recursively completing all nested scopes, it then simply ends the lifetime
336-
/// at the Unreachable instruction.
337-
bool OSSALifetimeCompletion::analyzeAndUpdateLifetime(
338-
SILValue value, bool forceBoundaryCompletion) {
341+
bool OSSALifetimeCompletion::analyzeAndUpdateLifetime(SILValue value,
342+
Boundary boundary) {
339343
// Called for inner borrows, inner adjacent reborrows, inner reborrows, and
340344
// scoped addresses.
341345
auto handleInnerScope = [this](SILValue innerBorrowedValue) {
@@ -345,10 +349,13 @@ bool OSSALifetimeCompletion::analyzeAndUpdateLifetime(
345349
liveness.compute(domInfo, handleInnerScope);
346350

347351
bool changed = false;
348-
if (value->isLexical() && !forceBoundaryCompletion) {
349-
changed |= endLifetimeAtUnreachableBlocks(value, liveness.getLiveness());
350-
} else {
351-
changed |= endLifetimeAtBoundary(value, liveness.getLiveness());
352+
switch (boundary) {
353+
case Boundary::Availability:
354+
changed |= endLifetimeAtAvailabilityBoundary(value, liveness.getLiveness());
355+
break;
356+
case Boundary::Liveness:
357+
changed |= endLifetimeAtLivenessBoundary(value, liveness.getLiveness());
358+
break;
352359
}
353360
// TODO: Rebuild outer adjacent phis on demand (SILGen does not currently
354361
// produce guaranteed phis). See FindEnclosingDefs &
@@ -367,9 +374,15 @@ static FunctionTest OSSALifetimeCompletionTest(
367374
"ossa-lifetime-completion",
368375
[](auto &function, auto &arguments, auto &test) {
369376
SILValue value = arguments.takeValue();
377+
std::optional<OSSALifetimeCompletion::Boundary> kind = std::nullopt;
378+
if (arguments.hasUntaken()) {
379+
kind = arguments.takeBool()
380+
? OSSALifetimeCompletion::Boundary::Liveness
381+
: OSSALifetimeCompletion::Boundary::Availability;
382+
}
370383
llvm::outs() << "OSSA lifetime completion: " << value;
371384
OSSALifetimeCompletion completion(&function, /*domInfo*/ nullptr);
372-
completion.completeOSSALifetime(value);
385+
completion.completeOSSALifetime(value, kind);
373386
function.print(llvm::outs());
374387
});
375388
} // end namespace swift::test

lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,10 +2673,11 @@ bool AllocOptimize::tryToRemoveDeadAllocation() {
26732673
// Lexical enums can have incomplete lifetimes in non payload paths that
26742674
// don't end in unreachable. Force their lifetime to end immediately after
26752675
// the last use instead.
2676-
bool forceBoundaryCompletion = v->getType().isOrHasEnum();
2676+
auto boundary = OSSALifetimeCompletion::Boundary::getForcingLiveness(
2677+
v->getType().isOrHasEnum());
26772678
LLVM_DEBUG(llvm::dbgs() << "Completing lifetime of: ");
26782679
LLVM_DEBUG(v->dump());
2679-
completion.completeOSSALifetime(v, forceBoundaryCompletion);
2680+
completion.completeOSSALifetime(v, boundary);
26802681
}
26812682

26822683
return true;

lib/SILOptimizer/Transforms/SILMem2Reg.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1754,7 +1754,8 @@ void StackAllocationPromoter::run(BasicBlockSetVector &livePhiBlocks) {
17541754
for (auto it : valuesToComplete) {
17551755
// Set forceBoundaryCompletion as true so that we complete at boundary for
17561756
// lexical values as well.
1757-
completion.completeOSSALifetime(it, /* forceBoundaryCompletion */ true);
1757+
completion.completeOSSALifetime(it,
1758+
OSSALifetimeCompletion::Boundary::Liveness);
17581759
}
17591760
}
17601761

lib/SILOptimizer/Transforms/TempRValueElimination.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,8 @@ void TempRValueOptPass::run() {
964964
// Call the utlity to complete ossa lifetime.
965965
OSSALifetimeCompletion completion(function, da->get(function));
966966
for (auto it : valuesToComplete) {
967-
completion.completeOSSALifetime(it, /* forceBoundaryCompletion */ true);
967+
completion.completeOSSALifetime(it,
968+
OSSALifetimeCompletion::Boundary::Liveness);
968969
}
969970
}
970971

0 commit comments

Comments
 (0)