Skip to content

Commit d207b34

Browse files
committed
Fix BorrowingOperand.visitScopeEndingUses
Only return false if the visitor returns false. Clients were ignoring the result. If the BorrowingOperand does not create a borrow scope, call visitUnknownUse instead. Until we have complete lifetimes, to avoid breaking code that cannot handle dead defs, consider a dead borrow scope to be an unknown use.
1 parent d3cb90f commit d207b34

File tree

7 files changed

+103
-57
lines changed

7 files changed

+103
-57
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,7 @@ extension LifetimeDependenceDefUseWalker {
778778
case .beginApply:
779779
// Skip the borrow scope; the type system enforces non-escapable
780780
// arguments.
781-
return visitInnerBorrowUses(of: borrowInst)
781+
return visitInnerBorrowUses(of: borrowInst, operand: operand)
782782
case .partialApply, .markDependence:
783783
fatalError("OwnershipUseVisitor should bypass partial_apply [on_stack] "
784784
+ "and mark_dependence [nonescaping]")

SwiftCompilerSources/Sources/Optimizer/Utilities/OwnershipLiveness.swift

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -321,13 +321,16 @@ extension OwnershipUseVisitor {
321321
}
322322
}
323323

324-
/// Visit only those uses of a value within an inner borrow scope
325-
/// that may affect the outer lifetime. An inner borrow scope is one
326-
/// in which the borrowing operand is itself a use of the outer
327-
/// lifetime, including: begin_borrow, reborrow, partial_apply,
328-
/// mark_dependence, or an inner adjacent phi (where original SSA
329-
/// def is a phi in the same block).
330-
mutating func visitInnerScopeUses(of: Value) -> WalkResult {
324+
/// Visit only those uses of a value within an inner borrow scope that may affect the outer lifetime. An inner borrow
325+
/// scope is one in which the borrowing operand is itself a use of the outer lifetime, including: begin_borrow,
326+
/// reborrow, borrowed_from, partial_apply, mark_dependence, or an inner adjacent phi (where original SSA def is a phi
327+
/// in the same block).
328+
///
329+
/// An owned lifetime may also be considered an inner borrow scope if it depends on a borrowed operand, such as a
330+
/// closure capture or owned mark_dependence.
331+
///
332+
/// Precondition: BeginBorrowValue(value) != nil || value.ownership == .owned
333+
mutating func visitInnerScopeUses(of value: Value) -> WalkResult {
331334
if let beginBorrow = BeginBorrowValue(value) {
332335
return beginBorrow.scopeEndingOperands.walk {
333336
switch $0.ownership {
@@ -367,10 +370,14 @@ extension OwnershipUseVisitor {
367370
// TODO: remove this stack by changing visitScopeEndingOperands to take a non-escaping closure.
368371
var stack = Stack<Operand>(context)
369372
defer { stack.deinitialize() }
370-
_ = borrowInst.visitScopeEndingOperands(context) {
373+
let result = borrowInst.visitScopeEndingOperands(context) {
371374
stack.push($0)
372375
return .continueWalk
373376
}
377+
guard result == .continueWalk else {
378+
// If the dependent value is not scoped then consider it a pointer escape.
379+
return pointerEscapingUse(of: operand)
380+
}
374381
return stack.walk { ownershipLeafUse(of: $0, isInnerLifetime: true) }
375382
}
376383
}
@@ -640,7 +647,7 @@ extension InteriorUseWalker: OwnershipUseVisitor {
640647
return visitAllUses(of: beginBorrow.value)
641648
}
642649
}
643-
return visitInnerScopeUses(of: borrowInst)
650+
return visitInnerBorrowUses(of: borrowInst, operand: operand)
644651
}
645652

646653
// Visit a reborrow operand. This ends an outer lifetime and extends

include/swift/SIL/OwnershipUtils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ struct BorrowingOperand {
365365
/// For an instantaneous borrow, such as apply, this visits no uses. For
366366
/// begin_apply it visits the end_apply uses. For borrow introducers, it
367367
/// visits the end of the introduced borrow scope.
368+
///
369+
/// For borrows that don't introduce a separate borrow scope, this calls
370+
/// visitUnknownUse on the current operand. The client may need to check each
371+
/// unknown operand to avoid infinite recursion.
368372
bool visitScopeEndingUses(function_ref<bool(Operand *)> visitScopeEnd,
369373
function_ref<bool(Operand *)> visitUnknownUse
370374
= [](Operand *){ return false; })

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -297,23 +297,19 @@ bool swift::findInnerTransitiveGuaranteedUses(
297297
//
298298
// FIXME: visit[Extended]ScopeEndingUses can't return false here once dead
299299
// borrows are disallowed.
300-
if (!BorrowingOperand(use).visitScopeEndingUses(
301-
[&](Operand *endUse) {
302-
if (endUse->getOperandOwnership() == OperandOwnership::Reborrow) {
303-
foundPointerEscape = true;
304-
}
305-
leafUse(endUse);
306-
return true;
307-
},
308-
[&](Operand *unknownUse) {
309-
foundPointerEscape = true;
310-
leafUse(unknownUse);
311-
return true;
312-
})) {
313-
// Special case for dead borrows. This is dangerous because clients
314-
// don't expect a begin_borrow to be in the use list.
315-
leafUse(use);
316-
}
300+
BorrowingOperand(use).visitScopeEndingUses(
301+
[&](Operand *endUse) {
302+
if (endUse->getOperandOwnership() == OperandOwnership::Reborrow) {
303+
foundPointerEscape = true;
304+
}
305+
leafUse(endUse);
306+
return true;
307+
},
308+
[&](Operand *unknownUse) {
309+
foundPointerEscape = true;
310+
leafUse(unknownUse);
311+
return true;
312+
});
317313
break;
318314
}
319315
}
@@ -676,10 +672,12 @@ bool BorrowingOperand::visitScopeEndingUses(
676672
return false;
677673
}
678674
}
679-
// FIXME: special case for dead borrows. This is dangerous because clients
680-
// only expect visitScopeEndingUses to return false if the visitor returned
681-
// false.
682-
return !deadBorrow;
675+
// Note: special case for dead borrows. This is dangerous because it could
676+
// cause unsuspecting clients to infinitely recurse.
677+
if (deadBorrow) {
678+
return visitUnknownUse(op);
679+
}
680+
return true;
683681
}
684682
case BorrowingOperandKind::StoreBorrow: {
685683
bool deadBorrow = true;
@@ -690,10 +688,12 @@ bool BorrowingOperand::visitScopeEndingUses(
690688
return false;
691689
}
692690
}
693-
// FIXME: special case for dead borrows. This is dangerous because clients
694-
// only expect visitScopeEndingUses to return false if the visitor returned
695-
// false.
696-
return !deadBorrow;
691+
// Note: special case for dead borrows. This is dangerous because it could
692+
// cause unsuspecting clients to infinitely recurse.
693+
if (deadBorrow) {
694+
return visitUnknownUse(op);
695+
}
696+
return true;
697697
}
698698
case BorrowingOperandKind::BeginApply: {
699699
bool deadApply = true;
@@ -733,7 +733,10 @@ bool BorrowingOperand::visitScopeEndingUses(
733733
return false;
734734
}
735735
}
736-
return !dead;
736+
if (dead) {
737+
return visitUnknownUse(op);
738+
}
739+
return true;
737740
}
738741
// These are instantaneous borrow scopes so there aren't any special end
739742
// scope instructions.
@@ -751,7 +754,10 @@ bool BorrowingOperand::visitScopeEndingUses(
751754
return false;
752755
}
753756
}
754-
return !deadBranch;
757+
if (deadBranch) {
758+
return visitUnknownUse(op);
759+
}
760+
return true;
755761
}
756762
}
757763
llvm_unreachable("Covered switch isn't covered");

