@@ -215,10 +215,11 @@ private func extendScopes(dependence: LifetimeDependence,
215
215
localReachabilityCache, context) else {
216
216
continue
217
217
}
218
- defer { useRange. deinitialize ( ) }
219
-
220
- scopeExtension. extend ( over: & useRange, context)
221
218
219
+ // deinitializes 'useRange'
220
+ guard scopeExtension. tryExtendScopes ( over: & useRange, context) else {
221
+ continue
222
+ }
222
223
if scopeExtension. dependsOnCaller, let arg = scopeExtension. dependsOnArg {
223
224
dependsOnArgs. push ( arg)
224
225
}
@@ -249,15 +250,15 @@ private struct ScopeExtension {
249
250
250
251
private extension LifetimeDependence . Scope {
251
252
/// The instruction that introduces an extendable scope. This returns a non-nil scope introducer for
252
- /// ScopeExtension.nestedScopes.
253
- var extendableBegin : Instruction ? {
253
+ /// each scope in ScopeExtension.nestedScopes.
254
+ var extendableBegin : ScopedInstruction ? {
254
255
switch self {
255
256
case let . access( beginAccess) :
256
257
return beginAccess
257
258
case let . borrowed( beginBorrow) :
258
- return beginBorrow. value. definingInstruction!
259
+ return beginBorrow. value. definingInstruction as? ScopedInstruction
259
260
case let . yield( yieldedValue) :
260
- return yieldedValue. definingInstruction!
261
+ return yieldedValue. definingInstruction as? ScopedInstruction
261
262
case let . initialized( initializer) :
262
263
switch initializer {
263
264
case let . store( initializingStore: store, initialAddress: _) :
@@ -274,24 +275,32 @@ private extension LifetimeDependence.Scope {
274
275
}
275
276
}
276
277
277
- /// Find any nested scopes that may be extended.
278
+ /// Precondition: the 'self' scope encloses a dependent value. 'innerScopes' are the extendable scopes enclosed by
279
+ /// 'self' that also enclose the dependent value.
278
280
///
279
- /// Return 'nil' if 'self' is not extendable.
281
+ /// Gather the list of ScopeExtensions. Each extension is a list of scopes, including 'innerScopes', 'self' and,
282
+ /// recursively, any of its enclosing scopes that are extendable. We may have multiple extensions because a scope
283
+ /// introducer may itself depend on multiple operands.
280
284
///
281
- /// TODO: handle trivial variable scopes
285
+ /// Return 'nil' if 'self' is not extendable.
282
286
func gatherExtensions( innerScopes: SingleInlineArray < LifetimeDependence . Scope > ? = nil , _ context: FunctionPassContext )
283
287
-> SingleInlineArray < ScopeExtension > ? {
284
288
289
+ // Note: LifetimeDependence.Scope.extend() will assume that all inner scopes begin with a ScopedInstruction.
285
290
var innerScopes = innerScopes ?? SingleInlineArray ( )
286
291
switch self {
287
292
case let . access( beginAccess) :
288
- let accessExtension = gatherAccessExtension ( beginAccess: beginAccess, innerScopes: & innerScopes)
289
- return SingleInlineArray ( element : accessExtension )
293
+ return gatherAccessExtension ( beginAccess: beginAccess, innerScopes: & innerScopes, context )
294
+
290
295
case let . borrowed( beginBorrow) :
291
- return gatherBorrowExtension ( borrowedValue: beginBorrow. baseOperand!. value, innerScopes: & innerScopes, context)
296
+ // begin_borrow is extendable, so push this scope.
297
+ innerScopes. push ( self )
298
+ return gatherBorrowExtension ( borrowedValue: beginBorrow. baseOperand!. value, innerScopes: innerScopes, context)
292
299
293
300
case let . yield( yieldedValue) :
301
+ // A yield is extendable, so push this scope.
294
302
innerScopes. push ( self )
303
+ // Create a separate ScopeExtension for each operand that the yielded value depends on.
295
304
var extensions = SingleInlineArray < ScopeExtension > ( )
296
305
let applySite = yieldedValue. definingInstruction as! BeginApplyInst
297
306
for operand in applySite. parameterOperands {
@@ -306,7 +315,9 @@ private extension LifetimeDependence.Scope {
306
315
switch initializer {
307
316
case let . store( initializingStore: store, initialAddress: _) :
308
317
if let sb = store as? StoreBorrowInst {
309
- return gatherBorrowExtension ( borrowedValue: sb. source, innerScopes: & innerScopes, context)
318
+ innerScopes. push ( self )
319
+ // Only follow the source of the store_borrow. The address is always an alloc_stack without any access scope.
320
+ return gatherBorrowExtension ( borrowedValue: sb. source, innerScopes: innerScopes, context)
310
321
}
311
322
return nil
312
323
case . argument, . yield:
@@ -344,7 +355,8 @@ private extension LifetimeDependence.Scope {
344
355
// all nested accesses were extended to the return statement, it is valid to logically combine them into a single
345
356
// access for the purpose of diagnostinc lifetime dependence.
346
357
func gatherAccessExtension( beginAccess: BeginAccessInst ,
347
- innerScopes: inout SingleInlineArray < LifetimeDependence . Scope > ) -> ScopeExtension {
358
+ innerScopes: inout SingleInlineArray < LifetimeDependence . Scope > ,
359
+ _ context: FunctionPassContext ) -> SingleInlineArray < ScopeExtension > {
348
360
// Finding the access base also finds all intermediate nested scopes; there is no need to recursively call
349
361
// gatherExtensions().
350
362
let accessBaseAndScopes = beginAccess. accessBaseWithScopes
@@ -368,22 +380,28 @@ private extension LifetimeDependence.Scope {
368
380
}
369
381
if case let . argument( arg) = accessBaseAndScopes. base {
370
382
if isCompatibleAccess && beginAccess. accessKind. isCompatible ( with: arg. convention) {
371
- return ScopeExtension ( owner: outerBeginAccess. address, nestedScopes: innerScopes, dependsOnArg: arg)
383
+ let scopes = ScopeExtension ( owner: outerBeginAccess. address, nestedScopes: innerScopes, dependsOnArg: arg)
384
+ return SingleInlineArray ( element: scopes)
372
385
}
373
386
}
374
- return ScopeExtension ( owner: outerBeginAccess, nestedScopes: innerScopes, dependsOnArg: nil )
387
+ /// Recurse in case of indirect yields.
388
+ let enclosingScope = LifetimeDependence . Scope ( base: outerBeginAccess. address, context)
389
+ if let extensions = enclosingScope. gatherExtensions ( innerScopes: innerScopes, context) {
390
+ return extensions
391
+ }
392
+ // When the owner is an address, the owner's scope is considered the availability of its access base starting at the
393
+ // position of innerScopes.last.
394
+ let scopes = ScopeExtension ( owner: outerBeginAccess. address, nestedScopes: innerScopes, dependsOnArg: nil )
395
+ return SingleInlineArray ( element: scopes)
375
396
}
376
397
377
398
func gatherBorrowExtension( borrowedValue: Value ,
378
- innerScopes: inout SingleInlineArray < LifetimeDependence . Scope > ,
399
+ innerScopes: SingleInlineArray < LifetimeDependence . Scope > ,
379
400
_ context: FunctionPassContext )
380
401
-> SingleInlineArray < ScopeExtension > {
381
402
382
403
let enclosingScope = LifetimeDependence . Scope ( base: borrowedValue, context)
383
- innerScopes. push ( self )
384
- var innerBorrowScopes = innerScopes
385
- innerBorrowScopes. push ( enclosingScope)
386
- if let extensions = enclosingScope. gatherExtensions ( innerScopes: innerBorrowScopes, context) {
404
+ if let extensions = enclosingScope. gatherExtensions ( innerScopes: innerScopes, context) {
387
405
return extensions
388
406
}
389
407
// This is the outermost scope to be extended because gatherExtensions did not find an enclosing scope.
@@ -429,7 +447,7 @@ extension ScopeExtension {
429
447
{
430
448
if owner. type. isAddress {
431
449
// Get the range of the accessBase lifetime at the point where the outermost extendable scope begins.
432
- if let range = AddressOwnershipLiveRange . compute ( for: owner, at: nestedScopes. last!. extendableBegin!,
450
+ if let range = AddressOwnershipLiveRange . compute ( for: owner, at: nestedScopes. last!. extendableBegin!. instruction ,
433
451
localReachabilityCache, context) {
434
452
return . addressRange( range)
435
453
}
@@ -457,7 +475,7 @@ private func computeDependentUseRange(of value: Value, within scopeExtension: in
457
475
defer { ownershipRange. deinitialize ( ) }
458
476
459
477
// The innermost scope that must be extended must dominate all uses.
460
- var useRange = InstructionRange ( begin: scopeExtension. innerScope. extendableBegin!, context)
478
+ var useRange = InstructionRange ( begin: scopeExtension. innerScope. extendableBegin!. instruction , context)
461
479
let function = value. parentFunction
462
480
var walker = LifetimeDependentUseWalker ( function, localReachabilityCache, context) {
463
481
// Do not extend the useRange past the ownershipRange.
@@ -487,44 +505,64 @@ private func computeDependentUseRange(of value: Value, within scopeExtension: in
487
505
488
506
// Extend nested scopes across a use-range within their owner's range.
489
507
extension ScopeExtension {
490
- func extend( over useRange: inout InstructionRange , _ context: some MutatingContext ) {
491
-
492
- // Prepare to extend each scope.
493
- var scopesToExtend = SingleInlineArray < LifetimeDependence . Scope > ( )
508
+ // Prepare to extend each scope.
509
+ func tryExtendScopes( over useRange: inout InstructionRange , _ context: some MutatingContext ) -> Bool {
510
+ var extendedUseRange = InstructionRange ( begin: useRange. begin!, ends: useRange. ends, context)
511
+ // Insert the first instruction of the exit blocks to mimic 'useRange'. This is innacurate, but it produces the same
512
+ // result for canExtend() check below, which only checks reachability of end_apply.
513
+ extendedUseRange. insert ( contentsOf: useRange. exits)
494
514
for innerScope in nestedScopes {
495
- guard let beginInst = innerScope. extendableBegin as? ScopedInstruction else {
515
+ guard let beginInst = innerScope. extendableBegin else {
496
516
fatalError ( " all nested scopes must have a scoped begin instruction " )
497
517
}
498
- // Extend 'useRange ' to to cover this scope's end instructions. The extended scope must at least cover the
518
+ // Extend 'extendedUseRange ' to to cover this scope's end instructions. The extended scope must at least cover the
499
519
// original scope because the original scope may protect other operations.
500
- var requiresExtension = false
501
- for endInst in beginInst. endInstructions {
502
- if useRange. contains ( endInst) {
503
- // If any end instruction is inside the new range, then all end instructions must be rewritten.
504
- requiresExtension = true
505
- } else {
506
- // Update 'range' with the current scope-ending instructions.
507
- useRange. insert ( endInst)
508
- }
509
- }
510
- if !requiresExtension {
511
- break
512
- }
513
- if !innerScope. canExtend ( over: & useRange, context) {
520
+ extendedUseRange. insert ( contentsOf: beginInst. endInstructions)
521
+ if !innerScope. canExtend ( over: & extendedUseRange, context) {
514
522
// Scope ending instructions cannot be inserted at the 'range' boundary. Ignore all nested scopes.
515
523
//
516
524
// Note: We could still extend previously prepared inner scopes up to this 'innerScope'. To do that, we would
517
525
// need to repeat the steps above: treat 'innerScope' as the new owner, and recompute 'useRange'. But this
518
526
// scenario could only happen with nested coroutine, where the range boundary is reachable from the outer
519
527
// coroutine's EndApply and AbortApply--it is vanishingly unlikely if not impossible.
520
- return
528
+ return false
521
529
}
522
- scopesToExtend. push ( innerScope)
523
530
}
531
+ extendedUseRange. deinitialize ( )
532
+ // extend(over:) must receive the original unmodified 'useRange'.
533
+ extend ( over: & useRange, context)
534
+ return true
535
+ }
536
+
537
+ // Extend the scopes that actually required extension.
538
+ //
539
+ // Consumes 'useRange'
540
+ private func extend( over useRange: inout InstructionRange , _ context: some MutatingContext ) {
541
+ var deadInsts = [ Instruction] ( )
542
+ for innerScope in nestedScopes {
543
+ guard let beginInst = innerScope. extendableBegin else {
544
+ fatalError ( " all nested scopes must have a scoped begin instruction " )
545
+ }
546
+ let mustExtend = beginInst. endInstructions. contains ( where: { useRange. contains ( $0) } )
524
547
525
- // Extend the scopes that actually required extension.
526
- for innerScope in scopesToExtend {
527
- innerScope. extend ( over: & useRange, context)
548
+ // Extend 'useRange' to to cover this scope's end instructions. 'useRange' cannot be extended until the
549
+ // inner scopes have been extended.
550
+ for endInst in beginInst. endInstructions {
551
+ useRange. insert ( endInst)
552
+ }
553
+ if mustExtend {
554
+ deadInsts += innerScope. extend ( over: & useRange, context)
555
+ }
556
+ // Continue checking enclosing scopes for extension even if 'mustExtend' is false. Multiple ScopeExtensions may
557
+ // share the same inner scope, so this inner scope may already have been extended while handling a previous
558
+ // ScopeExtension. Nonetheless, some enclosing scopes may still require extension. This only happens when a
559
+ // yielded value depends on multiple begin_apply operands.
560
+ }
561
+ // 'useRange' is invalid as soon as instructions are deleted.
562
+ useRange. deinitialize ( )
563
+ // Delete original end instructions.
564
+ for deadInst in deadInsts {
565
+ context. erase ( instruction: deadInst)
528
566
}
529
567
}
530
568
}
@@ -561,9 +599,9 @@ private extension LifetimeDependence.Scope {
561
599
}
562
600
}
563
601
564
- /// Extend this scope over the 'range' boundary.
565
- func extend( over range: inout InstructionRange , _ context: some MutatingContext ) {
566
- guard let beginInst = extendableBegin as? ScopedInstruction else {
602
+ /// Extend this scope over the 'range' boundary. Return the old scope ending instructions to be deleted.
603
+ func extend( over range: inout InstructionRange , _ context: some MutatingContext ) -> [ Instruction ] {
604
+ guard let beginInst = extendableBegin else {
567
605
fatalError ( " all nested scoped must have a scoped begin instruction " )
568
606
}
569
607
// Collect the original end instructions and extend the range to to cover them. The resulting access scope
@@ -574,11 +612,7 @@ private extension LifetimeDependence.Scope {
574
612
endInsts. append ( end)
575
613
}
576
614
insertBoundaryEnds ( range: & range, context)
577
-
578
- // Delete original end instructions
579
- for endInst in endInsts {
580
- context. erase ( instruction: endInst)
581
- }
615
+ return endInsts
582
616
}
583
617
584
618
/// Create new scope-ending instructions at the boundary of 'range'.
@@ -590,44 +624,46 @@ private extension LifetimeDependence.Scope {
590
624
// function argument.
591
625
let builder = Builder ( before: end, location: location, context)
592
626
// Insert newEnd so that this scope will be nested in any outer scopes.
593
- range. insert ( createEndInstruction ( builder, context) ! )
627
+ range. insert ( createEndInstruction ( builder, context) )
594
628
continue
595
629
}
596
630
Builder . insert ( after: end, location: location, context) {
597
- range. insert ( createEndInstruction ( $0, context) ! )
631
+ range. insert ( createEndInstruction ( $0, context) )
598
632
}
599
633
}
600
634
for exitInst in range. exits {
601
635
let location = exitInst. location. autoGenerated
602
636
let builder = Builder ( before: exitInst, location: location, context)
603
- range. insert ( createEndInstruction ( builder, context) ! )
637
+ range. insert ( createEndInstruction ( builder, context) )
604
638
}
605
639
}
606
640
607
641
/// Create a scope-ending instruction at 'builder's insertion point.
608
- func createEndInstruction( _ builder: Builder , _ context: some Context ) -> Instruction ? {
642
+ func createEndInstruction( _ builder: Builder , _ context: some Context ) -> Instruction {
609
643
switch self {
610
644
case let . access( beginAccess) :
611
645
return builder. createEndAccess ( beginAccess: beginAccess)
612
646
case let . borrowed( beginBorrow) :
613
647
return builder. createEndBorrow ( of: beginBorrow. value)
614
648
case let . yield( yieldedValue) :
615
649
let beginApply = yieldedValue. definingInstruction as! BeginApplyInst
616
- return beginApply. createEnd ( builder, context)
650
+ // createEnd() returns non-nil because beginApply.endReaches() was checked by canExtend()
651
+ return beginApply. createEnd ( builder, context) !
617
652
case let . initialized( initializer) :
618
653
switch initializer {
619
654
case let . store( initializingStore: store, initialAddress: _) :
620
655
if let sb = store as? StoreBorrowInst {
621
656
return builder. createEndBorrow ( of: sb)
622
657
}
623
- return nil
658
+ break
624
659
case . argument, . yield:
625
660
// TODO: extend indirectly yielded scopes.
626
- return nil
661
+ break
627
662
}
628
663
default :
629
- return nil
664
+ break
630
665
}
666
+ fatalError ( " Unsupported scoped extension: \( self ) " )
631
667
}
632
668
}
633
669
0 commit comments