Skip to content

Commit e74cfb0

Browse files
Merge pull request #73386 from nate-chandler/lifetime-completion/20240501/2
[LifetimeCompletion] Require boundary to be specified.
2 parents 796ae1d + 284262d commit e74cfb0

File tree

8 files changed

+134
-78
lines changed

8 files changed

+134
-78
lines changed

include/swift/SIL/OSSALifetimeCompletion.h

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,24 @@ class OSSALifetimeCompletion {
5757
// Availability: "As late as possible." Consume the value in the last blocks
5858
// beyond the non-consuming uses in which the value has been
5959
// consumed on no incoming paths.
60+
// AvailabilityWithLeaks: "As late as possible or later." Consume the value
61+
// in the last blocks beyond the non-consuming uses in
62+
// which the value has been consumed on no incoming
63+
// paths, unless that block's terminator isn't an
64+
// unreachable, in which case, don't consume it there.
65+
//
66+
// This boundary works around bugs where SILGen emits
67+
// illegal OSSA lifetimes.
6068
struct Boundary {
6169
enum Value : uint8_t {
6270
Liveness,
6371
Availability,
72+
AvailabilityWithLeaks,
6473
};
6574
Value value;
6675

6776
Boundary(Value value) : value(value){};
6877
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(); }
7878
};
7979

