Skip to content

Commit baffcb1

Browse files
authored
Merge pull request #84091 from atrick/lifedep-tempalloc
LifetimeDependenceDiagnostics: check for on-stack trivial copies
2 parents 3d5dc9b + 712d39a commit baffcb1

File tree

5 files changed

+340
-33
lines changed

5 files changed

+340
-33
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -365,59 +365,70 @@ extension ScopeExtension {
365365
private struct ExtendableScope {
366366
enum Introducer {
367367
case scoped(ScopedInstruction)
368+
case stack(Instruction)
368369
case owned(Value)
369370
}
370371

372+
// scope.allocStackInstruction is always valid for Introducer.allocStack and is valid for Introducer.scoped when
373+
// ScopedInstruction is a store_borrow.
371374
let scope: LifetimeDependence.Scope
372375
let introducer: Introducer
373376

374377
var firstInstruction: Instruction {
375378
switch introducer {
376379
case let .scoped(scopedInst):
377380
return scopedInst
381+
case let .stack(initializingStore):
382+
return initializingStore
378383
case let .owned(value):
379384
if let definingInst = value.definingInstructionOrTerminator {
380385
return definingInst
381386
}
382387
return value.parentBlock.instructions.first!
383388
}
384389
}
390+
385391
var endInstructions: LazyMapSequence<LazyFilterSequence<UseList>, Instruction> {
386392
switch introducer {
387393
case let .scoped(scopedInst):
388394
return scopedInst.scopeEndingOperands.users
395+
case .stack:
396+
// For alloc_stack without a store-borrow scope, include the deallocs in its scope to ensure that we never shorten
397+
// the original allocation. It's possible that some other use depends on the address.
398+
//
399+
// Same as 'AllocStackInst.deallocations' but as an Instruction list...
400+
return scope.allocStackInstruction!.uses.lazy.filter
401+
{ $0.instruction is DeallocStackInst }.lazy.map { $0.instruction }
402+
389403
case let .owned(value):
390404
return value.uses.endingLifetime.users
391405
}
392406
}
393407

394408
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
409+
guard let allocStack = scope.allocStackInstruction else {
410+
return nil
407411
}
408-
return nil
412+
return allocStack.deallocations
409413
}
410414

411-
// Allow scope extension as long as `beginInst` is scoped instruction and does not define a variable scope.
415+
// Allow scope extension as long as `beginInst` does not define a variable scope and is either a scoped instruction or
416+
// a store to a singly-initialized temporary.
412417
init?(_ scope: LifetimeDependence.Scope, beginInst: Instruction?) {
413418
self.scope = scope
414419
guard let beginInst = beginInst, VariableScopeInstruction(beginInst) == nil else {
415420
return nil
416421
}
417-
guard let scopedInst = beginInst as? ScopedInstruction else {
418-
return nil
422+
// Check for "scoped" store_borrow extension before checking allocStackInstruction.
423+
if let scopedInst = beginInst as? ScopedInstruction {
424+
self.introducer = .scoped(scopedInst)
425+
return
419426
}
420-
self.introducer = .scoped(scopedInst)
427+
if scope.allocStackInstruction != nil {
428+
self.introducer = .stack(beginInst)
429+
return
430+
}
431+
return nil
421432
}
422433

423434
// Allow extension of owned temporaries that
@@ -484,11 +495,14 @@ extension ScopeExtension {
484495
switch initializer {
485496
case let .store(initializingStore: store, initialAddress: _):
486497
if let sb = store as? StoreBorrowInst {
487-
// Follow the source for nested scopes.
498+
// Follow the stored value since the owner of the borrowed value needs to cover this allocation.
488499
gatherExtensions(valueOrAddress: sb.source)
489-
scopes.push(ExtendableScope(scope, beginInst: sb)!)
500+
}
501+
if scope.allocStackInstruction != nil {
502+
scopes.push(ExtendableScope(scope, beginInst: store)!)
490503
return
491504
}
505+
break
492506
case .argument, .yield:
493507
// TODO: extend indirectly yielded scopes.
494508
break
@@ -816,13 +830,10 @@ extension ExtendableScope {
816830
switch initializer {
817831
case .argument, .yield:
818832
// A yield is already considered nested within the coroutine.
819-
break
820-
case let .store(initializingStore, _):
821-
if initializingStore is StoreBorrowInst {
822-
return true
823-
}
833+
return true
834+
case .store:
835+
return self.scope.allocStackInstruction != nil
824836
}
825-
return true
826837
default:
827838
// non-yield scopes can always be ended at any point.
828839
return true
@@ -868,6 +879,9 @@ extension ExtendableScope {
868879
var endsToErase = [Instruction]()
869880
if let deallocs = self.deallocs {
870881
endsToErase.append(contentsOf: deallocs.map { $0 })
882+
for dealloc in deallocs {
883+
unreusedEnds.erase(dealloc)
884+
}
871885
}
872886

873887
for end in range.ends {
@@ -920,10 +934,12 @@ extension ExtendableScope {
920934
case let .initialized(initializer):
921935
switch initializer {
922936
case let .store(initializingStore: store, initialAddress: _):
937+
var endInsts = SingleInlineArray<Instruction>()
923938
if let sb = store as? StoreBorrowInst {
924-
var endInsts = SingleInlineArray<Instruction>()
925939
endInsts.append(builder.createEndBorrow(of: sb))
926-
endInsts.append(builder.createDeallocStack(sb.allocStack))
940+
}
941+
if let allocStack = self.scope.allocStackInstruction {
942+
endInsts.append(builder.createDeallocStack(allocStack))
927943
return endInsts
928944
}
929945
break

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,29 @@ extension LifetimeDependence.Scope {
381381
}
382382
}
383383

384+
// Scope helpers.
384385
extension LifetimeDependence.Scope {
386+
// If the LifetimeDependenceScope is .initialized, then return the alloc_stack.
387+
var allocStackInstruction: AllocStackInst? {
388+
switch self {
389+
case let .initialized(initializer):
390+
switch initializer {
391+
case let .store(initializingStore: store, initialAddress):
392+
if let allocStackInst = initialAddress as? AllocStackInst {
393+
return allocStackInst
394+
}
395+
if let sb = store as? StoreBorrowInst {
396+
return sb.allocStack
397+
}
398+
return nil
399+
default:
400+
return nil
401+
}
402+
default:
403+
return nil
404+
}
405+
}
406+
385407
/// Ignore "irrelevant" borrow scopes: load_borrow or begin_borrow without [var_decl]
386408
func ignoreBorrowScope(_ context: some Context) -> LifetimeDependence.Scope? {
387409
guard case let .borrowed(beginBorrowVal) = self else {
@@ -456,7 +478,7 @@ extension LifetimeDependence.Scope {
456478
}
457479
let address = initializer.initialAddress
458480
if address.type.objectType.isTrivial(in: address.parentFunction) {
459-
return nil
481+
return LifetimeDependence.Scope.computeAddressableRange(initializer: initializer, context)
460482
}
461483
return LifetimeDependence.Scope.computeInitializedRange(initializer: initializer, context)
462484
case let .unknown(value):
@@ -503,6 +525,46 @@ extension LifetimeDependence.Scope {
503525
}
504526
return range
505527
}
528+
529+
// For trivial values, compute the range in which the value may have its address taken.
530+
// Returns nil unless the address has both an addressable parameter use and a dealloc_stack use.
531+
//
532+
// This extended range is convervative. In theory, it can lead to a "false positive" diagnostic if this scope was
533+
// computed for a apply site that takes this address as *non-addressable*, as opposed to the apply site discovered
534+
// here that takes the alloc_stack as an *addressable* argument. This won't normally happen because addressability
535+
// depends on the value's type (via @_addressableForDepenencies), so all other lifetime-dependent applies should be
536+
// addressable. Furthermore, SILGen only creates temporary stack locations for addressable arguments for a single
537+
// argument.
538+
private static func computeAddressableRange(initializer: AccessBase.Initializer, _ context: Context)
539+
-> InstructionRange? {
540+
switch initializer {
541+
case .argument, .yield:
542+
return nil
543+
case let .store(initializingStore, initialAddr):
544+
guard initialAddr is AllocStackInst else {
545+
return nil
546+
}
547+
var isAddressable = false
548+
var deallocInsts = SingleInlineArray<DeallocStackInst>()
549+
for use in initialAddr.uses {
550+
let inst = use.instruction
551+
switch inst {
552+
case let apply as ApplySite:
553+
isAddressable = isAddressable || apply.isAddressable(operand: use)
554+
case let dealloc as DeallocStackInst:
555+
deallocInsts.append(dealloc)
556+
default:
557+
break
558+
}
559+
}
560+
guard isAddressable, !deallocInsts.isEmpty else {
561+
return nil
562+
}
563+
var range = InstructionRange(begin: initializingStore, context)
564+
range.insert(contentsOf: deallocInsts)
565+
return range
566+
}
567+
}
506568
}
507569

508570
// =============================================================================

test/SILOptimizer/lifetime_dependence/scope_fixup.sil

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ public struct AThing : ~Copyable {
8787
init(somethings: UnsafeMutableRawBufferPointer)
8888
}
8989

90+
@_addressableForDependencies
91+
public struct InlineInt {
92+
var i: Int
93+
}
94+
9095
sil @getContainer : $@convention(thin) (@inout AThing) -> @lifetime(borrow address_for_deps 0) @out Container
9196
sil @getMutableView : $@convention(thin) (@in_guaranteed Container) -> @lifetime(copy 0) @out Optional<MutableView>
9297
sil @modAThing : $@convention(thin) (@inout AThing) -> ()
@@ -138,6 +143,10 @@ sil @getAddressableObj : $@convention(method) (@guaranteed Holder) -> @owned Add
138143

139144
sil @getAddressableObj_NE : $@convention(thin) (@in_guaranteed AddressableForDepsObj) -> @owned NE
140145

146+
sil @globalAddress : $@convention(thin) () -> Builtin.RawPointer
147+
sil @addressInt : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
148+
sil @useSpan : $@convention(thin) (@guaranteed Span<Int>) -> ()
149+
141150
// NCContainer.wrapper._read:
142151
// var wrapper: Wrapper {
143152
// _read {
@@ -914,3 +923,50 @@ bb2:
914923
dealloc_box [dead_end] %1
915924
unreachable
916925
}
926+
927+
// Test trivial alloc_stack extension when used by an addressable dependency.
928+
//
929+
// CHECK-LABEL: sil hidden [noinline] [ossa] @testTempTrivialStackCopy : $@convention(thin) () -> () {
930+
// CHECK: [[STK:%[0-9]+]] = alloc_stack $InlineInt
931+
// CHECK: [[MD:%[0-9]+]] = mark_dependence [unresolved] %{{.*}} on [[STK]]
932+
// CHECK: [[VAR:%[0-9]+]] = move_value [var_decl] [[MD]]
933+
// CHECK: bb3: // Preds: bb2 bb1
934+
// CHECK: [[BORROW:%[0-9]+]] = begin_borrow [[VAR]]
935+
// CHECK: apply %{{.*}}([[BORROW]]) : $@convention(thin) (@guaranteed Span<Int>) -> ()
936+
// CHECK: destroy_value [[VAR]]
937+
// CHECK: dealloc_stack [[STK]]
938+
// CHECK-LABEL: } // end sil function 'testTempTrivialStackCopy'
939+
sil hidden [noinline] [ossa] @testTempTrivialStackCopy : $@convention(thin) () -> () {
940+
bb0:
941+
%0 = function_ref @globalAddress : $@convention(thin) () -> Builtin.RawPointer
942+
%1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer
943+
%2 = pointer_to_address %1 to [strict] $*InlineInt
944+
%3 = begin_access [read] [dynamic] %2
945+
%4 = load [trivial] %3
946+
end_access %3
947+
%6 = alloc_stack $InlineInt
948+
store %4 to [trivial] %6
949+
950+
%8 = function_ref @addressInt : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
951+
%9 = apply %8(%6) : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
952+
%10 = mark_dependence [unresolved] %9 on %6
953+
%11 = move_value [var_decl] %10
954+
cond_br undef, bb1, bb2
955+
956+
bb1:
957+
dealloc_stack %6
958+
br bb3
959+
bb2:
960+
dealloc_stack %6
961+
br bb3
962+
963+
bb3:
964+
%13 = begin_borrow %11
965+
966+
%14 = function_ref @useSpan : $@convention(thin) (@guaranteed Span<Int>) -> ()
967+
%15 = apply %14(%13) : $@convention(thin) (@guaranteed Span<Int>) -> ()
968+
end_borrow %13
969+
destroy_value %11
970+
%18 = tuple ()
971+
return %18
972+
}

0 commit comments

Comments
 (0)