lib/SIL/Utils/PrunedLiveness.cpp

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -278,21 +278,18 @@ PrunedLiveRange<LivenessWithDefs>::updateForBorrowingOperand(Operand *operand) {
278278
// Note: Ownership liveness should follow reborrows that are dominated by the
279279
// ownership definition.
280280
auto innerBorrowKind = InnerBorrowKind::Contained;
281-
if (!BorrowingOperand(operand).visitScopeEndingUses(
282-
[&](Operand *end) {
283-
if (end->getOperandOwnership() == OperandOwnership::Reborrow) {
284-
innerBorrowKind = InnerBorrowKind::Reborrowed;
285-
}
286-
updateForUse(end->getUser(), /*lifetimeEnding*/ false);
287-
return true;
288-
}, [&](Operand *unknownUse) {
289-
updateForUse(unknownUse->getUser(), /*lifetimeEnding*/ false);
290-
innerBorrowKind = InnerBorrowKind::Escaped;
291-
return true;
292-
})) {
293-
// Handle dead borrows.
294-
updateForUse(operand->getUser(), /*lifetimeEnding*/ false);
295-
}
281+
BorrowingOperand(operand).visitScopeEndingUses(
282+
[&](Operand *end) {
283+
if (end->getOperandOwnership() == OperandOwnership::Reborrow) {
284+
innerBorrowKind = InnerBorrowKind::Reborrowed;
285+
}
286+
updateForUse(end->getUser(), /*lifetimeEnding*/ false);
287+
return true;
288+
}, [&](Operand *unknownUse) {
289+
updateForUse(unknownUse->getUser(), /*lifetimeEnding*/ false);
290+
innerBorrowKind = InnerBorrowKind::Escaped;
291+
return true;
292+
});
296293
return innerBorrowKind;
297294
}
298295

