Skip to content

Commit b3f401d

Browse files
committed
LifetimeDependenceScopeFixup: handle indirect yields & fix bugs
Handle coroutines that yield an address. Fix bugs involving extension of multiple nested scopes that depend on multiple coroutine operands.
1 parent a0afb18 commit b3f401d

File tree

4 files changed

+174
-78
lines changed

4 files changed

+174
-78
lines changed

SwiftCompilerSources/Sources/Optimizer/DataStructures/InstructionRange.swift

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ struct InstructionRange : CustomStringConvertible, NoReflectionChildren {
5353
self.inExclusiveRange.insert(beginInst)
5454
}
5555

56+
// Note: 'ends' are simply the instructions to insert in the range. 'self.ends' might not return the same sequence
57+
// as this 'ends' argument because 'self.ends' will not include block exits.
5658
init<S: Sequence>(begin beginInst: Instruction, ends: S, _ context: some Context) where S.Element: Instruction {
5759
self = InstructionRange(begin: beginInst, context)
5860
insert(contentsOf: ends)

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

+101-65
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,11 @@ private func extendScopes(dependence: LifetimeDependence,
215215
localReachabilityCache, context) else {
216216
continue
217217
}
218-
defer { useRange.deinitialize() }
219-
220-
scopeExtension.extend(over: &useRange, context)
221218

219+
// deinitializes 'useRange'
220+
guard scopeExtension.tryExtendScopes(over: &useRange, context) else {
221+
continue
222+
}
222223
if scopeExtension.dependsOnCaller, let arg = scopeExtension.dependsOnArg {
223224
dependsOnArgs.push(arg)
224225
}
@@ -249,15 +250,15 @@ private struct ScopeExtension {
249250

250251
private extension LifetimeDependence.Scope {
251252
/// 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? {
254255
switch self {
255256
case let .access(beginAccess):
256257
return beginAccess
257258
case let .borrowed(beginBorrow):
258-
return beginBorrow.value.definingInstruction!
259+
return beginBorrow.value.definingInstruction as? ScopedInstruction
259260
case let .yield(yieldedValue):
260-
return yieldedValue.definingInstruction!
261+
return yieldedValue.definingInstruction as? ScopedInstruction
261262
case let .initialized(initializer):
262263
switch initializer {
263264
case let .store(initializingStore: store, initialAddress: _):
@@ -274,24 +275,32 @@ private extension LifetimeDependence.Scope {
274275
}
275276
}
276277

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.
278280
///
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.
280284
///
281-
/// TODO: handle trivial variable scopes
285+
/// Return 'nil' if 'self' is not extendable.
282286
func gatherExtensions(innerScopes: SingleInlineArray<LifetimeDependence.Scope>? = nil, _ context: FunctionPassContext)
283287
-> SingleInlineArray<ScopeExtension>? {
284288

289+
// Note: LifetimeDependence.Scope.extend() will assume that all inner scopes begin with a ScopedInstruction.
285290
var innerScopes = innerScopes ?? SingleInlineArray()
286291
switch self {
287292
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+
290295
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)
292299

293300
case let .yield(yieldedValue):
301+
// A yield is extendable, so push this scope.
294302
innerScopes.push(self)
303+
// Create a separate ScopeExtension for each operand that the yielded value depends on.
295304
var extensions = SingleInlineArray<ScopeExtension>()
296305
let applySite = yieldedValue.definingInstruction as! BeginApplyInst
297306
for operand in applySite.parameterOperands {
@@ -306,7 +315,9 @@ private extension LifetimeDependence.Scope {
306315
switch initializer {
307316
case let .store(initializingStore: store, initialAddress: _):
308317
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)
310321
}
311322
return nil
312323
case .argument, .yield:
@@ -344,7 +355,8 @@ private extension LifetimeDependence.Scope {
344355
// all nested accesses were extended to the return statement, it is valid to logically combine them into a single
345356
// access for the purpose of diagnostinc lifetime dependence.
346357
func gatherAccessExtension(beginAccess: BeginAccessInst,
347-
innerScopes: inout SingleInlineArray<LifetimeDependence.Scope>) -> ScopeExtension {
358+
innerScopes: inout SingleInlineArray<LifetimeDependence.Scope>,
359+
_ context: FunctionPassContext) -> SingleInlineArray<ScopeExtension> {
348360
// Finding the access base also finds all intermediate nested scopes; there is no need to recursively call
349361
// gatherExtensions().
350362
let accessBaseAndScopes = beginAccess.accessBaseWithScopes
@@ -368,22 +380,28 @@ private extension LifetimeDependence.Scope {
368380
}
369381
if case let .argument(arg) = accessBaseAndScopes.base {
370382
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)
372385
}
373386
}
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)
375396
}
376397

377398
func gatherBorrowExtension(borrowedValue: Value,
378-
innerScopes: inout SingleInlineArray<LifetimeDependence.Scope>,
399+
innerScopes: SingleInlineArray<LifetimeDependence.Scope>,
379400
_ context: FunctionPassContext)
380401
-> SingleInlineArray<ScopeExtension> {
381402

382403
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) {
387405
return extensions
388406
}
389407
// This is the outermost scope to be extended because gatherExtensions did not find an enclosing scope.
@@ -429,7 +447,7 @@ extension ScopeExtension {
429447
{
430448
if owner.type.isAddress {
431449
// 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,
433451
localReachabilityCache, context) {
434452
return .addressRange(range)
435453
}
@@ -457,7 +475,7 @@ private func computeDependentUseRange(of value: Value, within scopeExtension: in
457475
defer {ownershipRange.deinitialize()}
458476

459477
// 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)
461479
let function = value.parentFunction
462480
var walker = LifetimeDependentUseWalker(function, localReachabilityCache, context) {
463481
// Do not extend the useRange past the ownershipRange.
@@ -487,44 +505,64 @@ private func computeDependentUseRange(of value: Value, within scopeExtension: in
487505

488506
// Extend nested scopes across a use-range within their owner's range.
489507
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)
494514
for innerScope in nestedScopes {
495-
guard let beginInst = innerScope.extendableBegin as? ScopedInstruction else {
515+
guard let beginInst = innerScope.extendableBegin else {
496516
fatalError("all nested scopes must have a scoped begin instruction")
497517
}
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
499519
// 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) {
514522
// Scope ending instructions cannot be inserted at the 'range' boundary. Ignore all nested scopes.
515523
//
516524
// Note: We could still extend previously prepared inner scopes up to this 'innerScope'. To do that, we would
517525
// need to repeat the steps above: treat 'innerScope' as the new owner, and recompute 'useRange'. But this
518526
// scenario could only happen with nested coroutine, where the range boundary is reachable from the outer
519527
// coroutine's EndApply and AbortApply--it is vanishingly unlikely if not impossible.
520-
return
528+
return false
521529
}
522-
scopesToExtend.push(innerScope)
523530
}
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) })
524547

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)
528566
}
529567
}
530568
}
@@ -561,9 +599,9 @@ private extension LifetimeDependence.Scope {
561599
}
562600
}
563601

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 {
567605
fatalError("all nested scoped must have a scoped begin instruction")
568606
}
569607
// 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 {
574612
endInsts.append(end)
575613
}
576614
insertBoundaryEnds(range: &range, context)
577-
578-
// Delete original end instructions
579-
for endInst in endInsts {
580-
context.erase(instruction: endInst)
581-
}
615+
return endInsts
582616
}
583617

