Skip to content

Commit 1576ba4

Browse files
authored
Merge pull request #79605 from atrick/fix-borrowedfrom
Fix BorrowedFromInst operand ownership.
2 parents fa86097 + 6e75813 commit 1576ba4

24 files changed

+1439
-920
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DestroyHoisting.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ private func optimize(value: Value, _ context: FunctionPassContext) {
9191
var hoistableDestroys = selectHoistableDestroys(of: value, context)
9292
defer { hoistableDestroys.deinitialize() }
9393

94-
var minimalLiverange = InstructionRange(withLiverangeOf: value, ignoring: hoistableDestroys, context)
94+
guard var minimalLiverange = InstructionRange(withLiverangeOf: value, ignoring: hoistableDestroys, context) else {
95+
return
96+
}
9597
defer { minimalLiverange.deinitialize() }
9698

9799
hoistDestroys(of: value, toEndOf: minimalLiverange, restrictingTo: &hoistableDestroys, context)
@@ -177,10 +179,10 @@ private func removeDestroys(
177179

178180
private extension InstructionRange {
179181

180-
init(withLiverangeOf initialDef: Value, ignoring ignoreDestroys: InstructionSet, _ context: FunctionPassContext)
182+
init?(withLiverangeOf initialDef: Value, ignoring ignoreDestroys: InstructionSet, _ context: FunctionPassContext)
181183
{
182184
var liverange = InstructionRange(for: initialDef, context)
183-
var visitor = InteriorUseWalker(definingValue: initialDef, ignoreEscape: true, visitInnerUses: false, context) {
185+
var visitor = InteriorUseWalker(definingValue: initialDef, ignoreEscape: false, visitInnerUses: true, context) {
184186
if !ignoreDestroys.contains($0.instruction) {
185187
liverange.insert($0.instruction)
186188
}
@@ -197,7 +199,10 @@ private extension InstructionRange {
197199
return .continueWalk
198200
}
199201

200-
_ = visitor.visitUses()
202+
guard visitor.visitUses() == .continueWalk else {
203+
liverange.deinitialize()
204+
return nil
205+
}
201206
self = liverange
202207
}
203208

SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,8 +554,8 @@ extension AddressOwnershipLiveRange {
554554
///
555555
/// For address values, use AccessBase.computeOwnershipRange.
556556
///
557-
/// FIXME: This should use computeLinearLiveness rather than computeKnownLiveness as soon as lifetime completion
558-
/// runs immediately after SILGen.
557+
/// FIXME: This should use computeLinearLiveness rather than computeKnownLiveness as soon as complete OSSA lifetimes
558+
/// are verified.
559559
private static func computeValueLiveRange(of value: Value, _ context: FunctionPassContext)
560560
-> AddressOwnershipLiveRange? {
561561
switch value.ownership {

SwiftCompilerSources/Sources/Optimizer/Utilities/BorrowUtils.swift

Lines changed: 127 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -134,27 +134,26 @@ import SIL
134134

135135
/// A scoped instruction that borrows one or more operands.
136136
///
137-
/// If this instruction produces a borrowed value, then
138-
/// BeginBorrowValue(resultOf: self) != nil.
137+
/// If this instruction produces a borrowed value, then BeginBorrowValue(resultOf: self) != nil.
139138
///
140-
/// This does not include instructions like `apply` and `try_apply` that
141-
/// instantaneously borrow a value from the caller.
139+
/// This does not include instructions like `apply` and `try_apply` that instantaneously borrow a value from the caller.
142140
///
143-
/// This does not include `load_borrow` because it borrows a memory
144-
/// location, not the value of its operand.
141+
/// This does not include `load_borrow` because it borrows a memory location, not the value of its operand.
145142
///
146143
/// Note: This must handle all instructions with a .borrow operand ownership.
147144
///
148-
/// Note: mark_dependence is a BorrowingInstruction because it creates
149-
/// a borrow scope for its base operand. Its result, however, is not a
150-
/// BeginBorrowValue. It is instead a ForwardingInstruction relative
151-
/// to its value operand.
145+
/// Note: borrowed_from is a BorrowingInstruction because it creates a borrow scope for its enclosing operands. Its
146+
/// result, however, is only a BeginBorrowValue (.reborrow) if it forwards a reborrow phi. Otherwise, it simply forwards
147+
/// a guaranteed value and does not introduce a separate borrow scope.
152148
///
153-
/// TODO: replace BorrowIntroducingInstruction
149+
/// Note: mark_dependence [nonescaping] is a BorrowingInstruction because it creates a borrow scope for its base
150+
/// operand. Its result, however, is not a BeginBorrowValue. Instead it is a ForwardingInstruction relative to its value
151+
/// operand.
154152
///
155-
/// TODO: Add non-escaping MarkDependence.
153+
/// TODO: replace BorrowIntroducingInstruction with this.
156154
enum BorrowingInstruction : CustomStringConvertible, Hashable {
157155
case beginBorrow(BeginBorrowInst)
156+
case borrowedFrom(BorrowedFromInst)
158157
case storeBorrow(StoreBorrowInst)
159158
case beginApply(BeginApplyInst)
160159
case partialApply(PartialApplyInst)
@@ -165,13 +164,18 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
165164
switch inst {
166165
case let bbi as BeginBorrowInst:
167166
self = .beginBorrow(bbi)
167+
case let bfi as BorrowedFromInst:
168+
self = .borrowedFrom(bfi)
168169
case let sbi as StoreBorrowInst:
169170
self = .storeBorrow(sbi)
170171
case let bai as BeginApplyInst:
171172
self = .beginApply(bai)
172173
case let pai as PartialApplyInst where !pai.mayEscape:
173174
self = .partialApply(pai)
174175
case let mdi as MarkDependenceInst:
176+
guard mdi.isNonEscaping else {
177+
return nil
178+
}
175179
self = .markDependence(mdi)
176180
case let bi as BuiltinInst
177181
where bi.id == .StartAsyncLetWithLocalBuffer:
@@ -185,6 +189,8 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
185189
switch self {
186190
case .beginBorrow(let bbi):
187191
return bbi
192+
case .borrowedFrom(let bfi):
193+
return bfi
188194
case .storeBorrow(let sbi):
189195
return sbi
190196
case .beginApply(let bai):
@@ -198,65 +204,88 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
198204
}
199205
}
200206

201-
/// Visit the operands that end the local borrow scope.
202-
///
203-
/// Note: When this instruction's result is BeginBorrowValue the
204-
/// scopeEndingOperand may include reborrows. To find all uses that
205-
/// contribute to liveness, the caller needs to determine whether an
206-
/// incoming value dominates or is consumed by an outer adjacent
207-
/// phi. See InteriorLiveness.
208-
///
209-
/// FIXME: To generate conservatively correct liveness, this should return
210-
/// .abortWalk if this is a mark_dependence and the scope-ending use is not
211-
/// the last in the function (e.g. a store rather than a destroy or return).
212-
/// The client needs to use LifetimeDependenceDefUseWalker to do better.
213-
///
214-
/// TODO: to handle reborrow-extended uses, migrate ExtendedLiveness
215-
/// to SwiftCompilerSources.
216-
///
217-
/// TODO: Handle .partialApply and .markDependence forwarded uses
218-
/// that are phi operands. Currently, partial_apply [on_stack]
219-
/// and mark_dependence [nonescaping] cannot be cloned, so walking
220-
/// through the phi safely returns dominated scope-ending operands.
221-
/// Instead, this could report the phi as a scope-ending use, and
222-
/// the client could decide whether to walk through them or to
223-
/// construct reborrow-extended liveness.
224-
///
225-
/// TODO: For instructions that are not a BeginBorrowValue, verify
226-
/// that scope ending instructions exist on all paths. These
227-
/// instructions should be complete after SILGen and never cloned to
228-
/// produce phis.
229-
func visitScopeEndingOperands(_ context: Context,
230-
visitor: @escaping (Operand) -> WalkResult)
231-
-> WalkResult {
207+
var innerValue: Value? {
208+
if let dependent = dependentValue {
209+
return dependent
210+
}
211+
return scopedValue
212+
}
213+
214+
var dependentValue: Value? {
215+
switch self {
216+
case .borrowedFrom(let bfi):
217+
let phi = bfi.borrowedPhi
218+
if phi.isReborrow {
219+
return nil
220+
}
221+
return phi.value
222+
case .markDependence(let mdi):
223+
if mdi.hasScopedLifetime {
224+
return nil
225+
}
226+
return mdi
227+
case .beginBorrow, .storeBorrow, .beginApply, .partialApply, .startAsyncLet:
228+
return nil
229+
}
230+
}
231+
232+
/// If this is valid, then visitScopeEndingOperands succeeds.
233+
var scopedValue: Value? {
232234
switch self {
233235
case .beginBorrow, .storeBorrow:
234-
let svi = instruction as! SingleValueInstruction
235-
return svi.uses.filterUsers(ofType: EndBorrowInst.self).walk {
236-
visitor($0)
236+
return instruction as! SingleValueInstruction
237+
case let .borrowedFrom(bfi):
238+
let phi = bfi.borrowedPhi
239+
guard phi.isReborrow else {
240+
return nil
237241
}
242+
return phi.value
238243
case .beginApply(let bai):
239-
return bai.token.uses.walk { return visitor($0) }
240-
case .partialApply, .markDependence:
241-
let svi = instruction as! SingleValueInstruction
242-
assert(svi.ownership == .owned)
243-
return visitForwardedUses(introducer: svi, context) {
244-
switch $0 {
245-
case let .operand(operand):
246-
if operand.endsLifetime {
247-
return visitor(operand)
248-
}
249-
return .continueWalk
250-
case let .deadValue(_, operand):
251-
if let operand = operand {
252-
assert(!operand.endsLifetime,
253-
"a dead forwarding instruction cannot end a lifetime")
254-
}
255-
return .continueWalk
256-
}
244+
return bai.token
245+
case .partialApply(let pai):
246+
// We currently assume that closure lifetimes are always complete (destroyed on all paths).
247+
return pai
248+
case .markDependence(let mdi):
249+
guard mdi.hasScopedLifetime else {
250+
return nil
257251
}
252+
return mdi
258253
case .startAsyncLet(let builtin):
259-
return builtin.uses.walk {
254+
return builtin
255+
}
256+
}
257+
258+
/// Visit the operands that end the local borrow scope.
259+
///
260+
/// Returns .abortWalk if the borrow scope cannot be determined from lifetime-ending uses. For example:
261+
/// - borrowed_from where 'borrowedPhi.isReborrow == false'
262+
/// - non-owned mark_dependence [nonescaping]
263+
/// - owned mark_dependence [nonescaping] with a ~Escapable result (LifetimeDependenceDefUseWalker is needed).
264+
///
265+
/// Note: .partialApply and .markDependence cannot currently be forwarded to phis because partial_apply [on_stack] and
266+
/// mark_dependence [nonescaping] cannot be cloned. Walking through the phi therefore safely returns dominated
267+
/// scope-ending operands. Handling phis here requires the equivalent of borrowed_from for owned values.
268+
///
269+
/// TODO: For instructions that are not a BeginBorrowValue, verify that scope ending instructions exist on all
270+
/// paths. These instructions should be complete after SILGen and never cloned to produce phis.
271+
func visitScopeEndingOperands(_ context: Context, visitor: @escaping (Operand) -> WalkResult) -> WalkResult {
272+
guard let val = scopedValue else {
273+
return .abortWalk
274+
}
275+
switch self {
276+
case .beginBorrow, .storeBorrow:
277+
return visitEndBorrows(value: val, context, visitor)
278+
case .borrowedFrom:
279+
return visitEndBorrows(value: val, context, visitor)
280+
case .beginApply:
281+
return val.uses.walk { return visitor($0) }
282+
case .partialApply:
283+
// We currently assume that closure lifetimes are always complete (destroyed on all paths).
284+
return visitOwnedDependent(value: val, context, visitor)
285+
case .markDependence:
286+
return visitOwnedDependent(value: val, context, visitor)
287+
case .startAsyncLet:
288+
return val.uses.walk {
260289
if let builtinUser = $0.instruction as? BuiltinInst,
261290
builtinUser.id == .EndAsyncLetLifetime {
262291
return visitor($0)
@@ -265,6 +294,34 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
265294
}
266295
}
267296
}
297+
}
298+
299+
extension BorrowingInstruction {
300+
private func visitEndBorrows(value: Value, _ context: Context, _ visitor: @escaping (Operand) -> WalkResult)
301+
-> WalkResult {
302+
return value.lookThroughBorrowedFromUser.uses.filterUsers(ofType: EndBorrowInst.self).walk {
303+
visitor($0)
304+
}
305+
}
306+
307+
private func visitOwnedDependent(value: Value, _ context: Context, _ visitor: @escaping (Operand) -> WalkResult)
308+
-> WalkResult {
309+
return visitForwardedUses(introducer: value, context) {
310+
switch $0 {
311+
case let .operand(operand):
312+
if operand.endsLifetime {
313+
return visitor(operand)
314+
}
315+
return .continueWalk
316+
case let .deadValue(_, operand):
317+
if let operand = operand {
318+
assert(!operand.endsLifetime,
319+
"a dead forwarding instruction cannot end a lifetime")
320+
}
321+
return .continueWalk
322+
}
323+
}
324+
}
268325

269326
var description: String { instruction.description }
270327
}
@@ -341,9 +398,12 @@ enum BeginBorrowValue {
341398
init?(resultOf borrowInstruction: BorrowingInstruction) {
342399
switch borrowInstruction {
343400
case let .beginBorrow(beginBorrow):
344-
self = BeginBorrowValue(beginBorrow)!
401+
self.init(beginBorrow)
402+
case let .borrowedFrom(borrowedFrom):
403+
// only returns non-nil if borrowedPhi is a reborrow
404+
self.init(borrowedFrom.borrowedPhi.value)
345405
case let .beginApply(beginApply):
346-
self = BeginBorrowValue(beginApply.token)!
406+
self.init(beginApply.token)
347407
case .storeBorrow, .partialApply, .markDependence, .startAsyncLet:
348408
return nil
349409
}

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,12 +771,14 @@ extension LifetimeDependenceDefUseWalker {
771771
switch borrowInst {
772772
case let .beginBorrow(bbi):
773773
return walkDownUses(of: bbi, using: operand)
774+
case let .borrowedFrom(bfi):
775+
return walkDownUses(of: bfi, using: operand)
774776
case let .storeBorrow(sbi):
775777
return walkDownAddressUses(of: sbi)
776778
case .beginApply:
777779
// Skip the borrow scope; the type system enforces non-escapable
778780
// arguments.
779-
return visitInnerBorrowUses(of: borrowInst)
781+
return visitInnerBorrowUses(of: borrowInst, operand: operand)
780782
case .partialApply, .markDependence:
781783
fatalError("OwnershipUseVisitor should bypass partial_apply [on_stack] "
782784
+ "and mark_dependence [nonescaping]")

0 commit comments

Comments
 (0)