8080
/// Insert a lifetime-ending instruction on every path to complete the OSSA
@@ -95,9 +95,7 @@ class OSSALifetimeCompletion {
9595
/// lifetime.
9696
///
9797
/// TODO: We also need to complete scoped addresses (e.g. store_borrow)!
98-
LifetimeCompletion
99-
completeOSSALifetime(SILValue value,
100-
std::optional<Boundary> maybeBoundary = std::nullopt) {
98+
LifetimeCompletion completeOSSALifetime(SILValue value, Boundary boundary) {
10199
if (value->getOwnershipKind() == OwnershipKind::None)
102100
return LifetimeCompletion::NoLifetime;
103101

@@ -112,16 +110,19 @@ class OSSALifetimeCompletion {
112110
if (!completedValues.insert(value))
113111
return LifetimeCompletion::AlreadyComplete;
114112

115-
Boundary boundary = maybeBoundary.value_or(
116-
value->isLexical() ? Boundary::Availability : Boundary::Liveness);
117-
118113
return analyzeAndUpdateLifetime(value, boundary)
119114
? LifetimeCompletion::WasCompleted
120115
: LifetimeCompletion::AlreadyComplete;
121116
}
122117

118+
enum AllowLeaks_t : bool {
119+
AllowLeaks = true,
120+
DoNotAllowLeaks = false,
121+
};
122+
123123
static void visitUnreachableLifetimeEnds(
124-
SILValue value, const SSAPrunedLiveness &liveness,
124+
SILValue value, AllowLeaks_t allowLeaks,
125+
const SSAPrunedLiveness &liveness,
125126
llvm::function_ref<void(SILInstruction *)> visit);
126127

127128
protected:
@@ -170,6 +171,22 @@ class UnreachableLifetimeCompletion {
170171
bool completeLifetimes();
171172
};
172173

174+
inline llvm::raw_ostream &
175+
operator<<(llvm::raw_ostream &OS, OSSALifetimeCompletion::Boundary boundary) {
176+
switch (boundary) {
177+
case OSSALifetimeCompletion::Boundary::Liveness:
178+
OS << "liveness";
179+
break;
180+
case OSSALifetimeCompletion::Boundary::Availability:
181+
OS << "availability";
182+
break;
183+
case OSSALifetimeCompletion::Boundary::AvailabilityWithLeaks:
184+
OS << "availability_with_leaks";
185+
break;
186+
}
187+
return OS;
188+
}
189+
173190
} // namespace swift
174191

175192
#endif

lib/SIL/Utils/OSSALifetimeCompletion.cpp

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,21 @@ class VisitUnreachableLifetimeEnds {
122122
/// The value whose dead-end block lifetime ends are to be visited.
123123
SILValue value;
124124

125+
/// Whether to allow leaks.
126+
///
127+
/// Here, that entails allowing walks to reach non-unreachable terminators and
128+
/// not creating lifetime ends before them.
129+
OSSALifetimeCompletion::AllowLeaks_t allowLeaks;
130+
125131
/// The non-lifetime-ending boundary of `value`.
126132
BasicBlockSet starts;
127133
/// The region between (inclusive) the `starts` and the unreachable blocks.
128134
BasicBlockSetVector region;
129135

130136
public:
131-
VisitUnreachableLifetimeEnds(SILValue value)
132-
: value(value), starts(value->getFunction()),
137+
VisitUnreachableLifetimeEnds(SILValue value,
138+
OSSALifetimeCompletion::AllowLeaks_t allowLeaks)
139+
: value(value), allowLeaks(allowLeaks), starts(value->getFunction()),
133140
region(value->getFunction()) {}
134141

135142
/// Region discovery.
@@ -232,9 +239,17 @@ void VisitUnreachableLifetimeEnds::computeRegion(
232239
// (3) Forward walk to find the region in which `value` might be available.
233240
while (auto *block = regionWorklist.pop()) {
234241
if (block->succ_empty()) {
235-
// This assert will fail unless there is already a lifetime-ending
236-
// instruction on each path to normal function exits.
237-
assert(isa<UnreachableInst>(block->getTerminator()));
242+
// This is a function-exiting block.
243+
//
244+
// In valid-but-lifetime-incomplete OSSA there must be a lifetime-ending
245+
// instruction on each path from the def that exits the function normally.
246+
// Thus finding a value available at the end of such a block means that
247+
// the block does _not_ must not exits the function normally; in other
248+
// words its terminator must be an UnreachableInst.
249+
//
250+
// In invalid OSSA, indicated by the `allowLeaks` flag, no such guarantee
251+
// exists.
252+
assert(isa<UnreachableInst>(block->getTerminator()) || allowLeaks);
238253
}
239254
for (auto *successor : block->getSuccessorBlocks()) {
240255
regionWorklist.pushIfNotVisited(successor);
@@ -291,27 +306,33 @@ void VisitUnreachableLifetimeEnds::visitAvailabilityBoundary(
291306
if (!available) {
292307
continue;
293308
}
294-
auto hasUnreachableSuccessor = [&]() {
309+
auto hasUnavailableSuccessor = [&]() {
295310
// Use a lambda to avoid checking if possible.
296311
return llvm::any_of(block->getSuccessorBlocks(), [&result](auto *block) {
297312
return result.getState(block) == State::Unavailable;
298313
});
299314
};
300-
if (!block->succ_empty() && !hasUnreachableSuccessor()) {
315+
if (!block->succ_empty() && !hasUnavailableSuccessor()) {
316+
continue;
317+
}
318+
if (allowLeaks && block->succ_empty() &&
319+
!isa<UnreachableInst>(block->getTerminator())) {
320+
// Availability extends to the end of a function-exiting-normally block.
321+
// If leaks are allowed, don't visit.
301322
continue;
302323
}
303-
assert(hasUnreachableSuccessor() ||
324+
assert(hasUnavailableSuccessor() ||
304325
isa<UnreachableInst>(block->getTerminator()));
305326
visit(block->getTerminator());
306327
}
307328
}
308329
} // end anonymous namespace
309330

310331
void OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
311-
SILValue value, const SSAPrunedLiveness &liveness,
332+
SILValue value, AllowLeaks_t allowLeaks, const SSAPrunedLiveness &liveness,
312333
llvm::function_ref<void(SILInstruction *)> visit) {
313334

314-
VisitUnreachableLifetimeEnds visitor(value);
335+
VisitUnreachableLifetimeEnds visitor(value, allowLeaks);
315336

316337
visitor.computeRegion(liveness);
317338

@@ -322,12 +343,12 @@ void OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
322343
visitor.visitAvailabilityBoundary(result, visit);
323344
}
324345

325-
static bool
326-
endLifetimeAtAvailabilityBoundary(SILValue value,
327-
const SSAPrunedLiveness &liveness) {
346+
static bool endLifetimeAtAvailabilityBoundary(
347+
SILValue value, OSSALifetimeCompletion::AllowLeaks_t allowLeaks,
348+
const SSAPrunedLiveness &liveness) {
328349
bool changed = false;
329350
OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
330-
value, liveness, [&](auto *unreachable) {
351+
value, allowLeaks, liveness, [&](auto *unreachable) {
331352
SILBuilderWithScope builder(unreachable);
332353
endOSSALifetime(value, builder);
333354
changed = true;
@@ -342,20 +363,25 @@ bool OSSALifetimeCompletion::analyzeAndUpdateLifetime(SILValue value,
342363
Boundary boundary) {
343364
// Called for inner borrows, inner adjacent reborrows, inner reborrows, and
344365
// scoped addresses.
345-
auto handleInnerScope = [this](SILValue innerBorrowedValue) {
346-
completeOSSALifetime(innerBorrowedValue);
366+
auto handleInnerScope = [this, boundary](SILValue innerBorrowedValue) {
367+
completeOSSALifetime(innerBorrowedValue, boundary);
347368
};
348369
InteriorLiveness liveness(value);
349370
liveness.compute(domInfo, handleInnerScope);
350371

351372
bool changed = false;
352373
switch (boundary) {
353-
case Boundary::Availability:
354-
changed |= endLifetimeAtAvailabilityBoundary(value, liveness.getLiveness());
355-
break;
356374
case Boundary::Liveness:
357375
changed |= endLifetimeAtLivenessBoundary(value, liveness.getLiveness());
358376
break;
377+
case Boundary::Availability:
378+
changed |= endLifetimeAtAvailabilityBoundary(value, DoNotAllowLeaks,
379+
liveness.getLiveness());
380+
break;
381+
case Boundary::AvailabilityWithLeaks:
382+
changed |= endLifetimeAtAvailabilityBoundary(value, AllowLeaks,
383+
liveness.getLiveness());
384+
break;
359385
}
360386
// TODO: Rebuild outer adjacent phis on demand (SILGen does not currently
361387
// produce guaranteed phis). See FindEnclosingDefs &
@@ -371,16 +397,19 @@ namespace swift::test {
371397
// Dumps:
372398
// - function
373399
static FunctionTest OSSALifetimeCompletionTest(
374-
"ossa-lifetime-completion",
400+
"ossa_lifetime_completion",
375401
[](auto &function, auto &arguments, auto &test) {
376402
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-
}
383-
llvm::outs() << "OSSA lifetime completion: " << value;
403+
OSSALifetimeCompletion::Boundary kind =
404+
llvm::StringSwitch<OSSALifetimeCompletion::Boundary>(
405+
arguments.takeString())
406+
.Case("liveness", OSSALifetimeCompletion::Boundary::Liveness)
407+
.Case("availability",
408+
OSSALifetimeCompletion::Boundary::Availability)
409+
.Case("availability_with_leaks",
410+
OSSALifetimeCompletion::Boundary::AvailabilityWithLeaks);
411+
llvm::outs() << "OSSA lifetime completion on " << kind
412+
<< " boundary: " << value;
384413
OSSALifetimeCompletion completion(&function, /*domInfo*/ nullptr);
385414
completion.completeOSSALifetime(value, kind);
386415
function.print(llvm::outs());
@@ -462,8 +491,9 @@ bool UnreachableLifetimeCompletion::completeLifetimes() {
462491

463492
bool changed = false;
464493
for (auto value : incompleteValues) {
465-
if (completion.completeOSSALifetime(value)
466-
== LifetimeCompletion::WasCompleted) {
494+
if (completion.completeOSSALifetime(
495+
value, OSSALifetimeCompletion::Boundary::Availability) ==
496+
LifetimeCompletion::WasCompleted) {
467497
changed = true;
468498
}
469499
}

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4014,7 +4014,8 @@ bool MoveOnlyAddressChecker::completeLifetimes() {
40144014
[](auto *user) { return isa<BranchInst>(user); })) {
40154015
continue;
40164016
}
4017-
if (completion.completeOSSALifetime(result) ==
4017+
if (completion.completeOSSALifetime(
4018+
result, OSSALifetimeCompletion::Boundary::Availability) ==
40184019
LifetimeCompletion::WasCompleted) {
40194020
changed = true;
40204021
}
@@ -4024,7 +4025,8 @@ bool MoveOnlyAddressChecker::completeLifetimes() {
40244025
if (arg->isReborrow()) {
40254026
continue;
40264027
}
4027-
if (completion.completeOSSALifetime(arg) ==
4028+
if (completion.completeOSSALifetime(
4029+
arg, OSSALifetimeCompletion::Boundary::Availability) ==
40284030
LifetimeCompletion::WasCompleted) {
40294031
changed = true;
40304032
}

lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ void MoveOnlyChecker::completeObjectLifetimes(
163163
for (auto result : inst.getResults()) {
164164
if (!transitiveValues.isVisited(result))
165165
continue;
166-
if (completion.completeOSSALifetime(result) ==
166+
if (completion.completeOSSALifetime(
167+
result, OSSALifetimeCompletion::Boundary::Availability) ==
167168
LifetimeCompletion::WasCompleted) {
168169
madeChange = true;
169170
}
@@ -173,7 +174,8 @@ void MoveOnlyChecker::completeObjectLifetimes(
173174
assert(!arg->isReborrow() && "reborrows not legal at this SIL stage");
174175
if (!transitiveValues.isVisited(arg))
175176
continue;
176-
if (completion.completeOSSALifetime(arg) ==
177+
if (completion.completeOSSALifetime(
178+
arg, OSSALifetimeCompletion::Boundary::Availability) ==
177179
LifetimeCompletion::WasCompleted) {
178180
madeChange = true;
179181
}

lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,8 +2673,9 @@ 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-
auto boundary = OSSALifetimeCompletion::Boundary::getForcingLiveness(
2677-
v->getType().isOrHasEnum());
2676+
auto boundary = v->getType().isOrHasEnum()
2677+
? OSSALifetimeCompletion::Boundary::Liveness
2678+
: OSSALifetimeCompletion::Boundary::Availability;
26782679
LLVM_DEBUG(llvm::dbgs() << "Completing lifetime of: ");
26792680
LLVM_DEBUG(v->dump());
26802681
completion.completeOSSALifetime(v, boundary);

lib/SILOptimizer/Mandatory/SILGenCleanup.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,18 @@ bool SILGenCleanup::completeOSSALifetimes(SILFunction *function) {
117117
for (auto *block : postOrder->getPostOrder()) {
118118
for (SILInstruction &inst : reverse(*block)) {
119119
for (auto result : inst.getResults()) {
120-
if (completion.completeOSSALifetime(result) ==
120+
if (completion.completeOSSALifetime(
121+
result,
122+
OSSALifetimeCompletion::Boundary::AvailabilityWithLeaks) ==
121123
LifetimeCompletion::WasCompleted) {
122124
changed = true;
123125
}
124126
}
125127
}
126128
for (SILArgument *arg : block->getArguments()) {
127129
assert(!arg->isReborrow() && "reborrows not legal at this SIL stage");
128-
if (completion.completeOSSALifetime(arg) ==
130+
if (completion.completeOSSALifetime(
131+
arg, OSSALifetimeCompletion::Boundary::AvailabilityWithLeaks) ==
129132
LifetimeCompletion::WasCompleted) {
130133
changed = true;
131134
}

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ void CanonicalizeOSSALifetime::extendLivenessToDeinitBarriers() {
274274
}
275275

276276
OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
277-
getCurrentDef(), completeLiveness, [&](auto *unreachable) {
277+
getCurrentDef(), OSSALifetimeCompletion::DoNotAllowLeaks,
278+
completeLiveness, [&](auto *unreachable) {
278279
recordUnreachableLifetimeEnd(unreachable);
279280
unreachable->visitPriorInstructions([&](auto *inst) {
280281
liveness->extendToNonUse(inst);

0 commit comments

Comments
 (0)