584618
/// Create new scope-ending instructions at the boundary of 'range'.
@@ -590,44 +624,46 @@ private extension LifetimeDependence.Scope {
590624
// function argument.
591625
let builder = Builder(before: end, location: location, context)
592626
// 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))
594628
continue
595629
}
596630
Builder.insert(after: end, location: location, context) {
597-
range.insert(createEndInstruction($0, context)!)
631+
range.insert(createEndInstruction($0, context))
598632
}
599633
}
600634
for exitInst in range.exits {
601635
let location = exitInst.location.autoGenerated
602636
let builder = Builder(before: exitInst, location: location, context)
603-
range.insert(createEndInstruction(builder, context)!)
637+
range.insert(createEndInstruction(builder, context))
604638
}
605639
}
606640

607641
/// 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 {
609643
switch self {
610644
case let .access(beginAccess):
611645
return builder.createEndAccess(beginAccess: beginAccess)
612646
case let .borrowed(beginBorrow):
613647
return builder.createEndBorrow(of: beginBorrow.value)
614648
case let .yield(yieldedValue):
615649
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)!
617652
case let .initialized(initializer):
618653
switch initializer {
619654
case let .store(initializingStore: store, initialAddress: _):
620655
if let sb = store as? StoreBorrowInst {
621656
return builder.createEndBorrow(of: sb)
622657
}
623-
return nil
658+
break
624659
case .argument, .yield:
625660
// TODO: extend indirectly yielded scopes.
626-
return nil
661+
break
627662
}
628663
default:
629-
return nil
664+
break
630665
}
666+
fatalError("Unsupported scoped extension: \(self)")
631667
}
632668
}
633669

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

+2
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ extension LifetimeDependence.Scope {
350350
self = .initialized(.yield(result))
351351
return
352352
}
353+
// @inout arguments belong to the coroutine because they are valid for the duration of the yield, and, if local
354+
// mutation or reassignment were relevant, then the dependence would be on an access scope instead.
353355
self = .yield(result)
354356
}
355357
}

0 commit comments

Comments
 (0)