Skip to content

Commit 712d39a

Browse files
committed
Fix computeAddressableRange, test, and comment.
Address review feedback from the previous commit.
1 parent 52b0b05 commit 712d39a

File tree

4 files changed

+181
-8
lines changed

4 files changed

+181
-8
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,13 @@ extension LifetimeDependence.Scope {
528528

529529
// For trivial values, compute the range in which the value may have its address taken.
530530
// 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.
531538
private static func computeAddressableRange(initializer: AccessBase.Initializer, _ context: Context)
532539
-> InstructionRange? {
533540
switch initializer {
@@ -538,23 +545,23 @@ extension LifetimeDependence.Scope {
538545
return nil
539546
}
540547
var isAddressable = false
541-
var deallocInst: DeallocStackInst?
548+
var deallocInsts = SingleInlineArray<DeallocStackInst>()
542549
for use in initialAddr.uses {
543550
let inst = use.instruction
544551
switch inst {
545-
case let apply as ApplyInst:
552+
case let apply as ApplySite:
546553
isAddressable = isAddressable || apply.isAddressable(operand: use)
547554
case let dealloc as DeallocStackInst:
548-
deallocInst = dealloc
555+
deallocInsts.append(dealloc)
549556
default:
550557
break
551558
}
552559
}
553-
guard let deallocInst = deallocInst, isAddressable else {
560+
guard isAddressable, !deallocInsts.isEmpty else {
554561
return nil
555562
}
556563
var range = InstructionRange(begin: initializingStore, context)
557-
range.insert(deallocInst)
564+
range.insert(contentsOf: deallocInsts)
558565
return range
559566
}
560567
}

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+
}

test/SILOptimizer/lifetime_dependence/verify_diagnostics.sil

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ public struct InlineInt {
6767
}
6868
sil @globalAddress : $@convention(thin) () -> Builtin.RawPointer
6969
sil @addressInt : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
70+
sil @addressOfInt : $@convention(thin) (@in_guaranteed Int) -> @lifetime(borrow address 0) @owned Span<Int>
71+
sil @noAddressInt : $@convention(thin) (@in_guaranteed Int) -> @lifetime(borrow 0) @owned Span<Int>
7072
sil @useSpan : $@convention(thin) (@guaranteed Span<Int>) -> ()
7173

7274
// Test returning a owned dependence on a trivial value
@@ -259,10 +261,12 @@ bb0(%0 : $*NE, %1 : $*Holder):
259261
// extends to the dealloc_stack.
260262
//
261263
// 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.
264+
// so that a SILGen bug does not become a miscompile. Furthermore, LifetimeDependenceScopeFixup now automatically
265+
// extends such local allocations, so this diagnostic only fires on source-level tests when scope extension is
266+
// impossible.
263267
//
264268
// rdar://159680262 ([nonescapable] diagnose dependence on a temporary copy of a global array)
265-
sil hidden [noinline] [ossa] @testTrivialStackCopy : $@convention(thin) () -> () {
269+
sil hidden [noinline] [ossa] @testTempTrivialStackCopy : $@convention(thin) () -> () {
266270
bb0:
267271
%0 = function_ref @globalAddress : $@convention(thin) () -> Builtin.RawPointer
268272
%1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer
@@ -287,3 +291,64 @@ bb0:
287291
%18 = tuple ()
288292
return %18
289293
}
294+
295+
// Test an address dependency on a trivial value temporarily copied to the stack. The value's addressable range only
296+
// extends to the dealloc_stack. This represents the SIL from @testTempTrivialStackCopy after
297+
// LifetimeDependenceScopeFixup has extended the local allocation.
298+
sil hidden [noinline] [ossa] @testExtendedTrivialStackCopy : $@convention(thin) () -> () {
299+
bb0:
300+
%0 = function_ref @globalAddress : $@convention(thin) () -> Builtin.RawPointer
301+
%1 = apply %0() : $@convention(thin) () -> Builtin.RawPointer
302+
%2 = pointer_to_address %1 to [strict] $*InlineInt
303+
%3 = begin_access [read] [dynamic] %2
304+
%4 = load [trivial] %3
305+
end_access %3
306+
%6 = alloc_stack $InlineInt
307+
store %4 to [trivial] %6
308+
309+
%8 = function_ref @addressInt : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
310+
%9 = apply %8(%6) : $@convention(thin) (@in_guaranteed InlineInt) -> @lifetime(borrow address_for_deps 0) @owned Span<Int>
311+
%10 = mark_dependence [unresolved] %9 on %6
312+
%11 = move_value [var_decl] %10
313+
%13 = begin_borrow %11
314+
315+
%14 = function_ref @useSpan : $@convention(thin) (@guaranteed Span<Int>) -> ()
316+
%15 = apply %14(%13) : $@convention(thin) (@guaranteed Span<Int>) -> ()
317+
end_borrow %13
318+
destroy_value %11
319+
dealloc_stack %6
320+
%18 = tuple ()
321+
return %18
322+
}
323+
324+
// Test two address dependencies on the same temporay. The first dependency is addressable; it's uses are covered by the
325+
// stack allocation. The second dependency is non-addressable; it's uses are not covered by the stack allocation. So,
326+
// this is correct SIL but is falsely diagnosed as a lifetime error. This conservative error is fine because SILGen
327+
// never reuses a temporary allocaiton for both an addressable and non-addressable dependency.
328+
sil hidden [noinline] [ossa] @testFalseTempTrivialStackCopy : $@convention(thin) (Int) -> () {
329+
bb0(%0: $Int):
330+
%1 = alloc_stack $Int
331+
store %0 to [trivial] %1
332+
333+
%3 = function_ref @addressOfInt : $@convention(thin) (@in_guaranteed Int) -> @lifetime(borrow address 0) @owned Span<Int>
334+
%4 = apply %3(%1) : $@convention(thin) (@in_guaranteed Int) -> @lifetime(borrow address 0) @owned Span<Int>
335+
%5 = mark_dependence [unresolved] %4 on %1
336+
%6 = move_value [var_decl] %5
337+
destroy_value %6
338+
339+
%8 = function_ref @noAddressInt : $@convention(thin) (@in_guaranteed Int) -> @lifetime(borrow 0) @owned Span<Int>
340+
%9 = apply %8(%1) : $@convention(thin) (@in_guaranteed Int) -> @lifetime(borrow 0) @owned Span<Int>
341+
%10 = mark_dependence [unresolved] %9 on %1
342+
%11 = move_value [var_decl] %10 // expected-error{{lifetime-dependent value escapes its scope}}
343+
// expected-note@-15{{it depends on the lifetime of an argument of 'testFalseTempTrivialStackCopy'}}
344+
345+
dealloc_stack %1
346+
347+
%13 = begin_borrow %11
348+
%14 = function_ref @useSpan : $@convention(thin) (@guaranteed Span<Int>) -> ()
349+
%15 = apply %14(%13) : $@convention(thin) (@guaranteed Span<Int>) -> ()
350+
end_borrow %13
351+
destroy_value %11 // expected-note{{this use of the lifetime-dependent value is out of scope}}
352+
%18 = tuple ()
353+
return %18
354+
}

test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,18 +322,63 @@ func inoutToImmortal(_ s: inout RawSpan) {
322322
@available(Span 0.1, *)
323323
var values: InlineArray<_, Int> = [0, 1, 2]
324324

325+
@available(Span 0.1, *)
326+
func getValues() -> InlineArray<3, Int> { values }
327+
328+
// Test a trivial global variable used by addressableForDependencies (InlineArray.span). This either requires a direct address
329+
// to the global or it requires extension of the local stack allocation by LifetimeDependenceScopeFixup.
330+
//
325331
// rdar://159680262 ([nonescapable] diagnose dependence on a temporary copy of a global array)
326332
@available(Span 0.1, *)
327333
func readGlobalSpan() -> Int {
328334
let span = values.span
329335
return span[3]
330336
}
331337

332-
// TODO: rdar://151320168 ([nonescapable] support immortal span over global array)
338+
// Test a trivial global variable used by addressableForDependencies (InlineArray.span).
339+
//
340+
// TODO: This will no longer be an error when SILGen passes a direct address to the global instead of a temporary stack
341+
// allocation: rdar://151320168 ([nonescapable] support immortal span over global array)
333342
@available(Span 0.1, *)
334343
@_lifetime(immortal)
335344
func returnGlobalSpan() -> Span<Int> {
336345
let span = values.span // expected-error{{lifetime-dependent variable 'span' escapes its scope}}
337346
// expected-note@-1{{it depends on this scoped access to variable 'values'}}
338347
return span // expected-note{{this use causes the lifetime-dependent value to escape}}
339348
}
349+
350+
// Test a trivial temporary stack allocation used by addressableForDependencies
351+
// (InlineArray.span). LifetimeDependenceScopeFixup needs to extend the local stack allocation.
352+
@available(Span 0.1, *)
353+
func readTempSpan() -> Int {
354+
let span = getValues().span
355+
return span[3]
356+
}
357+
358+
// Test a trivial temporary stack allocation used by addressableForDependencies (InlineArray.span). This illegally
359+
// returns an address into a temporary.
360+
@available(Span 0.1, *)
361+
@_lifetime(immortal)
362+
func returnTempSpan() -> Span<Int> {
363+
let span = getValues().span // expected-error{{lifetime-dependent variable 'span' escapes its scope}}
364+
// expected-note@-1{{it depends on the lifetime of this parent value}}
365+
return span // expected-note{{this use causes the lifetime-dependent value to escape}}
366+
}
367+
368+
// Test a trivial temporary stack allocation used by an addressable parameter
369+
// (Borrow.init). LifetimeDependenceScopeFixup needs to extend the local stack allocation.
370+
@available(Span 0.1, *)
371+
func readTempBorrow() -> Int {
372+
let borrow = Borrow(3)
373+
return borrow[]
374+
}
375+
376+
// Test a trivial temporary stack allocation used by an addressable parameter (Borrow.init). This illegally returns an
377+
// address into a temporary.
378+
@available(Span 0.1, *)
379+
@_lifetime(immortal)
380+
func returnTempBorrow() -> Borrow<Int> {
381+
let span = Borrow(3) // expected-error{{lifetime-dependent variable 'span' escapes its scope}}
382+
// expected-note@-1{{it depends on the lifetime of this parent value}}
383+
return span // expected-note{{this use causes the lifetime-dependent value to escape}}
384+
}

0 commit comments

Comments
 (0)