Skip to content

Commit 52b0b05

Browse files
committed
LifetimeDependenceScopeFixup: extend temporary stack allocations
When the source of a lifetime dependency is a stack-allocated address, extend the stack allocation to cover all dependent uses. This avoids miscompilations for "addressable" dependencies which arise in code built with -enable-experimental-feature AddressableTypes or AddressableParameters. It is always an error for SILGen to emit the alloc_stack in such cases. Nonetheless, we want to handle these unexpected cases gracefully in SIL as a diagnostic error rather than allowing a miscompile. Fixes rdar://159680262 ([nonescapable] diagnose dependence on a temporary copy of a global array)
1 parent 72b02ea commit 52b0b05

File tree

3 files changed

+75
-29
lines changed

3 files changed

+75
-29
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: 22 additions & 0 deletions
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 {

test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,16 @@ var values: InlineArray<_, Int> = [0, 1, 2]
324324

325325
// rdar://159680262 ([nonescapable] diagnose dependence on a temporary copy of a global array)
326326
@available(Span 0.1, *)
327-
func test() -> Int {
327+
func readGlobalSpan() -> Int {
328+
let span = values.span
329+
return span[3]
330+
}
331+
332+
// TODO: rdar://151320168 ([nonescapable] support immortal span over global array)
333+
@available(Span 0.1, *)
334+
@_lifetime(immortal)
335+
func returnGlobalSpan() -> Span<Int> {
328336
let span = values.span // expected-error{{lifetime-dependent variable 'span' escapes its scope}}
329337
// expected-note@-1{{it depends on this scoped access to variable 'values'}}
330-
return span[3] // expected-note{{this use of the lifetime-dependent value is out of scope}}
338+
return span // expected-note{{this use causes the lifetime-dependent value to escape}}
331339
}

0 commit comments

Comments
 (0)