Skip to content

Commit faad7c7

Browse files
authored
Merge pull request swiftlang#83728 from atrick/rvalue-extension
LifetimeDependenceScopeFixup: extend store_borrow allocations
2 parents 114ea29 + 288cef1 commit faad7c7

File tree

14 files changed

+283
-176
lines changed

14 files changed

+283
-176
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/AllocBoxToStack.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ private func hoistMarkUnresolvedInsts(stackAddress: Value,
449449
builder = Builder(atBeginOf: stackAddress.parentBlock, context)
450450
}
451451
let mu = builder.createMarkUnresolvedNonCopyableValue(value: stackAddress, checkKind: checkKind, isStrict: false)
452-
stackAddress.uses.ignore(user: mu).ignoreDebugUses.ignoreUsers(ofType: DeallocStackInst.self)
452+
stackAddress.uses.ignore(user: mu).ignoreDebugUses.ignoreUses(ofType: DeallocStackInst.self)
453453
.replaceAll(with: mu, context)
454454
}
455455

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DestroyHoisting.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ let destroyHoisting = FunctionPass(name: "destroy-hoisting") {
8383
private func optimize(value: Value, _ context: FunctionPassContext) {
8484
guard value.ownership == .owned,
8585
// Avoid all the analysis effort if there are no destroys to hoist.
86-
!value.uses.filterUsers(ofType: DestroyValueInst.self).isEmpty
86+
!value.uses.filterUses(ofType: DestroyValueInst.self).isEmpty
8787
else {
8888
return
8989
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 66 additions & 36 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,27 +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-
var unusedEnds = InstructionSet(context)
842-
for end in endsToErase {
858+
let originalScopeEnds = [Instruction](self.endInstructions)
859+
// Track scope-ending instructions that have not yet been reused as range-ending instructions.
860+
var unreusedEnds = InstructionSet(context)
861+
for end in originalScopeEnds {
843862
assert(range.inclusiveRangeContains(end))
844-
unusedEnds.insert(end)
863+
unreusedEnds.insert(end)
845864
}
846-
defer { unusedEnds.deinitialize() }
865+
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+
847873
for end in range.ends {
848874
let location = end.location.asAutoGenerated
849875
switch end {
@@ -856,60 +882,64 @@ extension ExtendableScope {
856882
// function argument.
857883
let builder = Builder(before: end, location: location, context)
858884
// Insert newEnd so that this scope will be nested in any outer scopes.
859-
range.insert(createEndInstruction(builder, context))
885+
range.insert(contentsOf: createEndInstructions(builder, context))
860886
continue
861887
default:
862888
break
863889
}
864-
if unusedEnds.contains(end) {
865-
unusedEnds.erase(end)
866-
assert(!unusedEnds.contains(end))
890+
// If this range ending instruction was also scope-ending, then mark it as reused by removing it from the set.
891+
if unreusedEnds.contains(end) {
892+
unreusedEnds.erase(end)
893+
assert(!unreusedEnds.contains(end))
867894
continue
868895
}
869896
Builder.insert(after: end, location: location, context) {
870-
range.insert(createEndInstruction($0, context))
897+
range.insert(contentsOf: createEndInstructions($0, context))
871898
}
872899
}
873900
for exitInst in range.exits {
874901
let location = exitInst.location.asAutoGenerated
875902
let builder = Builder(before: exitInst, location: location, context)
876-
range.insert(createEndInstruction(builder, context))
903+
range.insert(contentsOf: createEndInstructions(builder, context))
877904
}
878-
return endsToErase.filter { unusedEnds.contains($0) }
905+
endsToErase.append(contentsOf: originalScopeEnds.filter { unreusedEnds.contains($0) })
906+
return endsToErase
879907
}
880908

881909
/// Create a scope-ending instruction at 'builder's insertion point.
882-
func createEndInstruction(_ builder: Builder, _ context: some Context) -> Instruction {
910+
func createEndInstructions(_ builder: Builder, _ context: some Context) -> SingleInlineArray<Instruction> {
883911
switch self.scope {
884912
case let .access(beginAccess):
885-
return builder.createEndAccess(beginAccess: beginAccess)
913+
return SingleInlineArray(element: builder.createEndAccess(beginAccess: beginAccess))
886914
case let .borrowed(beginBorrow):
887-
return builder.createEndBorrow(of: beginBorrow.value)
915+
return SingleInlineArray(element: builder.createEndBorrow(of: beginBorrow.value))
888916
case let .yield(yieldedValue):
889917
let beginApply = yieldedValue.definingInstruction as! BeginApplyInst
890918
// createEnd() returns non-nil because beginApply.endReaches() was checked by canExtend()
891-
return beginApply.createEnd(builder, context)!
919+
return SingleInlineArray(element: beginApply.createEnd(builder, context)!)
892920
case let .initialized(initializer):
893921
switch initializer {
894922
case let .store(initializingStore: store, initialAddress: _):
895923
if let sb = store as? StoreBorrowInst {
896-
// FIXME: we may need to rewrite the dealloc_stack.
897-
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
898928
}
899929
break
900930
case .argument, .yield:
901931
// TODO: extend indirectly yielded scopes.
902932
break
903933
}
904934
case let .owned(value):
905-
return builder.createDestroyValue(operand: value)
935+
return SingleInlineArray(element: builder.createDestroyValue(operand: value))
906936
case let .local(varInst):
907937
switch varInst {
908938
case let .beginBorrow(beginBorrow):
909939
// FIXME: we may need to rewrite the dealloc_stack.
910-
return builder.createEndBorrow(of: beginBorrow)
940+
return SingleInlineArray(element: builder.createEndBorrow(of: beginBorrow))
911941
case let .moveValue(moveValue):
912-
return builder.createDestroyValue(operand: moveValue)
942+
return SingleInlineArray(element: builder.createDestroyValue(operand: moveValue))
913943
}
914944
default:
915945
break

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private extension AllocStackInst {
245245
var liferange = InstructionRange(begin: self, context)
246246
defer { liferange.deinitialize() }
247247

248-
liferange.insert(contentsOf: uses.ignoreUsers(ofType: DeallocStackInst.self).lazy.map { $0.instruction })
248+
liferange.insert(contentsOf: uses.ignoreUses(ofType: DeallocStackInst.self).lazy.map { $0.instruction })
249249

250250
for use in uses {
251251
switch use.instruction {

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyAllocStack.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private extension AllocStackInst {
206206
iea.replace(with: newAlloc, context)
207207
}
208208
case let oea as OpenExistentialAddrInst:
209-
assert(oea.uses.ignoreUsers(ofType: DestroyAddrInst.self).isEmpty)
209+
assert(oea.uses.ignoreUses(ofType: DestroyAddrInst.self).isEmpty)
210210
oea.replace(with: newAlloc, context)
211211
case let cab as CheckedCastAddrBranchInst:
212212
let builder = Builder(before: cab, context)
@@ -246,7 +246,7 @@ private extension AllocStackInst {
246246
is DebugValueInst:
247247
break
248248
case let oea as OpenExistentialAddrInst:
249-
if !oea.uses.ignoreUsers(ofType: DestroyAddrInst.self).isEmpty {
249+
if !oea.uses.ignoreUses(ofType: DestroyAddrInst.self).isEmpty {
250250
return nil
251251
}
252252
case let iea as InitExistentialAddrInst:

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBeginAndLoadBorrow.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension BeginBorrowInst : OnoneSimplifiable, SILCombineSimplifiable {
2929

3030
extension LoadBorrowInst : Simplifiable, SILCombineSimplifiable {
3131
func simplify(_ context: SimplifyContext) {
32-
if uses.ignoreDebugUses.ignoreUsers(ofType: EndBorrowInst.self).isEmpty {
32+
if uses.ignoreDebugUses.ignoreUses(ofType: EndBorrowInst.self).isEmpty {
3333
context.erase(instructionIncludingAllUsers: self)
3434
return
3535
}
@@ -52,7 +52,7 @@ extension LoadBorrowInst : Simplifiable, SILCombineSimplifiable {
5252

5353
private func tryCombineWithCopy(_ context: SimplifyContext) {
5454
let forwardedValue = lookThroughSingleForwardingUses()
55-
guard let singleUser = forwardedValue.uses.ignoreUsers(ofType: EndBorrowInst.self).singleUse?.instruction,
55+
guard let singleUser = forwardedValue.uses.ignoreUses(ofType: EndBorrowInst.self).singleUse?.instruction,
5656
let copy = singleUser as? CopyValueInst,
5757
copy.parentBlock == self.parentBlock else {
5858
return
@@ -81,13 +81,13 @@ private func tryReplaceBorrowWithOwnedOperand(beginBorrow: BeginBorrowInst, _ co
8181
private func removeBorrowOfThinFunction(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
8282
guard let thin2thickFn = beginBorrow.borrowedValue as? ThinToThickFunctionInst,
8383
// For simplicity don't go into the trouble of removing reborrow phi arguments.
84-
beginBorrow.uses.filterUsers(ofType: BranchInst.self).isEmpty else
84+
beginBorrow.uses.filterUses(ofType: BranchInst.self).isEmpty else
8585
{
8686
return
8787
}
8888
// `thin_to_thick_function` has "none" ownership and is compatible with guaranteed values.
8989
// Therefore the `begin_borrow` is not needed.
90-
beginBorrow.uses.ignoreUsers(ofType: EndBorrowInst.self).replaceAll(with: thin2thickFn, context)
90+
beginBorrow.uses.ignoreUses(ofType: EndBorrowInst.self).replaceAll(with: thin2thickFn, context)
9191
context.erase(instructionIncludingAllUsers: beginBorrow)
9292
}
9393

@@ -110,7 +110,7 @@ private func tryReplaceCopy(
110110
withCopiedOperandOf beginBorrow: BeginBorrowInst,
111111
_ context: SimplifyContext
112112
) -> Bool {
113-
guard let singleUser = forwardedValue.uses.ignoreUsers(ofType: EndBorrowInst.self).singleUse?.instruction,
113+
guard let singleUser = forwardedValue.uses.ignoreUses(ofType: EndBorrowInst.self).singleUse?.instruction,
114114
let copy = singleUser as? CopyValueInst,
115115
copy.parentBlock == beginBorrow.parentBlock else {
116116
return false
@@ -153,7 +153,7 @@ private extension Value {
153153
/// ```
154154
/// Returns self if this value has no uses which are ForwardingInstructions.
155155
func lookThroughSingleForwardingUses() -> Value {
156-
if let singleUse = uses.ignoreUsers(ofType: EndBorrowInst.self).singleUse,
156+
if let singleUse = uses.ignoreUses(ofType: EndBorrowInst.self).singleUse,
157157
let fwdInst = singleUse.instruction as? (SingleValueInstruction & ForwardingInstruction),
158158
fwdInst.canConvertToOwned,
159159
fwdInst.isSingleForwardedOperand(singleUse),
@@ -165,7 +165,7 @@ private extension Value {
165165
}
166166

167167
var allUsesCanBeConvertedToOwned: Bool {
168-
let relevantUses = uses.ignoreUsers(ofType: EndBorrowInst.self)
168+
let relevantUses = uses.ignoreUses(ofType: EndBorrowInst.self)
169169
return relevantUses.allSatisfy { $0.canAccept(ownership: .owned) }
170170
}
171171

@@ -175,7 +175,7 @@ private extension Value {
175175
}
176176

177177
func replaceAllDestroys(with replacement: Value, _ context: SimplifyContext) {
178-
uses.filterUsers(ofType: DestroyValueInst.self).replaceAll(with: replacement, context)
178+
uses.filterUses(ofType: DestroyValueInst.self).replaceAll(with: replacement, context)
179179
}
180180
}
181181

@@ -187,7 +187,7 @@ private extension Instruction {
187187
// The value and instruction are in the same block. All uses are dominated by both.
188188
return true
189189
}
190-
let destroys = value.uses.filterUsers(ofType: DestroyValueInst.self)
190+
let destroys = value.uses.filterUses(ofType: DestroyValueInst.self)
191191
return destroys.allSatisfy({ $0.instruction.parentBlock == parentBlock})
192192
}
193193
}

SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ private extension Value {
400400
// var p = Point(x: 10, y: 20)
401401
// let o = UnsafePointer(&p)
402402
// Therefore ignore the `end_access` use of a `begin_access`.
403-
let relevantUses = singleUseValue.uses.ignoreDebugUses.ignoreUsers(ofType: EndAccessInst.self)
403+
let relevantUses = singleUseValue.uses.ignoreDebugUses.ignoreUses(ofType: EndAccessInst.self)
404404

405405
guard let use = relevantUses.singleUse else {
406406
return nil

SwiftCompilerSources/Sources/Optimizer/ModulePasses/StackProtection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ private struct StackProtectionOptimization {
365365
let builder = Builder(after: beginAccess, location: beginAccess.location.asAutoGenerated, context)
366366
let temporary = builder.createAllocStack(beginAccess.type)
367367

368-
beginAccess.uses.ignoreUsers(ofType: EndAccessInst.self).replaceAll(with: temporary, context)
368+
beginAccess.uses.ignoreUses(ofType: EndAccessInst.self).replaceAll(with: temporary, context)
369369

370370
for endAccess in beginAccess.endInstructions {
371371
let endBuilder = Builder(before: endAccess, location: endAccess.location.asAutoGenerated, context)

SwiftCompilerSources/Sources/Optimizer/Utilities/OwnershipLiveness.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ extension InteriorUseWalker: AddressUseVisitor {
717717
if handleInner(borrowed: sb) == .abortWalk {
718718
return .abortWalk
719719
}
720-
return sb.uses.filterUsers(ofType: EndBorrowInst.self).walk {
720+
return sb.uses.filterUses(ofType: EndBorrowInst.self).walk {
721721
useVisitor($0)
722722
}
723723
case let load as LoadBorrowInst:

SwiftCompilerSources/Sources/SIL/Operand.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,15 @@ extension Sequence where Element == Operand {
164164
self.lazy.filter { !($0.instruction is DebugValueInst) }
165165
}
166166

167-
public func filterUsers<I: Instruction>(ofType: I.Type) -> LazyFilterSequence<Self> {
167+
public func filterUses<I: Instruction>(ofType: I.Type) -> LazyFilterSequence<Self> {
168168
self.lazy.filter { $0.instruction is I }
169169
}
170170

171-
public func ignoreUsers<I: Instruction>(ofType: I.Type) -> LazyFilterSequence<Self> {
171+
public func filterUsers<I: Instruction>(ofType: I.Type) -> LazyMapSequence<LazyFilterSequence<Self>, I> {
172+
self.lazy.filter { $0.instruction is I }.lazy.map { $0.instruction as! I }
173+
}
174+
175+
public func ignoreUses<I: Instruction>(ofType: I.Type) -> LazyFilterSequence<Self> {
172176
self.lazy.filter { !($0.instruction is I) }
173177
}
174178

@@ -177,11 +181,11 @@ extension Sequence where Element == Operand {
177181
}
178182

179183
public func getSingleUser<I: Instruction>(ofType: I.Type) -> I? {
180-
filterUsers(ofType: I.self).singleUse?.instruction as? I
184+
filterUses(ofType: I.self).singleUse?.instruction as? I
181185
}
182186

183187
public func getSingleUser<I: Instruction>(notOfType: I.Type) -> Instruction? {
184-
ignoreUsers(ofType: I.self).singleUse?.instruction
188+
ignoreUses(ofType: I.self).singleUse?.instruction
185189
}
186190

187191
public var endingLifetime: LazyFilterSequence<Self> {

0 commit comments

Comments
 (0)