lib/SIL/Verifier/SILOwnershipVerifier.cpp

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,23 @@ bool SILValueOwnershipChecker::gatherNonGuaranteedUsers(
277277

278278
// If our scoped operand is not also a borrow introducer, then we know that
279279
// we do not need to consider guaranteed phis and thus can just add the
280-
// initial end scope instructions without any further work.
281-
//
282-
// Maybe: Is borrow scope non-local?
283-
initialScopedOperand.getImplicitUses(nonLifetimeEndingUsers);
280+
// initial end scope instructions without any further work. This avoids
281+
// cubic complexity in the verifier.
282+
auto addLeafUse = [&](Operand *endOp) {
283+
nonLifetimeEndingUsers.push_back(endOp);
284+
return true;
285+
};
286+
auto addUnknownUse = [&](Operand *endOp) {
287+
// FIXME: This ignores mark_dependence base uses and borrowed_from base
288+
// uses. Instead, recursively call gatherUsers.
289+
// Note: we may have op == endOp.
290+
nonLifetimeEndingUsers.push_back(endOp);
291+
return true;
292+
};
293+
// FIXME: This ignores dead-end blocks. Instead, recursively call
294+
// gatherUsers.
295+
initialScopedOperand.visitScopeEndingUses(addLeafUse, addUnknownUse);
296+
284297
if (initialScopedOperand.kind == BorrowingOperandKind::BeginBorrow) {
285298
guaranteedPhiVerifier.verifyReborrows(
286299
cast<BeginBorrowInst>(op->getUser()));
@@ -391,7 +404,21 @@ bool SILValueOwnershipChecker::gatherUsers(
391404
if (auto scopedOperand = BorrowingOperand(op)) {
392405
assert(!scopedOperand.isReborrow());
393406

394-
scopedOperand.getImplicitUses(nonLifetimeEndingUsers);
407+
auto addLeafUse = [&](Operand *endOp) {
408+
nonLifetimeEndingUsers.push_back(endOp);
409+
return true;
410+
};
411+
auto addUnknownUse = [&](Operand *endOp) {
412+
// FIXME: This ignores mark_dependence base uses and borrowed_from
413+
// base uses. Instead, recursively call gatherUsers. Note: we may
414+
// have op == endOp.
415+
nonLifetimeEndingUsers.push_back(endOp);
416+
return true;
417+
};
418+
// FIXME: This ignores dead-end blocks. Instead, recursively call
419+
// gatherUsers.
420+
scopedOperand.visitScopeEndingUses(addLeafUse, addUnknownUse);
421+
395422
if (scopedOperand.kind == BorrowingOperandKind::BeginBorrow) {
396423
guaranteedPhiVerifier.verifyReborrows(
397424
cast<BeginBorrowInst>(op->getUser()));

lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,9 @@ bool Implementation::gatherUses(SILValue value) {
477477
false /*is lifetime ending*/);
478478
}
479479
// The liveness extends to the scope-ending uses of the borrow.
480+
//
481+
// FIXME: this ignores visitScopeEndingUses failure, which may result from
482+
// unknown uses or dead borrows.
480483
BorrowingOperand(nextUse).visitScopeEndingUses([&](Operand *end) -> bool {
481484
if (end->getOperandOwnership() == OperandOwnership::Reborrow) {
482485
return false;
@@ -1090,6 +1093,7 @@ static void insertEndBorrowsForNonConsumingUse(Operand *op,
10901093
LLVM_DEBUG(llvm::dbgs() << " -- Ending borrow after borrow scope:\n"
10911094
" ";
10921095
op->getUser()->print(llvm::dbgs()));
1096+
// FIXME: ignoring the visitScopeEndingUses result ignores unknown uses.
10931097
bOp.visitScopeEndingUses([&](Operand *endScope) -> bool {
10941098
auto *endScopeInst = endScope->getUser();
10951099
LLVM_DEBUG(llvm::dbgs() << " ";
@@ -1107,6 +1111,7 @@ static void insertEndBorrowsForNonConsumingUse(Operand *op,
11071111
// End the borrow where the original borrow of the subject was ended.
11081112
// TODO: handle if the switch isn't directly on a borrow?
11091113
auto beginBorrow = cast<BeginBorrowInst>(swi->getOperand());
1114+
// FIXME: ignoring the visitScopeEndingUses result ignores unknown uses.
11101115
BorrowingOperand(&beginBorrow->getOperandRef())
11111116
.visitScopeEndingUses([&](Operand *endScope) -> bool {
11121117
auto *endScopeInst = endScope->getUser();

0 commit comments

Comments
 (0)