@@ -134,27 +134,26 @@ import SIL
134
134
135
135
/// A scoped instruction that borrows one or more operands.
136
136
///
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.
139
138
///
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.
142
140
///
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.
145
142
///
146
143
/// Note: This must handle all instructions with a .borrow operand ownership.
147
144
///
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.
152
148
///
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.
154
152
///
155
- /// TODO: Add non-escaping MarkDependence .
153
+ /// TODO: replace BorrowIntroducingInstruction with this .
156
154
enum BorrowingInstruction : CustomStringConvertible , Hashable {
157
155
case beginBorrow( BeginBorrowInst )
156
+ case borrowedFrom( BorrowedFromInst )
158
157
case storeBorrow( StoreBorrowInst )
159
158
case beginApply( BeginApplyInst )
160
159
case partialApply( PartialApplyInst )
@@ -165,13 +164,18 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
165
164
switch inst {
166
165
case let bbi as BeginBorrowInst :
167
166
self = . beginBorrow( bbi)
167
+ case let bfi as BorrowedFromInst :
168
+ self = . borrowedFrom( bfi)
168
169
case let sbi as StoreBorrowInst :
169
170
self = . storeBorrow( sbi)
170
171
case let bai as BeginApplyInst :
171
172
self = . beginApply( bai)
172
173
case let pai as PartialApplyInst where !pai. mayEscape:
173
174
self = . partialApply( pai)
174
175
case let mdi as MarkDependenceInst :
176
+ guard mdi. isNonEscaping else {
177
+ return nil
178
+ }
175
179
self = . markDependence( mdi)
176
180
case let bi as BuiltinInst
177
181
where bi. id == . StartAsyncLetWithLocalBuffer:
@@ -185,6 +189,8 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
185
189
switch self {
186
190
case . beginBorrow( let bbi) :
187
191
return bbi
192
+ case . borrowedFrom( let bfi) :
193
+ return bfi
188
194
case . storeBorrow( let sbi) :
189
195
return sbi
190
196
case . beginApply( let bai) :
@@ -198,65 +204,88 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
198
204
}
199
205
}
200
206
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 ? {
232
234
switch self {
233
235
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
237
241
}
242
+ return phi. value
238
243
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
257
251
}
252
+ return mdi
258
253
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 {
260
289
if let builtinUser = $0. instruction as? BuiltinInst ,
261
290
builtinUser. id == . EndAsyncLetLifetime {
262
291
return visitor ( $0)
@@ -265,6 +294,34 @@ enum BorrowingInstruction : CustomStringConvertible, Hashable {
265
294
}
266
295
}
267
296
}
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
+ }
268
325
269
326
var description : String { instruction. description }
270
327
}
@@ -341,9 +398,12 @@ enum BeginBorrowValue {
341
398
init ? ( resultOf borrowInstruction: BorrowingInstruction ) {
342
399
switch borrowInstruction {
343
400
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)
345
405
case let . beginApply( beginApply) :
346
- self = BeginBorrowValue ( beginApply. token) !
406
+ self . init ( beginApply. token)
347
407
case . storeBorrow, . partialApply, . markDependence, . startAsyncLet:
348
408
return nil
349
409
}
0 commit comments