Skip to content

Commit 1b72c7b

Browse files
committed
SILGenCleanup: extend to handle trivial local var scopes
Improves OSSALifetimeCompletion to handle trivial variables when running from SILGenCleanup. This only affects lifetime dependence diagnostics. For the purpose of lifetime diagnostics, trivial local variables are only valid within their lexical scope. Sadly, SILGen only know how to insert cleanup code on normal function exits. SILGenCleanup relies on lifetime completion to fix lifetimes on dead end paths. %var = move_value [var_decl] try_apply %f() : $..., normal bb1, error error error: extend_lifetime %var <=== insert this unreachable This allows Span to depend on local unsafe pointers AND be used within throwing closures: _ = a.withUnsafeBufferPointer { let buffer = $0 let view = Span(_unsafeElements: buffer) return view.withUnsafeBufferPointer(\.count) }
1 parent f2ddef7 commit 1b72c7b

File tree

7 files changed

+127
-13
lines changed

7 files changed

+127
-13
lines changed

include/swift/SIL/OSSALifetimeCompletion.h

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class DeadEndBlocks;
3939
enum class LifetimeCompletion { NoLifetime, AlreadyComplete, WasCompleted };
4040

4141
class OSSALifetimeCompletion {
42+
public:
43+
enum HandleTrivialVariable_t { IgnoreTrivialVariable, ExtendTrivialVariable };
44+
45+
private:
4246
// If domInfo is nullptr, then InteriorLiveness never assumes dominance. As a
4347
// result it may report extra unenclosedPhis. In that case, any attempt to
4448
// create a new phi would result in an immediately redundant phi.
@@ -50,11 +54,15 @@ class OSSALifetimeCompletion {
5054
// recomputing their lifetimes.
5155
ValueSet completedValues;
5256

57+
// Extend trivial variables for lifetime diagnostics (only in SILGenCleanup).
58+
HandleTrivialVariable_t handleTrivialVariable;
59+
5360
public:
5461
OSSALifetimeCompletion(SILFunction *function, const DominanceInfo *domInfo,
55-
DeadEndBlocks &deadEndBlocks)
62+
DeadEndBlocks &deadEndBlocks,
63+
HandleTrivialVariable_t handleTrivialVariable = IgnoreTrivialVariable)
5664
: domInfo(domInfo), deadEndBlocks(deadEndBlocks),
57-
completedValues(function) {}
65+
completedValues(function), handleTrivialVariable(handleTrivialVariable) {}
5866

5967
// The kind of boundary at which to complete the lifetime.
6068
//
@@ -93,10 +101,15 @@ class OSSALifetimeCompletion {
93101
LifetimeCompletion completeOSSALifetime(SILValue value, Boundary boundary) {
94102
switch (value->getOwnershipKind()) {
95103
case OwnershipKind::None: {
96-
auto scopedAddress = ScopedAddressValue(value);
97-
if (!scopedAddress)
98-
return LifetimeCompletion::NoLifetime;
99-
break;
104+
if (auto scopedAddress = ScopedAddressValue(value)) {
105+
break;
106+
}
107+
// During SILGenCleanup, extend move_value [var_decl].
108+
if (handleTrivialVariable == ExtendTrivialVariable
109+
&& value->isFromVarDecl()) {
110+
break;
111+
}
112+
return LifetimeCompletion::NoLifetime;
100113
}
101114
case OwnershipKind::Owned:
102115
break;

include/swift/SIL/OwnershipUseVisitor.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ class OwnershipUseVisitor : OwnershipUseVisitorBase<Impl> {
165165
protected:
166166
bool visitConsumes(SILValue ssaDef);
167167

168+
bool visitExtends(SILValue ssaDef);
169+
168170
bool visitOuterBorrow(SILValue borrowBegin);
169171

170172
bool visitOuterBorrowScopeEnd(Operand *borrowEnd);
@@ -202,8 +204,12 @@ bool OwnershipUseVisitor<Impl>::visitLifetimeEndingUses(SILValue ssaDef) {
202204
case OwnershipKind::Guaranteed:
203205
return visitOuterBorrow(ssaDef);
204206

205-
case OwnershipKind::Any:
206207
case OwnershipKind::None:
208+
if (ssaDef->isFromVarDecl()) {
209+
return visitExtends(ssaDef);
210+
}
211+
LLVM_FALLTHROUGH;
212+
case OwnershipKind::Any:
207213
case OwnershipKind::Unowned:
208214
llvm_unreachable("requires an owned or guaranteed orignalDef");
209215
}
@@ -231,6 +237,18 @@ bool OwnershipUseVisitor<Impl>::visitConsumes(SILValue ssaDef) {
231237
return true;
232238
}
233239

240+
template <typename Impl>
241+
bool OwnershipUseVisitor<Impl>::visitExtends(SILValue ssaDef) {
242+
for (Operand *use : ssaDef->getUses()) {
243+
if (isa<ExtendLifetimeInst>(use->getUser())) {
244+
if (!handleUsePoint(use, UseLifetimeConstraint::NonLifetimeEnding))
245+
return false;
246+
continue;
247+
}
248+
}
249+
return true;
250+
}
251+
234252
template <typename Impl>
235253
bool OwnershipUseVisitor<Impl>::visitOuterBorrow(SILValue borrowBegin) {
236254
BorrowedValue borrow(borrowBegin);

lib/SIL/Utils/OSSALifetimeCompletion.cpp

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ static SILInstruction *endOSSALifetime(SILValue value,
8080
if (auto scopedAddress = ScopedAddressValue(value)) {
8181
return scopedAddress.createScopeEnd(builder.getInsertionPoint(), loc);
8282
}
83+
if (value->getOwnershipKind() == OwnershipKind::None) {
84+
return builder.createExtendLifetime(loc, value);
85+
}
8386
return builder.createEndBorrow(loc, lookThroughBorrowedFromUser(value));
8487
}
8588

@@ -297,9 +300,16 @@ void AvailabilityBoundaryVisitor::computeRegion(
297300
regionWorklist.push(block);
298301
};
299302

300-
for (auto *endBlock : boundary.endBlocks) {
301-
if (!consumingBlocks.contains(endBlock)) {
302-
collect(endBlock);
303+
// Trivial values that correspond to local variables (as opposed to
304+
// ScopedAddresses) are available only up to their last extend_lifetime on
305+
// non-dead-end paths. They cannot be consumed, but are only "available" up to
306+
// the end of their scope.
307+
if (value->getOwnershipKind() != OwnershipKind::None
308+
|| ScopedAddressValue(value)) {
309+
for (auto *endBlock : boundary.endBlocks) {
310+
if (!consumingBlocks.contains(endBlock)) {
311+
collect(endBlock);
312+
}
303313
}
304314
}
305315
for (SILBasicBlock *edge : boundary.boundaryEdges) {
@@ -497,12 +507,21 @@ bool OSSALifetimeCompletion::analyzeAndUpdateLifetime(SILValue value,
497507
if (auto scopedAddress = ScopedAddressValue(value)) {
498508
return analyzeAndUpdateLifetime(scopedAddress, boundary);
499509
}
500-
501510
// Called for inner borrows, inner adjacent reborrows, inner reborrows, and
502511
// scoped addresses.
503512
auto handleInnerScope = [this, boundary](SILValue innerBorrowedValue) {
504513
completeOSSALifetime(innerBorrowedValue, boundary);
505514
};
515+
if (value->getOwnershipKind() == OwnershipKind::None) {
516+
// Trivial variable lifetimes are only relevant up to the extend_lifetime
517+
// instructions emitted by SILGen. Their other uses have no meaning with
518+
// respect to lifetime. The only purpose of "completing" their lifetime is
519+
// to insert extend_lifetime on dead-end blocks.
520+
LinearLiveness liveness(value);
521+
liveness.compute();
522+
return endLifetimeAtBoundary(value, liveness.getLiveness(), boundary,
523+
deadEndBlocks);
524+
}
506525
InteriorLiveness liveness(value);
507526
liveness.compute(domInfo, handleInnerScope);
508527
// TODO: Rebuild outer adjacent phis on demand (SILGen does not currently

lib/SIL/Utils/OwnershipLiveness.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,21 @@ struct LinearLivenessVisitor :
7979
LinearLiveness::LinearLiveness(SILValue def,
8080
IncludeExtensions_t includeExtensions)
8181
: OSSALiveness(def), includeExtensions(includeExtensions) {
82-
if (def->getOwnershipKind() != OwnershipKind::Owned) {
82+
switch (def->getOwnershipKind()) {
83+
case OwnershipKind::Owned:
84+
break;
85+
case OwnershipKind::Guaranteed: {
8386
BorrowedValue borrowedValue(def);
8487
assert(borrowedValue && borrowedValue.isLocalScope());
8588
(void)borrowedValue;
89+
break;
90+
}
91+
case OwnershipKind::None:
92+
assert(def->isFromVarDecl());
93+
break;
94+
case OwnershipKind::Unowned:
95+
case OwnershipKind::Any:
96+
llvm_unreachable("bad ownership for LinearLiveness");
8697
}
8798
}
8899

lib/SILOptimizer/Mandatory/SILGenCleanup.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,9 @@ bool SILGenCleanup::completeOSSALifetimes(SILFunction *function) {
267267
}
268268

269269
bool changed = false;
270-
OSSALifetimeCompletion completion(function, /*DomInfo*/ nullptr, *deba);
270+
OSSALifetimeCompletion completion(
271+
function, /*DomInfo*/ nullptr, *deba,
272+
OSSALifetimeCompletion::ExtendTrivialVariable);
271273
BasicBlockSet completed(function);
272274
for (auto *root : roots) {
273275
if (root == function->getEntryBlock()) {

test/SILOptimizer/lifetime_dependence/semantics.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,19 @@ func testTrivialScope<T>(a: Array<T>) -> Span<T> {
228228
// expected-note @-3{{this use causes the lifetime-dependent value to escape}}
229229
}
230230

231+
extension Span {
232+
public func withThrowingClosure<E: Error>(_ body: () throws(E) -> ()) throws(E) -> () {
233+
try body()
234+
}
235+
}
236+
237+
// Test dependence on an local variable that needs to be extended into the dead-end block of a never-throwing apply.
238+
public func test(p: UnsafePointer<Int>) {
239+
let pointer = p
240+
let span = Span(base: pointer, count: 1)
241+
span.withThrowingClosure {}
242+
}
243+
231244
// =============================================================================
232245
// Scoped dependence on property access
233246
// =============================================================================

test/SILOptimizer/silgen_cleanup_complete_ossa.sil

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ sil_stage raw
66

77
typealias AnyObject = Builtin.AnyObject
88

9+
protocol Error {}
10+
911
class Klass {
1012
var property: Builtin.Int64
1113
}
@@ -28,6 +30,12 @@ struct UInt8 {
2830

2931
protocol P : AnyObject {}
3032

33+
struct Err: Error {
34+
var i: Int
35+
}
36+
37+
sil @throwing : $@convention(thin) (UInt8) -> @error_indirect Err
38+
3139
// =============================================================================
3240
// Test complete OSSA lifetimes
3341
// =============================================================================
@@ -367,3 +375,33 @@ left:
367375
right:
368376
unreachable
369377
}
378+
379+
// CHECK-LABEL: sil [ossa] @testExtendTrivialToDeadEnd : $@convention(thin) (UInt8) -> () {
380+
// CHECK: bb0(%0 : $UInt8):
381+
// CHECK: [[MV:%.*]] = move_value [var_decl] %0 : $UInt8
382+
// CHECK: try_apply %{{.*}}(%{{.*}}, [[MV]]) : $@convention(thin) (UInt8) -> @error_indirect Err, normal bb1, error bb2
383+
// CHECK: bb1(
384+
// CHECK: extend_lifetime [[MV]] : $UInt8
385+
// CHECK: return
386+
// CHECK: bb2:
387+
// CHECK: dealloc_stack
388+
// CHECK: extend_lifetime [[MV]] : $UInt8
389+
// CHECK: unreachable
390+
// CHECK-LABEL: } // end sil function 'testExtendTrivialToDeadEnd'
391+
sil [ossa] @testExtendTrivialToDeadEnd : $@convention(thin) (UInt8) -> () {
392+
bb0(%0 : $UInt8):
393+
%mv = move_value [var_decl] %0
394+
%e = alloc_stack $Err
395+
%f = function_ref @throwing : $@convention(thin) (UInt8) -> @error_indirect Err
396+
try_apply %f(%e, %mv) : $@convention(thin) (UInt8) -> @error_indirect Err, normal bb1, error bb2
397+
398+
bb1(%ret : $()):
399+
extend_lifetime %mv
400+
dealloc_stack %e : $*Err
401+
%99 = tuple ()
402+
return %99
403+
404+
bb2:
405+
dealloc_stack %e : $*Err
406+
unreachable
407+
}

0 commit comments

Comments
 (0)