Skip to content

Commit 72b02ea

Browse files
committed
LifetimeDependenceDiagnostics: check for on-stack trivial copies
Add a diagnostic to catch addressable dependencies on a trivial values that have been copied to a temporary stack location. SILGen should never copy the source of an addressable dependency to a temporary stack location, but this diagnostic catches such compiler bugs rather than allowing miscompilation. Fixes rdar://159680262 ([nonescapable] diagnose dependence on a temporary copy of a global array)
1 parent 8362997 commit 72b02ea

File tree

3 files changed

+94
-6
lines changed

3 files changed

+94
-6
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ extension LifetimeDependence.Scope {
456456
}
457457
let address = initializer.initialAddress
458458
if address.type.objectType.isTrivial(in: address.parentFunction) {
459-
return nil
459+
return LifetimeDependence.Scope.computeAddressableRange(initializer: initializer, context)
460460
}
461461
return LifetimeDependence.Scope.computeInitializedRange(initializer: initializer, context)
462462
case let .unknown(value):
@@ -503,6 +503,39 @@ extension LifetimeDependence.Scope {
503503
}
504504
return range
505505
}
506+
507+
// For trivial values, compute the range in which the value may have its address taken.
508+
// Returns nil unless the address has both an addressable parameter use and a dealloc_stack use.
509+
private static func computeAddressableRange(initializer: AccessBase.Initializer, _ context: Context)
510+
-> InstructionRange? {
511+
switch initializer {
512+
case .argument, .yield:
513+
return nil
514+
case let .store(initializingStore, initialAddr):
515+
guard initialAddr is AllocStackInst else {
516+
return nil
517+
}
518+
var isAddressable = false
519+
var deallocInst: DeallocStackInst?
520+
for use in initialAddr.uses {
521+
let inst = use.instruction
522+
switch inst {
523+
case let apply as ApplyInst:
524+
isAddressable = isAddressable || apply.isAddressable(operand: use)
525+
case let dealloc as DeallocStackInst:
526+
deallocInst = dealloc
527+
default:
528+
break
529+
}
530+
}
531+
guard let deallocInst = deallocInst, isAddressable else {
532+
return nil
533+
}
534+
var range = InstructionRange(begin: initializingStore, context)
535+
range.insert(deallocInst)
536+
return range
537+
}
538+
}
506539
}
507540

508541
// =============================================================================

test/SILOptimizer/lifetime_dependence/verify_diagnostics.sil

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
// RUN: --lifetime-dependence-diagnostics \
33
// RUN: -verify \
44
// RUN: -sil-verify-all \
5-
// RUN: -module-name Swift \
65
// RUN: -enable-experimental-feature Lifetimes \
6+
// RUN: -enable-experimental-feature AddressableTypes \
77
// RUN: -o /dev/null
88

99
// REQUIRES: swift_in_compiler
1010
// REQUIRES: swift_feature_Lifetimes
11+
// REQUIRES: swift_feature_AddressableTypes
1112

1213
// Verify diagnostics from the LifetimeDependenceDiagnostics pass.
1314

1415
sil_stage raw
1516

1617
import Builtin
17-
18-
@_marker protocol Copyable: ~Escapable {}
19-
@_marker protocol Escapable: ~Copyable {}
18+
import Swift
19+
import SwiftShims
2020

2121
class C {}
2222

@@ -61,6 +61,14 @@ sil @getTrivialNE : $@convention(thin) (@in_guaranteed TrivialHolder) -> @lifeti
6161
sil @makeHolder: $@convention(method) (@thin Holder.Type) -> @owned Holder // user: %6
6262
sil @getGeneric : $@convention(thin) <τ_0_0 where τ_0_0 : ~Escapable> (@guaranteed Holder, @thick τ_0_0.Type) -> @lifetime(borrow 0) @out τ_0_0 // user: %12
6363

64+
@_addressableForDependencies
65+
public struct InlineInt {
66+
var i: Int
67+
}
68+
sil @globalAddress : $@convention(thin) () -> Builtin.RawPointer
69+
sil @addressInt : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
70+
sil @useSpan : $@convention(thin) (@guaranteed Span<Int>) -> ()
71+
6472
// Test returning a owned dependence on a trivial value
6573
sil [ossa] @return_trivial_dependence : $@convention(thin) (@guaranteed C) -> @lifetime(borrow 0) @owned NE {
6674
entry(%0 : @guaranteed $C):
@@ -246,3 +254,36 @@ bb0(%0 : $*NE, %1 : $*Holder):
246254
%18 = tuple ()
247255
return %18
248256
}
257+
258+
// Test a address dependency on a trivial value temporarily copied to the stack. The value's addressable range only
259+
// extends to the dealloc_stack.
260+
//
261+
// SILGen should never generate temporary stack copies for addressable dependencies, but we need to diagnose them anyway
262+
// so that a SILGen bug does not become a miscompile.
263+
//
264+
// rdar://159680262 ([nonescapable] diagnose dependence on a temporary copy of a global array)
265+
sil hidden [noinline] [ossa] @testTrivialStackCopy : $@convention(thin) () -> () {
266+
bb0:
267+
%0 = function_ref @globalAddress : $@convention(thin) () -> Builtin.RawPointer
268+
%1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer
269+
%2 = pointer_to_address %1 to [strict] $*InlineInt
270+
%3 = begin_access [read] [dynamic] %2 // expected-note{{it depends on this scoped access to variable ''}}
271+
%4 = load [trivial] %3
272+
end_access %3
273+
%6 = alloc_stack $InlineInt
274+
store %4 to [trivial] %6
275+
276+
%8 = function_ref @addressInt : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
277+
%9 = apply %8(%6) : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
278+
%10 = mark_dependence [unresolved] %9 on %6
279+
%11 = move_value [var_decl] %10 // expected-error{{lifetime-dependent value escapes its scope}}
280+
dealloc_stack %6
281+
%13 = begin_borrow %11
282+
283+
%14 = function_ref @useSpan : $@convention(thin) (@guaranteed Span<Int>) -> ()
284+
%15 = apply %14(%13) : $@convention(thin) (@guaranteed Span<Int>) -> ()
285+
end_borrow %13
286+
destroy_value %11 // expected-note{{this use of the lifetime-dependent value is out of scope}}
287+
%18 = tuple ()
288+
return %18
289+
}

test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend %s -emit-sil \
1+
// RUN: %target-swift-frontend -primary-file %s -parse-as-library -emit-sil \
22
// RUN: -o /dev/null \
33
// RUN: -verify \
44
// RUN: -sil-verify-all \
@@ -315,3 +315,17 @@ func inoutToImmortal(_ s: inout RawSpan) {
315315
s = _overrideLifetime(tmp, borrowing: ())
316316
}
317317

318+
// =============================================================================
319+
// addressable dependencies
320+
// =============================================================================
321+
322+
@available(Span 0.1, *)
323+
var values: InlineArray<_, Int> = [0, 1, 2]
324+
325+
// rdar://159680262 ([nonescapable] diagnose dependence on a temporary copy of a global array)
326+
@available(Span 0.1, *)
327+
func test() -> Int {
328+
let span = values.span // expected-error{{lifetime-dependent variable 'span' escapes its scope}}
329+
// 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}}
331+
}

0 commit comments

Comments
 (0)