Skip to content

Commit bdabc21

Browse files
committed
Add computeKnownLiveness utility
To fix LifetimeDependenceScopeFixup in the presense of pointer escapes.
1 parent aa208bb commit bdabc21

File tree

4 files changed

+123
-66
lines changed

4 files changed

+123
-66
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ extension AddressOwnershipLiveRange {
502502
///
503503
/// For address values, use AccessBase.computeOwnershipRange.
504504
///
505-
/// FIXME: This should use computeLinearLiveness rather than computeInteriorLiveness as soon as lifetime completion
505+
/// FIXME: This should use computeLinearLiveness rather than computeKnownLiveness as soon as lifetime completion
506506
/// runs immediately after SILGen.
507507
private static func computeValueLiveRange(of value: Value, _ context: FunctionPassContext)
508508
-> AddressOwnershipLiveRange? {
@@ -511,7 +511,7 @@ extension AddressOwnershipLiveRange {
511511
// This is unexpected for a value with derived addresses.
512512
return nil
513513
case .owned:
514-
return .owned(value, computeInteriorLiveness(for: value, context))
514+
return .owned(value, computeKnownLiveness(for: value, context))
515515
case .guaranteed:
516516
return .borrow(computeBorrowLiveRange(for: value, context))
517517
}

SwiftCompilerSources/Sources/Optimizer/Utilities/BorrowUtils.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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/OwnershipLiveness.swift

Lines changed: 116 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@
2323

2424
import SIL
2525

26+
private let verbose = false
27+
28+
private func log(_ message: @autoclosure () -> String) {
29+
if verbose {
30+
print("### \(message())")
31+
}
32+
}
33+
2634
/// Compute liveness and return a range, which the caller must deinitialize.
2735
///
2836
/// `definingValue` must introduce an OSSA lifetime. It may be either
@@ -66,15 +74,13 @@ typealias InnerScopeHandler = (Value) -> WalkResult
6674

6775
/// Compute liveness and return a range, which the caller must deinitialize.
6876
///
69-
/// An OSSA lifetime begins with a single "defining" value, which must
70-
/// be owned, or must begin a borrow scope. A complete OSSA lifetime
71-
/// has a linear lifetime, meaning that it has a lifetime-ending use
72-
/// on all paths. Interior liveness computes liveness without assuming
73-
/// the lifetime is complete. To do this, it must find all "use
74-
/// points" and prove that the defining value is never propagated
75-
/// beyond those points. This is used to initially complete OSSA
76-
/// lifetimes and fix them after transformations that's don't preserve
77-
/// OSSA.
77+
/// An OSSA lifetime begins with a single "defining" value, which must be owned, or must begin a borrow scope. A
78+
/// complete OSSA lifetime has a linear lifetime, meaning that it has a lifetime-ending use on all paths. Interior
79+
/// liveness computes liveness without assuming the lifetime is complete. To do this, it must find all "use points" and
80+
/// prove that the defining value is never propagated beyond those points. This is used to initially complete OSSA
81+
/// lifetimes and fix them after transformations that's don't preserve OSSA.
82+
///
83+
/// The caller must check that `definingValue` has no pointer escape before calling this.
7884
///
7985
/// Invariants:
8086
///
@@ -83,39 +89,100 @@ typealias InnerScopeHandler = (Value) -> WalkResult
8389
/// - Liveness does not extend beyond lifetime-ending operations
8490
/// (a.k.a. affine lifetimes).
8591
///
86-
/// - All inner scopes are complete. (Use `innerScopeHandler` to
87-
/// complete them or bail-out).
88-
func computeInteriorLiveness(for definingValue: Value,
89-
_ context: FunctionPassContext,
90-
innerScopeHandler: InnerScopeHandler? = nil) -> InstructionRange {
91-
92-
assert(definingValue.ownership == .owned
93-
|| BeginBorrowValue(definingValue) != nil,
94-
"value must define an OSSA lifetime")
95-
96-
var range = InstructionRange(for: definingValue, context)
97-
98-
var visitor = InteriorUseWalker(definingValue: definingValue, context) {
99-
range.insert($0.instruction)
100-
return .continueWalk
101-
}
102-
defer { visitor.deinitialize() }
103-
visitor.innerScopeHandler = innerScopeHandler
104-
let success = visitor.visitUses()
105-
switch visitor.pointerStatus {
106-
case .nonEscaping:
92+
/// - All inner scopes are complete. (Use `innerScopeHandler` to complete them or bail-out).
93+
func computeInteriorLiveness(for definingValue: Value, _ context: FunctionPassContext,
94+
innerScopeHandler: InnerScopeHandler? = nil) -> InstructionRange {
95+
let result = InteriorLivenessResult.compute(for: definingValue, ignoreEscape: false, context)
96+
switch result.pointerStatus {
97+
case .nonescaping:
10798
break
108-
case let .escaping(operand):
99+
case let .escaping(operands):
109100
fatalError("""
110101
check findPointerEscape() before computing interior liveness.
111-
Pointer escape: \(operand.instruction)
102+
Pointer escape: \(operands[0].instruction)
112103
""")
113104
case let .unknown(operand):
114105
fatalError("Unrecognized SIL address user \(operand.instruction)")
115106
}
116-
assert(success == .continueWalk, "our visitor never fails")
117-
assert(visitor.unenclosedPhis.isEmpty, "missing adjacent phis")
118-
return range
107+
return result.range
108+
}
109+
110+
/// Compute known liveness and return a range, which the caller must deinitialize.
111+
///
112+
/// This computes a minimal liveness, ignoring pointer escaping uses.
113+
func computeKnownLiveness(for definingValue: Value, _ context: FunctionPassContext) -> InstructionRange {
114+
return InteriorLivenessResult.compute(for: definingValue, ignoreEscape: true, context).range
115+
}
116+
117+
/// If any interior pointer may escape, then record the first instance here. If 'ignoseEscape' is true, this
118+
/// immediately aborts the walk, so further instances are unavailable.
119+
///
120+
/// .escaping may either be a non-address operand with
121+
/// .pointerEscape ownership, or and address operand that escapes
122+
/// the address (address_to_pointer).
123+
///
124+
/// .unknown is an address operand whose user is unrecognized.
125+
enum InteriorPointerStatus: CustomDebugStringConvertible {
126+
case nonescaping
127+
case escaping(SingleInlineArray<Operand>)
128+
case unknown(Operand)
129+
130+
mutating func setEscaping(operand: Operand) {
131+
switch self {
132+
case .nonescaping:
133+
self = .escaping(SingleInlineArray(element: operand))
134+
case let .escaping(oldOperands):
135+
var newOperands = SingleInlineArray<Operand>()
136+
newOperands.append(contentsOf: oldOperands)
137+
newOperands.append(operand)
138+
self = .escaping(newOperands)
139+
case .unknown:
140+
break
141+
}
142+
}
143+
144+
var debugDescription: String {
145+
switch self {
146+
case .nonescaping:
147+
return "No pointer escape"
148+
case let .escaping(operands):
149+
return "Pointer escapes: " + operands.map({ "\($0)" }).joined(separator: "\n ")
150+
case let .unknown(operand):
151+
return "Unknown use: \(operand)"
152+
}
153+
}
154+
}
155+
156+
struct InteriorLivenessResult: CustomDebugStringConvertible {
157+
let success: WalkResult
158+
let range: InstructionRange
159+
let pointerStatus: InteriorPointerStatus
160+
161+
static func compute(for definingValue: Value, ignoreEscape: Bool = false,
162+
_ context: FunctionPassContext,
163+
innerScopeHandler: InnerScopeHandler? = nil) -> InteriorLivenessResult {
164+
165+
assert(definingValue.ownership == .owned || BeginBorrowValue(definingValue) != nil,
166+
"value must define an OSSA lifetime")
167+
168+
var range = InstructionRange(for: definingValue, context)
169+
170+
var visitor = InteriorUseWalker(definingValue: definingValue, ignoreEscape: ignoreEscape, context) {
171+
range.insert($0.instruction)
172+
return .continueWalk
173+
}
174+
defer { visitor.deinitialize() }
175+
visitor.innerScopeHandler = innerScopeHandler
176+
let success = visitor.visitUses()
177+
assert(visitor.unenclosedPhis.isEmpty, "missing adjacent phis")
178+
let result = InteriorLivenessResult(success: success, range: range, pointerStatus: visitor.pointerStatus)
179+
log("Interior liveness for: \(definingValue)\n\(result)")
180+
return result
181+
}
182+
183+
var debugDescription: String {
184+
"\(success)\n\(range)\n\(pointerStatus)"
185+
}
119186
}
120187

121188
/// Classify ownership uses. This reduces operand ownership to a
@@ -455,6 +522,7 @@ struct InteriorUseWalker {
455522
var context: Context { functionContext }
456523

457524
let definingValue: Value
525+
let ignoreEscape: Bool
458526
let useVisitor: (Operand) -> WalkResult
459527

460528
var innerScopeHandler: InnerScopeHandler? = nil
@@ -470,33 +538,20 @@ struct InteriorUseWalker {
470538

471539
var function: Function { definingValue.parentFunction }
472540

473-
/// If any interior pointer may escape, then record the first instance
474-
/// here. This immediately aborts the walk, so further instances are
475-
/// unavailable.
476-
///
477-
/// .escaping may either be a non-address operand with
478-
/// .pointerEscape ownership, or and address operand that escapes
479-
/// the address (address_to_pointer).
480-
///
481-
/// .unknown is an address operand whose user is unrecognized.
482-
enum InteriorPointerStatus {
483-
case nonEscaping
484-
case escaping(Operand)
485-
case unknown(Operand)
486-
}
487-
var pointerStatus: InteriorPointerStatus = .nonEscaping
541+
var pointerStatus: InteriorPointerStatus = .nonescaping
488542

489543
private var visited: ValueSet
490544

491545
mutating func deinitialize() {
492546
visited.deinitialize()
493547
}
494548

495-
init(definingValue: Value, _ context: FunctionPassContext,
549+
init(definingValue: Value, ignoreEscape: Bool, _ context: FunctionPassContext,
496550
visitor: @escaping (Operand) -> WalkResult) {
497551
assert(!definingValue.type.isAddress, "address values have no ownership")
498552
self.functionContext = context
499553
self.definingValue = definingValue
554+
self.ignoreEscape = ignoreEscape
500555
self.useVisitor = visitor
501556
self.visited = ValueSet(context)
502557
}
@@ -598,8 +653,8 @@ extension InteriorUseWalker: OwnershipUseVisitor {
598653
if useVisitor(operand) == .abortWalk {
599654
return .abortWalk
600655
}
601-
pointerStatus = .escaping(operand)
602-
return .abortWalk
656+
pointerStatus.setEscaping(operand: operand)
657+
return ignoreEscape ? .continueWalk : .abortWalk
603658
}
604659

605660
// Call the innerScopeHandler before visiting the scope-ending uses.
@@ -695,8 +750,8 @@ extension InteriorUseWalker: AddressUseVisitor {
695750
}
696751

697752
mutating func escapingAddressUse(of operand: Operand) -> WalkResult {
698-
pointerStatus = .escaping(operand)
699-
return .abortWalk
753+
pointerStatus.setEscaping(operand: operand)
754+
return ignoreEscape ? .continueWalk : .abortWalk
700755
}
701756

702757
mutating func unknownAddressUse(of operand: Operand) -> WalkResult {
@@ -894,7 +949,7 @@ let interiorLivenessTest = FunctionTest("interior_liveness_swift") {
894949
var range = InstructionRange(for: value, context)
895950
defer { range.deinitialize() }
896951

897-
var visitor = InteriorUseWalker(definingValue: value, context) {
952+
var visitor = InteriorUseWalker(definingValue: value, ignoreEscape: true, context) {
898953
range.insert($0.instruction)
899954
return .continueWalk
900955
}
@@ -903,10 +958,12 @@ let interiorLivenessTest = FunctionTest("interior_liveness_swift") {
903958
let success = visitor.visitUses()
904959

905960
switch visitor.pointerStatus {
906-
case .nonEscaping:
961+
case .nonescaping:
907962
break
908-
case let .escaping(operand):
909-
print("Pointer escape: \(operand.instruction)")
963+
case let .escaping(operands):
964+
for operand in operands {
965+
print("Pointer escape: \(operand.instruction)")
966+
}
910967
case let .unknown(operand):
911968
print("Unrecognized SIL address user \(operand.instruction)")
912969
}

test/SILOptimizer/ownership_liveness_unit.sil

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,6 @@ exit(%reborrow : @guaranteed $C, %phi : @guaranteed $D):
254254
// CHECK-LABEL: testInteriorRefElementEscape: interior_liveness_swift with: %0
255255
// CHECK: Interior liveness: %0 = argument of bb0 : $C
256256
// CHECK-NEXT: Pointer escape: %{{.*}} = address_to_pointer %{{.*}} : $*C to $Builtin.RawPointer
257-
// CHECK-NEXT: Incomplete liveness
258257
// CHECK-NEXT: begin: %{{.*}} = unchecked_ref_cast %0 : $C to $D
259258
// CHECK-NEXT: ends: %{{.*}} = address_to_pointer %{{.*}} : $*C to $Builtin.RawPointer
260259
// CHECK-NEXT: exits:

0 commit comments

Comments
 (0)