Skip to content

Commit 288cef1

Browse files
committed
LifetimeDependenceScopeFixup: extend store_borrow allocations
Extend temporary allocations (sink dealloc_stacks) initialized by a store_borrow across lifetime dependent uses. Fixes rdar://143159873 ([nonescapable] extend rvalue lifetimes when they are the source of a dependency)
1 parent b3e169b commit 288cef1

File tree

3 files changed

+247
-145
lines changed

3 files changed

+247
-145
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
106106

107107
let localReachabilityCache = LocalVariableReachabilityCache()
108108

109+
var mustFixStackNesting = false
109110
for instruction in function.instructions {
110111
guard let markDep = instruction as? MarkDependenceInstruction else {
111112
continue
@@ -122,6 +123,7 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
122123
guard scopeExtension.extendScopes(dependence: newLifetimeDep) else {
123124
continue
124125
}
126+
mustFixStackNesting = mustFixStackNesting || scopeExtension.mustFixStackNesting
125127
let args = scopeExtension.findArgumentDependencies()
126128

127129
// If the scope cannot be extended to the caller, this must be the outermost dependency level.
@@ -133,6 +135,9 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
133135
// Redirect the dependence base to the function arguments. This may create additional mark_dependence instructions.
134136
markDep.redirectFunctionReturn(to: args, context)
135137
}
138+
if mustFixStackNesting {
139+
context.fixStackNesting(in: function)
140+
}
136141
}
137142

