Skip to content

Commit 7da4934

Browse files
authored
Merge pull request #72407 from atrick/pointer-escape-bailout
Fix lifetime dependence in the presence of pointer escapes.
2 parents b9df9a7 + 5032cf4 commit 7da4934

20 files changed

+307
-140
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ private func extendAccessScopes(dependence: LifetimeDependence,
8585

8686
_ = walker.walkDown(root: dependence.dependentValue)
8787

88+
log("Scope fixup for dependent uses:\n\(accessRange)")
89+
8890
// Lifetime dependenent uses may not be dominated by the access. The dependent value may be used by a phi or stored
8991
// into a memory location. The access may be conditional relative to such uses. If any use was not dominated, then
9092
// `accessRange` will include the function entry.
@@ -219,6 +221,7 @@ private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalk
219221
}
220222

221223
mutating func escapingDependence(on operand: Operand) -> WalkResult {
224+
log(">>> Escaping dependence: \(operand)")
222225
_ = visitor(operand)
223226
// Make a best-effort attempt to extend the access scope regardless of escapes. It is possible that some mandatory
224227
// pass between scope fixup and diagnostics will make it possible for the LifetimeDependenceDefUseWalker to analyze

SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import SIL
1414

15-
private let verbose = true
15+
private let verbose = false
1616

1717
private func log(_ message: @autoclosure () -> String) {
1818
if verbose {
@@ -97,20 +97,26 @@ extension AddressUseVisitor {
9797
if markDep.type.isAddress {
9898
return projectedAddressUse(of: operand, into: markDep)
9999
}
100-
if LifetimeDependence(markDep, context) != nil {
101-
// This is unreachable from InteriorUseVisitor because the
102-
// base address of a `mark_dependence [nonescaping]` must be a
103-
// `begin_access`, and interior liveness does not check uses of
104-
// the accessed address.
100+
switch markDep.dependenceKind {
101+
case .Unresolved:
102+
if LifetimeDependence(markDep, context) == nil {
103+
break
104+
}
105+
fallthrough
106+
case .NonEscaping:
107+
// Note: This is unreachable from InteriorUseVisitor because the base address of a `mark_dependence
108+
// [nonescaping]` must be a `begin_access`, and interior liveness does not check uses of the accessed address.
105109
return dependentAddressUse(of: operand, into: markDep)
110+
case .Escaping:
111+
break
106112
}
107113
// A potentially escaping value depends on this address.
108114
return escapingAddressUse(of: operand)
109115

110-
case let pai as PartialApplyInst where pai.isOnStack:
116+
case let pai as PartialApplyInst where !pai.mayEscape:
111117
return dependentAddressUse(of: operand, into: pai)
112118

113-
case let pai as PartialApplyInst where !pai.isOnStack:
119+
case let pai as PartialApplyInst where pai.mayEscape:
114120
return escapingAddressUse(of: operand)
115121

116122
case is ReturnInst, is ThrowInst, is YieldInst, is AddressToPointerInst:
@@ -502,7 +508,7 @@ extension AddressOwnershipLiveRange {
502508
///
503509
/// For address values, use AccessBase.computeOwnershipRange.
504510
///
505-
/// FIXME: This should use computeLinearLiveness rather than computeInteriorLiveness as soon as lifetime completion
511+
/// FIXME: This should use computeLinearLiveness rather than computeKnownLiveness as soon as lifetime completion
506512
/// runs immediately after SILGen.
507513
private static func computeValueLiveRange(of value: Value, _ context: FunctionPassContext)
508514
-> AddressOwnershipLiveRange? {
@@ -511,7 +517,7 @@ extension AddressOwnershipLiveRange {
511517
// This is unexpected for a value with derived addresses.
512518
return nil
513519
case .owned:
514-
return .owned(value, computeInteriorLiveness(for: value, context))
520+
return .owned(value, computeKnownLiveness(for: value, context))
515521
case .guaranteed:
516522
return .borrow(computeBorrowLiveRange(for: value, context))
517523
}

SwiftCompilerSources/Sources/Optimizer/Utilities/BorrowUtils.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
169169
self = .storeBorrow(sbi)
170170
case let bai as BeginApplyInst:
171171
self = .beginApply(bai)
172-
case let pai as PartialApplyInst where pai.isOnStack:
172+
case let pai as PartialApplyInst where !pai.mayEscape:
173173
self = .partialApply(pai)
174174
case let mdi as MarkDependenceInst:
175175
self = .markDependence(mdi)
@@ -405,8 +405,9 @@ func gatherBorrowIntroducers(for value: Value,
405405
}
406406

407407
/// Compute the live range for the borrow scopes of a guaranteed value. This returns a separate instruction range for
408-
/// each of the value's borrow introducers. Unioning those ranges would be incorrect. We typically want their
409-
/// intersection.
408+
/// each of the value's borrow introducers.
409+
///
410+
/// TODO: This should return a single multiply-defined instruction range.
410411
func computeBorrowLiveRange(for value: Value, _ context: FunctionPassContext)
411412
-> SingleInlineArray<(BeginBorrowValue, InstructionRange)> {
412413
assert(value.ownership == .guaranteed)
@@ -418,10 +419,10 @@ func computeBorrowLiveRange(for value: Value, _ context: FunctionPassContext)
418419
// If introducers is empty, then the dependence is on a trivial value, so
419420
// there is no ownership range.
420421
while let beginBorrow = introducers.pop() {
421-
/// FIXME: Remove calls to computeInteriorLiveness as soon as lifetime completion runs immediately after
422+
/// FIXME: Remove calls to computeKnownLiveness() as soon as lifetime completion runs immediately after
422423
/// SILGen. Instead, this should compute linear liveness for borrowed value by switching over BeginBorrowValue, just
423424
/// like LifetimeDependenc.Scope.computeRange().
424-
ranges.push((beginBorrow, computeInteriorLiveness(for: beginBorrow.value, context)))
425+
ranges.push((beginBorrow, computeKnownLiveness(for: beginBorrow.value, context)))
425426
}
426427
return ranges
427428
}

SwiftCompilerSources/Sources/Optimizer/Utilities/ForwardingUtils.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@
1818

1919
import SIL
2020

21+
private let verbose = false
22+
23+
private func log(_ message: @autoclosure () -> String) {
24+
if verbose {
25+
print("### \(message())")
26+
}
27+
}
28+
2129
/// Return true if any use in `value`s forward-extend lifetime has
2230
/// .pointerEscape operand ownership.
2331
///
@@ -355,7 +363,7 @@ struct NonEscapingClosureDefUseWalker {
355363
}
356364

357365
mutating func walkDown(closure: PartialApplyInst) -> WalkResult {
358-
assert(closure.isOnStack)
366+
assert(!closure.mayEscape)
359367
return walkDownUses(of: closure, using: nil)
360368
}
361369

@@ -370,6 +378,7 @@ struct NonEscapingClosureDefUseWalker {
370378
if operand.instruction.isIncidentalUse {
371379
return .continueWalk
372380
}
381+
log(">>> Unexpected closure use \(operand)")
373382
// Escaping or unexpected closure use. Expected escaping uses include ReturnInst with a lifetime-dependent result.
374383
//
375384
// TODO: Check in the SIL verifier that all uses are expected.
@@ -385,11 +394,15 @@ extension NonEscapingClosureDefUseWalker: ForwardingDefUseWalker {
385394

386395
mutating func nonForwardingUse(of operand: Operand) -> WalkResult {
387396
// Nonescaping closures may be moved, copied, or borrowed.
388-
if let transition = operand.instruction as? OwnershipTransitionInstruction {
397+
switch operand.instruction {
398+
case let transition as OwnershipTransitionInstruction:
389399
return walkDownUses(of: transition.ownershipResult, using: operand)
400+
case let convert as ConvertEscapeToNoEscapeInst:
401+
return walkDownUses(of: convert, using: operand)
402+
default:
403+
// Otherwise, assume the use cannot propagate the closure context.
404+
return closureContextLeafUse(of: operand)
390405
}
391-
// Otherwise, assume the use cannot propagate the closure context.
392-
return closureContextLeafUse(of: operand)
393406
}
394407

395408
mutating func deadValue(_ value: Value, using operand: Operand?) -> WalkResult {

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
import SIL
5656

57-
private let verbose = true
57+
private let verbose = false
5858

5959
private func log(_ message: @autoclosure () -> String) {
6060
if verbose {
@@ -530,13 +530,12 @@ extension LifetimeDependence {
530530
visitedValues.insert(value)
531531
}
532532

533-
// Visit the base value of a lifetime dependence. If the base is
534-
// an address, the dependence scope is the enclosing access. The
535-
// walker does not walk past an `mark_dependence [nonescaping]`
536-
// that produces an address, because that will never occur inside
537-
// of an access scope. An address type mark_dependence
538-
// [nonescaping]` can only result from an indirect function result
539-
// when opaque values are not enabled.
533+
// Visit the base value of a lifetime dependence. If the base is an address, the dependence scope is the enclosing
534+
// access. The walker does not walk past an `mark_dependence [nonescaping]` that produces an address, because that
535+
// will never occur inside of an access scope. An address type mark_dependence [unresolved]` can only result from an
536+
// indirect function result when opaque values are not enabled. Address type `mark_dependence [nonescaping]`
537+
// instruction are also produced for captured arguments but ClosureLifetimeFixup, but those aren't considered to
538+
// have a LifetimeDependence scope.
540539
mutating func introducer(_ value: Value, _ owner: Value?) -> WalkResult {
541540
let base = owner ?? value
542541
guard let scope = LifetimeDependence.Scope(base: base, context)
@@ -617,32 +616,24 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {
617616

618617
/// Walk up the lifetime dependence chain.
619618
///
620-
/// This finds the introducers of a dependence chain. which represent
621-
/// the value's "inherited" dependencies. This stops at phis; all
622-
/// introducers dominate. This stops at addresses in general, but if
623-
/// the value is loaded from a singly-initialized location, then it
624-
/// continues walking up the value stored by the initializer. This
619+
/// This finds the introducers of a dependence chain. which represent the value's "inherited" dependencies. This stops
620+
/// at phis, so all introducers dominate their dependencies. This stops at addresses in general, but if the value is
621+
/// loaded from a singly-initialized location, then it continues walking up the value stored by the initializer. This
625622
/// bypasses the copies to temporary memory locations emitted by SILGen.
626623
///
627-
/// In this example, the dependence root is
628-
/// copied, borrowed, and forwarded before being used as the base
629-
/// operand of `mark_dependence`. The dependence "root" is the parent
630-
/// of the outer-most dependence scope.
624+
/// In this example, the dependence root is copied, borrowed, and forwarded before being used as the base operand of
625+
/// `mark_dependence`. The dependence "root" is the parent of the outer-most dependence scope.
631626
///
632627
/// %root = apply // lifetime dependence root
633628
/// %copy = copy_value %root
634629
/// %parent = begin_borrow %copy // lifetime dependence parent value
635630
/// %base = struct_extract %parent // lifetime dependence base value
636631
/// %dependent = mark_dependence [nonescaping] %value on %base
637632
///
638-
/// This extends the ForwardingUseDefWalker, which finds the
639-
/// forward-extended lifetime introducers. Certain forward-extended
640-
/// lifetime introducers can inherit a lifetime dependency from their
641-
/// operand: namely copies, moves, and borrows. These introducers are
642-
/// considered part of their operand's dependence scope because
643-
/// non-escapable values can be copied, moved, and
644-
/// borrowed. Nonetheless, all of their uses must remain within
645-
/// original dependence scope.
633+
/// This extends the ForwardingUseDefWalker, which finds the forward-extended lifetime introducers. Certain
634+
/// forward-extended lifetime introducers can inherit a lifetime dependency from their operand: namely copies, moves,
635+
/// and borrows. These introducers are considered part of their operand's dependence scope because non-escapable values
636+
/// can be copied, moved, and borrowed. Nonetheless, all of their uses must remain within original dependence scope.
646637
///
647638
/// # owned lifetime dependence
648639
/// %parent = apply // begin dependence scope -+
@@ -662,10 +653,9 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {
662653
/// ... |
663654
/// destroy_value %parent // end dependence scope -+
664655
///
665-
/// All of the dependent uses including `end_borrow %5` and
666-
/// `destroy_value %4` must be before the end of the dependence scope:
667-
/// `destroy_value %parent`. In this case, the dependence parent is an
668-
/// owned value, so the scope is simply the value's OSSA lifetime.
656+
/// All of the dependent uses including `end_borrow %5` and `destroy_value %4` must be before the end of the dependence
657+
/// scope: `destroy_value %parent`. In this case, the dependence parent is an owned value, so the scope is simply the
658+
/// value's OSSA lifetime.
669659
///
670660
/// Minimal requirements:
671661
/// var context: Context
@@ -740,6 +730,7 @@ extension LifetimeDependenceUseDefWalker {
740730
if Phi(value) != nil {
741731
return introducer(value, owner)
742732
}
733+
// ForwardingUseDefWalker will callback to introducer() when it finds no forwarding instruction.
743734
return walkUpDefault(forwarded: value, owner)
744735
}
745736

@@ -792,6 +783,8 @@ extension LifetimeDependenceUseDefWalker {
792783
/// yieldedDependence(result: Operand) -> WalkResult
793784
/// Start walking:
794785
/// walkDown(root: Value)
786+
///
787+
/// Note: this may visit values that are not dominated by `root` because of dependent phi operands.
795788
protocol LifetimeDependenceDefUseWalker : ForwardingDefUseWalker,
796789
OwnershipUseVisitor,
797790
AddressUseVisitor {

SwiftCompilerSources/Sources/Optimizer/Utilities/LocalVariableUtils.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
import SIL
2727

28-
private let verbose = true
28+
private let verbose = false
2929

3030
private func log(_ message: @autoclosure () -> String) {
3131
if verbose {
@@ -188,8 +188,8 @@ class LocalVariableAccessInfo: CustomStringConvertible {
188188
}
189189

190190
var description: String {
191-
return "\(access)" +
192-
"\n fully-assigned: \(_isFullyAssigned == nil ? "unknown" : String(describing: _isFullyAssigned!))"
191+
return "full-assign: \(_isFullyAssigned == nil ? "unknown" : String(describing: _isFullyAssigned!)) "
192+
+ "\(access)"
193193
}
194194
}
195195

@@ -200,7 +200,7 @@ class LocalVariableAccessInfo: CustomStringConvertible {
200200
/// map.
201201
///
202202
/// TODO: In addition to isFullyAssigned, consider adding a lazily computed access path if any need arises.
203-
struct LocalVariableAccessMap: Collection, FormattedLikeArray {
203+
struct LocalVariableAccessMap: Collection, CustomStringConvertible {
204204
let context: Context
205205
let allocation: Value
206206

@@ -279,6 +279,10 @@ struct LocalVariableAccessMap: Collection, FormattedLikeArray {
279279
subscript(_ accessIndex: Int) -> LocalVariableAccessInfo { accessList[accessIndex] }
280280

281281
subscript(instruction: Instruction) -> LocalVariableAccessInfo? { accessMap[instruction] }
282+
283+
public var description: String {
284+
"Access map:\n" + map({String(describing: $0)}).joined(separator: "\n")
285+
}
282286
}
283287

284288
/// Gather the accesses of a local allocation: alloc_box, alloc_stack, @in, @inout.
@@ -416,7 +420,7 @@ extension LocalVariableAccessWalker: AddressUseVisitor {
416420

417421
mutating func dependentAddressUse(of operand: Operand, into value: Value) -> WalkResult {
418422
// Find all uses of partial_apply [on_stack].
419-
if let pai = value as? PartialApplyInst, pai.isOnStack {
423+
if let pai = value as? PartialApplyInst, !pai.mayEscape {
420424
var walker = NonEscapingClosureDefUseWalker(context)
421425
defer { walker.deinitialize() }
422426
if walker.walkDown(closure: pai) == .abortWalk {
@@ -441,7 +445,8 @@ extension LocalVariableAccessWalker: AddressUseVisitor {
441445
}
442446

443447
mutating func escapingAddressUse(of operand: Operand) -> WalkResult {
444-
return .abortWalk
448+
visit(LocalVariableAccess(.escape, operand))
449+
return .continueWalk
445450
}
446451

447452
mutating func unknownAddressUse(of operand: Operand) -> WalkResult {
@@ -732,6 +737,8 @@ extension LocalVariableReachableAccess {
732737
forwardPropagateEffect(in: block, blockInfo: blockInfo, effect: currentEffect, blockList: &blockList,
733738
accessStack: &accessStack)
734739
}
740+
log("\(accessMap)")
741+
log("Reachable access:\n\(accessStack.map({ String(describing: $0)}).joined(separator: "\n"))")
735742
return true
736743
}
737744

0 commit comments

Comments
 (0)