138143
private extension Type {
@@ -285,6 +290,9 @@ private struct ScopeExtension {
285290
// Initialized after walking dependent uses. True if the scope can be extended into the caller.
286291
var dependsOnCaller: Bool?
287292

293+
// Does scope extension potentially invalidate stack nesting?
294+
var mustFixStackNesting = false
295+
288296
// Scopes listed in RPO over an upward walk. The outermost scope is first.
289297
var scopes = SingleInlineArray<ExtendableScope>()
290298

@@ -383,6 +391,23 @@ private struct ExtendableScope {
383391
}
384392
}
385393

394+
var deallocs: LazyMapSequence<LazyFilterSequence<UseList>, DeallocStackInst>? {
395+
switch self.scope {
396+
case let .initialized(initializer):
397+
switch initializer {
398+
case let .store(initializingStore: store, initialAddress: _):
399+
if let sb = store as? StoreBorrowInst {
400+
return sb.allocStack.uses.filterUsers(ofType: DeallocStackInst.self)
401+
}
402+
default:
403+
break
404+
}
405+
default:
406+
break
407+
}
408+
return nil
409+
}
410+
386411
// Allow scope extension as long as `beginInst` is scoped instruction and does not define a variable scope.
387412
init?(_ scope: LifetimeDependence.Scope, beginInst: Instruction?) {
388413
self.scope = scope
@@ -742,9 +767,9 @@ extension ScopeExtension {
742767
// Extend the scopes that actually required extension.
743768
//
744769
// Consumes 'useRange'
745-
private func extend(scopesToExtend: SingleInlineArray<ExtendableScope>,
746-
over useRange: inout InstructionRange,
747-
_ context: some MutatingContext) {
770+
private mutating func extend(scopesToExtend: SingleInlineArray<ExtendableScope>,
771+
over useRange: inout InstructionRange,
772+
_ context: some MutatingContext) {
748773
var deadInsts = [Instruction]()
749774
for extScope in scopesToExtend {
750775
// Extend 'useRange' to to cover this scope's end instructions. 'useRange' cannot be extended until the
@@ -772,6 +797,9 @@ extension ScopeExtension {
772797

773798
// Delete original end instructions.
774799
for deadInst in deadInsts {
800+
if deadInst is DeallocStackInst {
801+
mustFixStackNesting = true
802+
}
775803
context.erase(instruction: deadInst)
776804
}
777805
}
@@ -790,8 +818,8 @@ extension ExtendableScope {
790818
// A yield is already considered nested within the coroutine.
791819
break
792820
case let .store(initializingStore, _):
793-
if let sb = initializingStore as? StoreBorrowInst {
794-
return canExtend(storeBorrow: sb, over: &range)
821+
if initializingStore is StoreBorrowInst {
822+
return true
795823
}
796824
}
797825
return true
@@ -823,28 +851,25 @@ extension ExtendableScope {
823851
return true
824852
}
825853

826-
/// A store borrow is considered to be nested within the scope of its stored values. It is, however, also
827-
/// restricted to the range of its allocation.
828-
///
829-
/// TODO: consider rewriting the dealloc_stack instructions if we ever find that SILGen emits them sooner that
830-
/// we need for lifetime dependencies.
831-
func canExtend(storeBorrow: StoreBorrowInst, over range: inout InstructionRange) -> Bool {
832-
// store_borrow can be extended if all deallocations occur after the use range.
833-
return storeBorrow.allocStack.deallocations.allSatisfy({ !range.contains($0) })
834-
}
835-
836854
/// Extend this scope over the 'range' boundary. Return the old scope ending instructions to be deleted.
837855
func extend(over range: inout InstructionRange, _ context: some MutatingContext) -> [Instruction] {
838856
// Collect the original end instructions and extend the range to to cover them. The resulting access scope
839857
// must cover the original scope because it may protect other memory operations.
840-
let endsToErase = self.endInstructions
841-
// Track range ending instructions that have not been reused for later deletion.
858+
let originalScopeEnds = [Instruction](self.endInstructions)
859+
// Track scope-ending instructions that have not yet been reused as range-ending instructions.
842860
var unreusedEnds = InstructionSet(context)
843-
for end in endsToErase {
861+
for end in originalScopeEnds {
844862
assert(range.inclusiveRangeContains(end))
845863
unreusedEnds.insert(end)
846864
}
847865
defer { unreusedEnds.deinitialize() }
866+
867+
// Never reuse dealloc_stack to avoid running data flow.
868+
var endsToErase = [Instruction]()
869+
if let deallocs = self.deallocs {
870+
endsToErase.append(contentsOf: deallocs.map { $0 })
871+
}
872+
848873
for end in range.ends {
849874
let location = end.location.asAutoGenerated
850875
switch end {
@@ -857,45 +882,49 @@ extension ExtendableScope {
857882
// function argument.
858883
let builder = Builder(before: end, location: location, context)
859884
// Insert newEnd so that this scope will be nested in any outer scopes.
860-
range.insert(createEndInstruction(builder, context))
885+
range.insert(contentsOf: createEndInstructions(builder, context))
861886
continue
862887
default:
863888
break
864889
}
890+
// If this range ending instruction was also scope-ending, then mark it as reused by removing it from the set.
865891
if unreusedEnds.contains(end) {
866892
unreusedEnds.erase(end)
867893
assert(!unreusedEnds.contains(end))
868894
continue
869895
}
870896
Builder.insert(after: end, location: location, context) {
871-
range.insert(createEndInstruction($0, context))
897+
range.insert(contentsOf: createEndInstructions($0, context))
872898
}
873899
}
874900
for exitInst in range.exits {
875901
let location = exitInst.location.asAutoGenerated
876902
let builder = Builder(before: exitInst, location: location, context)
877-
range.insert(createEndInstruction(builder, context))
903+
range.insert(contentsOf: createEndInstructions(builder, context))
878904
}
879-
return endsToErase.filter { unreusedEnds.contains($0) }
905+
endsToErase.append(contentsOf: originalScopeEnds.filter { unreusedEnds.contains($0) })
906+
return endsToErase
880907
}
881908

882909
/// Create a scope-ending instruction at 'builder's insertion point.
883-
func createEndInstruction(_ builder: Builder, _ context: some Context) -> Instruction {
910+
func createEndInstructions(_ builder: Builder, _ context: some Context) -> SingleInlineArray<Instruction> {
884911
switch self.scope {
885912
case let .access(beginAccess):
886-
return builder.createEndAccess(beginAccess: beginAccess)
913+
return SingleInlineArray(element: builder.createEndAccess(beginAccess: beginAccess))
887914
case let .borrowed(beginBorrow):
888-
return builder.createEndBorrow(of: beginBorrow.value)
915+
return SingleInlineArray(element: builder.createEndBorrow(of: beginBorrow.value))
889916
case let .yield(yieldedValue):
890917
let beginApply = yieldedValue.definingInstruction as! BeginApplyInst
891918
// createEnd() returns non-nil because beginApply.endReaches() was checked by canExtend()
892-
return beginApply.createEnd(builder, context)!
919+
return SingleInlineArray(element: beginApply.createEnd(builder, context)!)
893920
case let .initialized(initializer):
894921
switch initializer {
895922
case let .store(initializingStore: store, initialAddress: _):
896923
if let sb = store as? StoreBorrowInst {
897-
// FIXME: we may need to rewrite the dealloc_stack.
898-
return builder.createEndBorrow(of: sb)
924+
var endInsts = SingleInlineArray<Instruction>()
925+
endInsts.append(builder.createEndBorrow(of: sb))
926+
endInsts.append(builder.createDeallocStack(sb.allocStack))
927+
return endInsts
899928
}
900929
break
901930
case .argument, .yield:

test/SILOptimizer/lifetime_dependence/lifetime_dependence_scope_fixup.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,10 @@ func testWrite(_ w: inout ArrayWrapper) {
327327
span[i] = 0
328328
}
329329
}
330+
331+
// Test store_borrow extension which is required when the addressable UTF8View is extended over the Span's uses.
332+
@available(SwiftStdlib 6.2, *)
333+
func testSpanOfBorrowByAddress(_ i: Int) -> Int {
334+
let span = String(i).utf8.span
335+
return span.count
336+
}

0 commit comments

Comments
 (0)