mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
The previous implementation made extremely subtle and specific assumptions about how the API is used which doesn't apply everywhere. It was trying very hard to avoid regressing performance relative to an even older implementation that didn't even try to consider deinitializer side effects. The aggressive logic was based on the idea that a release must have a corresponding retain somewhere in the same function and we don't care if the last release happens early if there are no more aliasing uses. All the unit tests I wrote previously were based on release hoisting, which happens to work given the way the API is used. But this logic is incorrect for retain sinking. In that case sinking past an "unrelated" release could cause the object to be freed early. See test/SILOptimizer/arc_crash.swift. With SemanticARC and other SIL improvements being made, I'm afraid bugs like this will begin to surface. To fix it, just remove the subtle logic to leave behind a simple and sound EscapeAnalysis API. To do better, we will need to rewrite the AliasAnalysis logic for release side effects, which is currently a tangled web. In the meantime, SemanticARC can handle many cases without EscapeAnalysis. Fixes rdar://74469299 (ARC miscompile: EscapeAnalysis::mayReleaseContent; potential use-after-free) While fixing this, add support for address-type queries too: Fixes rdar://74360041 (Assertion failed: (!releasedReference->getType().isAddress() && "an address is never a reference"), function mayReleaseContent
1048 lines
33 KiB
Plaintext
1048 lines
33 KiB
Plaintext
// RUN: %target-sil-opt -enable-sil-verify-all -retain-sinking -late-release-hoisting %s | %FileCheck %s
|
|
// RUN: %target-sil-opt -enable-sil-verify-all -release-hoisting %s | %FileCheck --check-prefix=CHECK-RELEASE-HOISTING %s
|
|
// RUN: %target-sil-opt -enable-sil-verify-all -retain-sinking -retain-sinking -late-release-hoisting %s | %FileCheck --check-prefix=CHECK-MULTIPLE-RS-ROUNDS %s
|
|
|
|
import Builtin
|
|
import Swift
|
|
|
|
struct Int {
|
|
var value : Builtin.Int64
|
|
}
|
|
|
|
struct Int32 {
|
|
var value : Builtin.Int32
|
|
}
|
|
|
|
struct Int64 {
|
|
var value : Builtin.Int64
|
|
}
|
|
|
|
struct UInt64 {
|
|
var value : Builtin.Int64
|
|
}
|
|
|
|
struct Bool {
|
|
var value : Builtin.Int1
|
|
}
|
|
|
|
struct A {
|
|
var i : Builtin.Int32
|
|
}
|
|
|
|
class fuzz { }
|
|
|
|
protocol P : AnyObject { }
|
|
|
|
enum Boo {
|
|
case one
|
|
case two
|
|
}
|
|
|
|
class B { }
|
|
class E : B { }
|
|
|
|
class C {}
|
|
|
|
class C2 {
|
|
var current: A
|
|
init()
|
|
}
|
|
|
|
struct S {
|
|
var ptr : Builtin.NativeObject
|
|
}
|
|
|
|
struct foo {
|
|
var a: Int
|
|
init(a: Int)
|
|
init()
|
|
}
|
|
|
|
enum Optional<T> {
|
|
case none
|
|
case some(T)
|
|
}
|
|
|
|
struct Unowned {
|
|
@_hasStorage unowned let x: @sil_unowned Builtin.NativeObject
|
|
}
|
|
|
|
sil @createS : $@convention(thin) () -> @owned S
|
|
|
|
sil @use_C2 : $@convention(thin) (C2) -> ()
|
|
sil @user : $@convention(thin) (Builtin.NativeObject) -> ()
|
|
sil @user_int : $@convention(thin) (Int) -> ()
|
|
sil @optional_user : $@convention(thin) (Optional<Builtin.Int32>) -> ()
|
|
sil @blocker : $@convention(thin) () -> ()
|
|
sil @get_object : $@convention(thin) () -> Builtin.NativeObject
|
|
sil @foo_init : $@convention(thin) (@thin foo.Type) -> foo
|
|
sil [serialized] @guaranteed_use : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
|
|
sil [serialized] @guaranteed_throwing_use : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @error Error
|
|
|
|
// CHECK-LABEL: sil @sink_retains_from_preds : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
// CHECK: bb1:
|
|
// CHECK-NOT: strong_retain
|
|
// CHECK: bb2:
|
|
// CHECK-NOT: strong_retain
|
|
// CHECK: bb3:
|
|
// CHECK: strong_retain
|
|
sil @sink_retains_from_preds : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
strong_release %0 : $Builtin.NativeObject
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
br bb3
|
|
|
|
bb2:
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
br bb3
|
|
|
|
bb3:
|
|
%1 = tuple()
|
|
return %1 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @hoist_releases_from_succs : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
// CHECK: bb0(
|
|
// CHECK: strong_release
|
|
// CHECK: bb1:
|
|
// CHECK-NOT: strong_release
|
|
// CHECK: bb2:
|
|
// CHECK-NOT: strong_release
|
|
sil @hoist_releases_from_succs : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
strong_release %0 : $Builtin.NativeObject
|
|
br bb3
|
|
|
|
bb2:
|
|
strong_release %0 : $Builtin.NativeObject
|
|
br bb3
|
|
|
|
bb3:
|
|
retain_value %0 : $Builtin.NativeObject
|
|
%1 = tuple()
|
|
return %1 : $()
|
|
}
|
|
|
|
// Make sure that we do not move retains over unreachable terminator.
|
|
// CHECK-LABEL: sil @no_return_stops_codemotion : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
// CHECK-NOT: strong_retain
|
|
// CHECK: } // end sil function 'no_return_stops_codemotion'
|
|
sil @no_return_stops_codemotion : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
%1 = alloc_ref $C
|
|
strong_retain %1 : $C
|
|
unreachable
|
|
}
|
|
|
|
// CHECK-LABEL: sil @retain_blocked_by_maydecrement
|
|
// CHECK: strong_retain
|
|
// CHECK-NEXT: apply
|
|
sil @retain_blocked_by_maydecrement : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
release_value %0 : $Builtin.NativeObject
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
%2 = function_ref @blocker : $@convention(thin) () -> ()
|
|
apply %2() : $@convention(thin) () -> ()
|
|
br bb1
|
|
|
|
bb1:
|
|
%1 = tuple()
|
|
return %1 : $()
|
|
}
|
|
|
|
// Make sure release is not hoisted above define.
|
|
// CHECK-LABEL: sil @define_stops_codemotion : $@convention(thin) () -> () {
|
|
// CHECK: alloc_ref
|
|
// CHECK-NEXT: strong_release
|
|
sil @define_stops_codemotion : $@convention(thin) () -> () {
|
|
bb0:
|
|
%1 = alloc_ref $C
|
|
%2 = tuple()
|
|
strong_release %1 : $C
|
|
%3 = tuple()
|
|
return %3 : $()
|
|
}
|
|
|
|
// Make sure bb3 silargument blocks the release to be hoisted.
|
|
// CHECK-LABEL: sil @silargument_stops_codemotion
|
|
// CHECK: bb3([[IN:%[0-9]+]] : $C):
|
|
// CHECK: release
|
|
// CHECK: return
|
|
sil @silargument_stops_codemotion : $@convention(thin) () -> () {
|
|
bb0:
|
|
%1 = alloc_ref $C
|
|
%2 = alloc_ref $C
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
br bb3(%1 : $C)
|
|
|
|
bb2:
|
|
br bb3(%2 : $C)
|
|
|
|
bb3(%3 : $C):
|
|
strong_release %3 : $C
|
|
%4 = tuple()
|
|
return %4 : $()
|
|
}
|
|
|
|
// Make sure retain instruction is sunk across copy_addr inst, as copy_addr
|
|
// dest is initialized.
|
|
//
|
|
// CHECK-LABEL: retain_not_blocked_by_copyaddrinit
|
|
// CHECK: bb0
|
|
// CHECK-NEXT: strong_release
|
|
// CHECK-NEXT: copy_addr
|
|
// CHECK-NEXT: tuple
|
|
// CHECK-NEXT: strong_retain
|
|
sil hidden @retain_not_blocked_by_copyaddrinit : $@convention(thin) <T> (@in T, B) -> @out T {
|
|
bb0(%0 : $*T, %1 : $*T, %2 : $B):
|
|
strong_release %2 : $B
|
|
strong_retain %2 : $B
|
|
copy_addr [take] %1 to [initialization] %0 : $*T // id: %3
|
|
%4 = tuple () // user: %5
|
|
return %4 : $() // id: %5
|
|
}
|
|
|
|
// Make sure that is_unique stops code motion.
|
|
// CHECK-LABEL: sil @is_unique_stops_codemotion : $@convention(thin) (@inout Builtin.NativeObject) -> () {
|
|
// CHECK: bb0([[IN:%[0-9]+]] : $*Builtin.NativeObject):
|
|
// CHECK: [[LD:%[0-9]+]] = load [[IN]] : $*Builtin.NativeObject
|
|
// CHECK: strong_retain [[LD]] : $Builtin.NativeObject
|
|
// CHECK: is_unique [[IN]] : $*Builtin.NativeObject
|
|
sil @is_unique_stops_codemotion : $@convention(thin) (@inout Builtin.NativeObject) -> () {
|
|
bb0(%0 : $*Builtin.NativeObject):
|
|
%1 = load %0 : $*Builtin.NativeObject
|
|
strong_retain %1 : $Builtin.NativeObject
|
|
is_unique %0 : $*Builtin.NativeObject
|
|
%9999 = tuple()
|
|
return %9999 : $()
|
|
}
|
|
|
|
// Make sure that is_unique stops code motion.
|
|
// CHECK-LABEL: sil @retain_inserted_deterministically : $@convention(thin)
|
|
// CHECK: bb0([[IN0:%[0-9]+]] : $Builtin.NativeObject, [[IN1:%[0-9]+]] : $Builtin.NativeObject, [[IN2:%[0-9]+]] : $Builtin.NativeObject, [[IN3:%[0-9]+]] : $Builtin.NativeObject):
|
|
// CHECK: strong_retain [[IN1]] : $Builtin.NativeObject
|
|
// CHECK: strong_retain [[IN3]] : $Builtin.NativeObject
|
|
// CHECK: strong_retain [[IN2]] : $Builtin.NativeObject
|
|
// CHECK: strong_retain [[IN0]] : $Builtin.NativeObject
|
|
sil @retain_inserted_deterministically : $@convention(thin) (@owned Builtin.NativeObject, @owned Builtin.NativeObject, @owned Builtin.NativeObject, @owned Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject, %1 : $Builtin.NativeObject, %2 : $Builtin.NativeObject, %3 : $Builtin.NativeObject):
|
|
strong_retain %1 : $Builtin.NativeObject
|
|
strong_retain %3 : $Builtin.NativeObject
|
|
strong_retain %2 : $Builtin.NativeObject
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
%9999 = tuple()
|
|
%9998 = function_ref @blocker : $@convention(thin) () -> ()
|
|
apply %9998() : $@convention(thin) () -> ()
|
|
return %9999 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @sink_retain_partially_block : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
// CHECK: bb3:
|
|
// CHECK-NOT: string_retain
|
|
// CHECK: return
|
|
sil @sink_retain_partially_block : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
%2 = function_ref @blocker : $@convention(thin) () -> ()
|
|
apply %2() : $@convention(thin) () -> ()
|
|
br bb3
|
|
|
|
bb2:
|
|
br bb3
|
|
|
|
bb3:
|
|
%5 = tuple()
|
|
return %5 : $()
|
|
}
|
|
|
|
final class MyArrayBuffer {
|
|
@_hasStorage var dummyElements: Int32
|
|
init()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @builtin_does_not_block_locally_allocated_ref
|
|
// CHECK: builtin
|
|
// CHECK-NEXT: return
|
|
sil @builtin_does_not_block_locally_allocated_ref : $@convention(thin) () -> @owned MyArrayBuffer {
|
|
bb0:
|
|
%3 = integer_literal $Builtin.Word, 3
|
|
%8 = alloc_ref $MyArrayBuffer
|
|
%74 = metatype $@thick String.Type
|
|
%67 = ref_element_addr %8 : $MyArrayBuffer, #MyArrayBuffer.dummyElements
|
|
%68 = address_to_pointer %67 : $*Int32 to $Builtin.RawPointer
|
|
strong_retain %8 : $MyArrayBuffer
|
|
%77 = builtin "destroyArray"<String>(%74 : $@thick String.Type, %68 : $Builtin.RawPointer, %3 : $Builtin.Word) : $()
|
|
strong_release %8 : $MyArrayBuffer
|
|
return %8 : $MyArrayBuffer
|
|
}
|
|
// CHECK-LABEL: sil @hoist_release_partially_available_retain
|
|
// CHECK: bb0
|
|
// CHECK: cond_br undef, bb1, bb2
|
|
// CHECK: bb1:
|
|
// CHECK-NEXT: br bb3
|
|
// CHECK: bb2:
|
|
// CHECK: strong_retain
|
|
// CHECK: apply
|
|
// CHECK: strong_release
|
|
// CHECK: br bb3
|
|
// CHECK: bb3:
|
|
// CHECK-NOT: strong_release
|
|
// CHECK: return
|
|
sil @hoist_release_partially_available_retain : $@convention(thin) (Builtin.NativeObject, Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject, %1: $Builtin.NativeObject):
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
strong_retain %0: $Builtin.NativeObject
|
|
br bb3
|
|
|
|
bb2:
|
|
strong_retain %0: $Builtin.NativeObject
|
|
%2 = function_ref @blocker : $@convention(thin) () -> ()
|
|
apply %2() : $@convention(thin) () -> ()
|
|
br bb3
|
|
|
|
bb3:
|
|
strong_release %0: $Builtin.NativeObject
|
|
%5 = tuple()
|
|
return %5 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil [serialized] @try_apply_blocks_release_hoisting : $@convention(thin) (Builtin.NativeObject) -> @error Error {
|
|
// CHECK: bb0(
|
|
// CHECK: strong_retain
|
|
// CHECK: try_apply
|
|
// CHECK: bb1(
|
|
// CHECK: strong_release
|
|
// CHECK: bb2(
|
|
// CHECK: strong_release
|
|
sil [serialized] @try_apply_blocks_release_hoisting : $@convention(thin) (Builtin.NativeObject) -> @error Error {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
%1 = function_ref @guaranteed_use : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
|
|
apply %1(%0) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
|
|
%2 = function_ref @guaranteed_throwing_use : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @error Error
|
|
try_apply %2(%0) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> @error Error, normal bb1, error bb2
|
|
|
|
bb1(%3 : $()):
|
|
strong_release %0 : $Builtin.NativeObject
|
|
return undef : $()
|
|
|
|
bb2(%4 : $Error):
|
|
strong_release %0 : $Builtin.NativeObject
|
|
throw %4 : $Error
|
|
}
|
|
|
|
|
|
// Make sure release can be hoisted across memory that do not escape.
|
|
// CHECK-LABEL: sil @hoist_release_across_local_memory_use
|
|
// CHECK: bb1:
|
|
// CHECK-NEXT: br bb3
|
|
// CHECK: bb2:
|
|
// CHECK: strong_release
|
|
// CHECK: br bb3
|
|
// CHECK: bb3:
|
|
// CHECK-NOT: strong_release
|
|
// CHECK: return
|
|
sil @hoist_release_across_local_memory_use : $@convention(thin) (Builtin.NativeObject) ->() {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
%1 = alloc_stack $A
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
br bb3
|
|
|
|
bb2:
|
|
br bb3
|
|
|
|
bb3:
|
|
%2 = integer_literal $Builtin.Int32, 0
|
|
%3 = struct $A (%2 : $Builtin.Int32)
|
|
store %3 to %1 : $*A
|
|
strong_release %0 : $Builtin.NativeObject
|
|
dealloc_stack %1 : $*A
|
|
%5 = tuple()
|
|
return %5 : $()
|
|
}
|
|
|
|
// Make sure release can not be hoisted across memory that do escape. i.e. the release
|
|
// deinit can read or write to the memory.
|
|
// CHECK-LABEL: sil @hoist_release_across_escaping_param_memory_use
|
|
// CHECK: bb1:
|
|
// CHECK-NOT: strong_release
|
|
// CHECK: br bb3
|
|
// CHECK: bb2:
|
|
// CHECK-NOT: strong_release
|
|
// CHECK: br bb3
|
|
// CHECK: bb3:
|
|
// CHECK: store
|
|
// CHECK: strong_release
|
|
// CHECK: return
|
|
sil @hoist_release_across_escaping_param_memory_use : $@convention(thin) (Builtin.NativeObject, C2) ->() {
|
|
bb0(%0 : $Builtin.NativeObject, %1 : $C2):
|
|
%2 = ref_element_addr %1 : $C2, #C2.current
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
br bb3
|
|
|
|
bb2:
|
|
br bb3
|
|
|
|
bb3:
|
|
%3 = integer_literal $Builtin.Int32, 0
|
|
%4 = struct $A (%3 : $Builtin.Int32)
|
|
store %4 to %2 : $*A
|
|
strong_release %0 : $Builtin.NativeObject
|
|
%5 = tuple()
|
|
return %5 : $()
|
|
}
|
|
|
|
// Make sure release can not be hoisted across memory that do escape, even though its allocated locally.
|
|
// i.e. the release
|
|
// deinit can read or write to the memory.
|
|
// CHECK-LABEL: sil @hoist_release_across_escaping_local_alloc_memory_use
|
|
// CHECK: bb1:
|
|
// CHECK-NOT: strong_release
|
|
// CHECK: br bb3
|
|
// CHECK: bb2:
|
|
// CHECK-NOT: strong_release
|
|
// CHECK: br bb3
|
|
// CHECK: bb3:
|
|
// CHECK: store
|
|
// CHECK: strong_release
|
|
// CHECK: return
|
|
sil @hoist_release_across_escaping_local_alloc_memory_use : $@convention(thin) (Builtin.NativeObject) ->() {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
%1 = alloc_ref $C2
|
|
%2 = ref_element_addr %1 : $C2, #C2.current
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
br bb3
|
|
|
|
bb2:
|
|
br bb3
|
|
|
|
bb3:
|
|
%22 = function_ref @use_C2 : $@convention(thin) (C2) -> ()
|
|
%23 = apply %22(%1) : $@convention(thin) (C2) -> ()
|
|
%3 = integer_literal $Builtin.Int32, 0
|
|
%4 = struct $A (%3 : $Builtin.Int32)
|
|
store %4 to %2 : $*A
|
|
strong_release %0 : $Builtin.NativeObject
|
|
%5 = tuple()
|
|
return %5 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @move_retain_over_loop
|
|
// CHECK: bb0({{.*}}):
|
|
// CHECK-NEXT: br bb1
|
|
// CHECK: bb1:
|
|
// CHECK-NEXT: cond_br
|
|
// CHECK: bb2:
|
|
// CHECK: strong_retain
|
|
// CHECK: apply
|
|
// CHECK: strong_release
|
|
// CHECK: return
|
|
sil @move_retain_over_loop : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
br bb1
|
|
|
|
bb1:
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb2:
|
|
%2 = function_ref @blocker : $@convention(thin) () -> ()
|
|
apply %2() : $@convention(thin) () -> ()
|
|
strong_release %0 : $Builtin.NativeObject
|
|
%1 = tuple()
|
|
return %1 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @move_release_over_loop
|
|
// CHECK: bb0{{.*}}:
|
|
// CHECK: strong_retain
|
|
// CHECK: apply
|
|
// CHECK: strong_release
|
|
// CHECK: br bb1
|
|
// CHECK: bb1:
|
|
// CHECK-NEXT: cond_br
|
|
// CHECK: bb2:
|
|
// CHECK-NEXT: br bb1
|
|
// CHECK: bb3:
|
|
// CHECK-NEXT: tuple
|
|
// CHECK-NEXT: return
|
|
sil @move_release_over_loop : $@convention(thin) (Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
%2 = function_ref @blocker : $@convention(thin) () -> ()
|
|
apply %2() : $@convention(thin) () -> ()
|
|
br bb1
|
|
|
|
bb1:
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb2:
|
|
strong_release %0 : $Builtin.NativeObject
|
|
%1 = tuple()
|
|
return %1 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @handle_infinite_loop
|
|
// CHECK: bb0{{.*}}:
|
|
// CHECK-NEXT: cond_br
|
|
// CHECK: bb1:
|
|
// CHECK-NOT: {{(retain|release)}}
|
|
// CHECK: apply
|
|
// CHECK-NEXT: br bb2
|
|
// CHECK: bb2:
|
|
// CHECK-NEXT: br bb2
|
|
// CHECK: bb3:
|
|
// CHECK-NEXT: tuple
|
|
// CHECK-NEXT: return
|
|
sil @handle_infinite_loop : $@convention(thin) (@inout Builtin.NativeObject) -> () {
|
|
bb0(%a : $*Builtin.NativeObject):
|
|
cond_br undef, bb1, bb3
|
|
|
|
bb1:
|
|
%2 = function_ref @blocker : $@convention(thin) () -> ()
|
|
apply %2() : $@convention(thin) () -> ()
|
|
br bb2
|
|
|
|
bb2:
|
|
br bb2
|
|
|
|
bb3:
|
|
%0 = load %a : $*Builtin.NativeObject
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
strong_release %0 : $Builtin.NativeObject
|
|
%1 = tuple()
|
|
return %1 : $()
|
|
}
|
|
|
|
/// Check that retain sinking needs multiple-passes to sink 2 retains.
|
|
|
|
/// One round of retain-sinking can sink only one of retains.
|
|
/// CHECK-LABEL: sil @checkRetainSinkingMultipleRounds
|
|
/// CHECK: bb9:
|
|
/// CHECK-NEXT: release_value %2 : $S
|
|
/// In the ideal world, we should see a third retain_value here.
|
|
/// But it would require another round of retain sinking.
|
|
/// CHECK-NEXT: br bb5
|
|
|
|
|
|
/// Two rounds of retain-sinking can sink only two retains.
|
|
/// CHECK-MULTIPLE-RS-ROUNDS-LABEL: sil @checkRetainSinkingMultipleRounds
|
|
/// CHECK-MULTIPLE-RS-ROUNDS: bb9:
|
|
/// CHECK-MULTIPLE-RS-ROUNDS-NEXT: retain_value %2 : $S
|
|
/// CHECK-MULTIPLE-RS-ROUNDS-NEXT: release_value %2 : $S
|
|
/// CHECK-MULTIPLE-RS-ROUNDS-NEXT: br bb5
|
|
|
|
sil @checkRetainSinkingMultipleRounds : $@convention(thin) (Int) -> () {
|
|
bb0(%0 : $Int):
|
|
%1 = function_ref @createS : $@convention(thin) () -> @owned S
|
|
%2 = apply %1() : $@convention(thin) () -> @owned S
|
|
br bb2
|
|
|
|
bb1:
|
|
cond_br undef, bb10, bb11
|
|
|
|
bb2:
|
|
cond_br undef, bb4, bb6
|
|
|
|
bb3:
|
|
br bb2
|
|
|
|
bb4:
|
|
retain_value %2 : $S
|
|
br bb5
|
|
|
|
bb5:
|
|
release_value %2 : $S
|
|
cond_br undef, bb1, bb3
|
|
|
|
bb6:
|
|
retain_value %2 : $S
|
|
retain_value %2 : $S
|
|
br bb7
|
|
|
|
bb7:
|
|
cond_br undef, bb9, bb8
|
|
|
|
bb8:
|
|
br bb7
|
|
|
|
bb9:
|
|
release_value %2 : $S
|
|
br bb5
|
|
|
|
bb10:
|
|
unreachable
|
|
|
|
bb11:
|
|
release_value %2 : $S
|
|
%26 = tuple ()
|
|
return %26 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @detect_escape_of_bbarg
|
|
// CHECK: bb3({{.*}}):
|
|
// CHECK-NEXT: strong_retain
|
|
// CHECK-NEXT: apply
|
|
// CHECK-NEXT: strong_release
|
|
sil @detect_escape_of_bbarg : $@convention(thin) () -> () {
|
|
bb0:
|
|
%f = function_ref @use_C2 : $@convention(thin) (C2) -> ()
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
%a = alloc_ref $C2
|
|
br bb3(%a: $C2, %a: $C2)
|
|
|
|
bb2:
|
|
%b = alloc_ref $C2
|
|
br bb3(%b: $C2, %b: $C2)
|
|
|
|
bb3(%p1: $C2, %p2: $C2):
|
|
strong_retain %p1: $C2 // This retain must not be moved over the apply
|
|
%c = apply %f(%p2) : $@convention(thin) (C2) -> ()
|
|
strong_release %p2: $C2
|
|
%10 = tuple ()
|
|
return %10 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @test_unowned
|
|
// CHECK: bb0(%0 : $Builtin.NativeObject):
|
|
// CHECK-NEXT: br bb1
|
|
// CHECK: bb1:
|
|
// CHECK-NEXT: tuple
|
|
// CHECK-NEXT: return
|
|
sil @test_unowned : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
%1 = ref_to_unowned %0 : $Builtin.NativeObject to $@sil_unowned Builtin.NativeObject
|
|
%2 = struct $Unowned (%1 : $@sil_unowned Builtin.NativeObject)
|
|
retain_value %2: $Unowned
|
|
br bb1
|
|
|
|
bb1:
|
|
release_value %2: $Unowned
|
|
%5 = tuple()
|
|
return %5 : $()
|
|
}
|
|
|
|
// CHECK-RELEASE-HOISTING-LABEL: sil @dont_eliminate_strong_retain_and_unowned_release_pair
|
|
// CHECK-RELEASE-HOISTING: = function_ref @blocker
|
|
// CHECK-RELEASE-HOISTING-NEXT: apply
|
|
// CHECK-RELEASE-HOISTING-NEXT: strong_retain %0 : $Builtin.NativeObject
|
|
// CHECK-RELEASE-HOISTING-NEXT: unowned_release %1 : $@sil_unowned Builtin.NativeObject
|
|
sil @dont_eliminate_strong_retain_and_unowned_release_pair : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
%1 = ref_to_unowned %0 : $Builtin.NativeObject to $@sil_unowned Builtin.NativeObject
|
|
%2 = struct $Unowned (%1 : $@sil_unowned Builtin.NativeObject)
|
|
retain_value %2: $Unowned
|
|
%b = function_ref @blocker : $@convention(thin) () -> ()
|
|
apply %b() : $@convention(thin) () -> ()
|
|
strong_retain %0: $Builtin.NativeObject
|
|
br bb1
|
|
|
|
bb1:
|
|
release_value %2: $Unowned
|
|
apply %b() : $@convention(thin) () -> ()
|
|
strong_release %0: $Builtin.NativeObject
|
|
%5 = tuple()
|
|
return %5 : $()
|
|
}
|
|
|
|
sil [_semantics "programtermination_point"] @fatalError : $@convention(thin) () -> Never
|
|
|
|
// This should eliminate all retains except for the first one b/c of user.
|
|
// CHECK-LABEL: sil @eliminate_retains_on_fatalError_path_single_bb : $@convention(thin) (@owned Builtin.NativeObject) -> () {
|
|
// CHECK: strong_retain
|
|
// CHECK-NOT: strong_retain
|
|
// CHECK: } // end sil function 'eliminate_retains_on_fatalError_path_single_bb'
|
|
sil @eliminate_retains_on_fatalError_path_single_bb : $@convention(thin) (@owned Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
%2 = function_ref @user : $@convention(thin) (Builtin.NativeObject) -> ()
|
|
apply %2(%0) : $@convention(thin) (Builtin.NativeObject) -> ()
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
%1 = function_ref @fatalError : $@convention(thin) () -> Never
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
apply %1() : $@convention(thin) () -> Never
|
|
unreachable
|
|
}
|
|
|
|
// We should eliminate all retains except for the one that we sink into the
|
|
// return block.
|
|
//
|
|
// CHECK-LABEL: sil @eliminate_retains_on_fatalError_path_diamond : $@convention(thin) (@owned Builtin.NativeObject) -> () {
|
|
// CHECK-NOT: strong_retain
|
|
// CHECK: bb4:
|
|
// CHECK: strong_retain
|
|
// CHECK-NOT: strong_retain
|
|
// CHECK: } // end sil function 'eliminate_retains_on_fatalError_path_diamond'
|
|
sil @eliminate_retains_on_fatalError_path_diamond : $@convention(thin) (@owned Builtin.NativeObject) -> () {
|
|
bb0(%0 : $Builtin.NativeObject):
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
cond_br undef, bb1, bb2
|
|
|
|
bb1:
|
|
cond_br undef, bb3, bb4
|
|
|
|
bb3:
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
%1 = function_ref @fatalError : $@convention(thin) () -> Never
|
|
strong_retain %0 : $Builtin.NativeObject
|
|
apply %1() : $@convention(thin) () -> Never
|
|
unreachable
|
|
|
|
bb4:
|
|
unreachable
|
|
|
|
bb2:
|
|
%9999 = tuple()
|
|
return %9999 : $()
|
|
}
|
|
|
|
// Don't remove a retain of an AnyObject which comes from a metatype.
|
|
//
|
|
// CHECK-LABEL: sil @conditional_metatype_cast
|
|
// CHECK: bb2([[ARG:%[0-9]+]] : $AnyObject):
|
|
// CHECK: strong_retain [[ARG]]
|
|
// CHECK: checked_cast_addr_br
|
|
// CHECK: } // end sil function 'conditional_metatype_cast'
|
|
sil @conditional_metatype_cast : $@convention(thin) () -> AnyObject {
|
|
bb0:
|
|
%0 = metatype $@thick Int.Type
|
|
checked_cast_br %0 : $@thick Int.Type to AnyObject, bb2, bb1
|
|
|
|
bb1:
|
|
unreachable
|
|
|
|
bb2(%6 : $AnyObject):
|
|
strong_retain %6 : $AnyObject
|
|
%9 = alloc_stack $AnyObject
|
|
store %6 to %9 : $*AnyObject
|
|
%11 = alloc_stack $@thick Int.Type
|
|
checked_cast_addr_br take_always AnyObject in %9 : $*AnyObject to Int.Type in %11 : $*@thick Int.Type, bb3, bb4
|
|
|
|
bb3:
|
|
dealloc_stack %11 : $*@thick Int.Type
|
|
dealloc_stack %9 : $*AnyObject
|
|
return %6 : $AnyObject
|
|
|
|
bb4:
|
|
unreachable
|
|
}
|
|
|
|
// Don't remove a retain of an AnyObject which comes from a metatype.
|
|
//
|
|
// CHECK-LABEL: sil @unconditional_metatype_cast
|
|
// CHECK: [[O:%[0-9]+]] = unconditional_checked_cast
|
|
// CHECK: strong_retain [[O]]
|
|
// CHECK: checked_cast_addr_br
|
|
// CHECK: } // end sil function 'unconditional_metatype_cast'
|
|
sil @unconditional_metatype_cast : $@convention(thin) () -> AnyObject {
|
|
bb0:
|
|
%0 = metatype $@thick Int.Type
|
|
%6 = unconditional_checked_cast %0 : $@thick Int.Type to AnyObject
|
|
strong_retain %6 : $AnyObject
|
|
%9 = alloc_stack $AnyObject
|
|
store %6 to %9 : $*AnyObject
|
|
%11 = alloc_stack $@thick Int.Type
|
|
checked_cast_addr_br take_always AnyObject in %9 : $*AnyObject to Int.Type in %11 : $*@thick Int.Type, bb3, bb4
|
|
|
|
bb3:
|
|
dealloc_stack %11 : $*@thick Int.Type
|
|
dealloc_stack %9 : $*AnyObject
|
|
return %6 : $AnyObject
|
|
|
|
bb4:
|
|
unreachable
|
|
}
|
|
|
|
|
|
|
|
// Hoist releases above dealloc_stack
|
|
// CHECK-LABEL: sil @testReleaseHoistDeallocStack : $@convention(thin) (AnyObject) -> () {
|
|
// CHECK: bb0(%0 : $AnyObject):
|
|
// CHECK-NOT: retain
|
|
// CHECK: [[A:%.*]] = alloc_stack $Int64
|
|
// CHECK-NEXT: dealloc_stack [[A]] : $*Int64
|
|
// CHECK-NOT: release
|
|
// CHECK-LABEL: } // end sil function 'testReleaseHoistDeallocStack'
|
|
sil @testReleaseHoistDeallocStack : $@convention(thin) (AnyObject)->() {
|
|
bb0(%0 : $AnyObject):
|
|
strong_retain %0 : $AnyObject
|
|
%alloc = alloc_stack $Int64
|
|
dealloc_stack %alloc : $*Int64
|
|
strong_release %0 : $AnyObject
|
|
%34 = tuple ()
|
|
return %34 : $()
|
|
}
|
|
|
|
// Do not hoist releases above builtins that operate on object references.
|
|
//
|
|
// CHECK-RELEASE-HOISTING-LABEL: sil @testCopyArray : $@convention(thin) (_ContiguousArrayBuffer<AnyObject>, Builtin.Word, Builtin.Word) -> Builtin.RawPointer {
|
|
// CHECK-RELEASE-HOISTING: bb0(%0 : $_ContiguousArrayBuffer<AnyObject>, %1 : $Builtin.Word, %2 : $Builtin.Word):
|
|
// CHECK-RELEASE-HOISTING: builtin "copyArray"<AnyObject>
|
|
// CHECK-RELEASE-HOISTING: release_value %0 : $_ContiguousArrayBuffer<AnyObject>
|
|
// CHECK-RELEASE-HOISTING-LABEL: } // end sil function 'testCopyArray'
|
|
sil @testCopyArray : $@convention(thin) (_ContiguousArrayBuffer<AnyObject>, Builtin.Word, Builtin.Word) -> Builtin.RawPointer {
|
|
bb0(%0 : $_ContiguousArrayBuffer<AnyObject>, %1 : $Builtin.Word, %2 : $Builtin.Word):
|
|
%eltty = metatype $@thick AnyObject.Protocol
|
|
%newptr = builtin "allocRaw"(%1 : $Builtin.Word, %1 : $Builtin.Word) : $Builtin.RawPointer
|
|
bind_memory %newptr : $Builtin.RawPointer, %1 : $Builtin.Word to $*AnyObject
|
|
%storage = struct_extract %0 : $_ContiguousArrayBuffer<AnyObject>, #_ContiguousArrayBuffer._storage
|
|
%elements = ref_tail_addr %storage : $__ContiguousArrayStorageBase, $AnyObject
|
|
%eltptr = address_to_pointer %elements : $*AnyObject to $Builtin.RawPointer
|
|
%objptr = struct $UnsafePointer<AnyObject> (%eltptr : $Builtin.RawPointer)
|
|
%ptrdep = mark_dependence %objptr : $UnsafePointer<AnyObject> on %storage : $__ContiguousArrayStorageBase
|
|
%rawptr = struct_extract %ptrdep : $UnsafePointer<AnyObject>, #UnsafePointer._rawValue
|
|
%copy = builtin "copyArray"<AnyObject>(%eltty : $@thick AnyObject.Protocol, %newptr : $Builtin.RawPointer, %rawptr : $Builtin.RawPointer, %1 : $Builtin.Word) : $()
|
|
release_value %0 : $_ContiguousArrayBuffer<AnyObject>
|
|
return %newptr : $Builtin.RawPointer
|
|
}
|
|
|
|
// CHECK-LABEL: sil @dontMoveOverExistentialToClassCast : $@convention(thin) (@guaranteed AnyObject) -> Optional<fuzz>
|
|
// CHECK: strong_retain %0
|
|
// CHECK: checked_cast_br %0
|
|
// CHECK: } // end sil function 'dontMoveOverExistentialToClassCast'
|
|
sil @dontMoveOverExistentialToClassCast : $@convention(thin) (@guaranteed AnyObject) -> Optional<fuzz> {
|
|
bb0(%0 : $AnyObject):
|
|
strong_retain %0 : $AnyObject
|
|
checked_cast_br %0 : $AnyObject to fuzz, bb1, bb2
|
|
|
|
bb1(%18 : $fuzz):
|
|
%19 = enum $Optional<fuzz>, #Optional.some!enumelt, %18 : $fuzz
|
|
br bb3(%19 : $Optional<fuzz>)
|
|
|
|
bb2:
|
|
strong_release %0 : $AnyObject
|
|
%22 = enum $Optional<fuzz>, #Optional.none!enumelt
|
|
br bb3(%22 : $Optional<fuzz>)
|
|
|
|
bb3(%24 : $Optional<fuzz>):
|
|
return %24 : $Optional<fuzz>
|
|
}
|
|
|
|
// CHECK-LABEL: sil @dontMoveOverClassToExistentialCast : $@convention(thin) (@guaranteed fuzz) -> Optional<P>
|
|
// CHECK: strong_retain %0
|
|
// CHECK: checked_cast_br %0
|
|
// CHECK: } // end sil function 'dontMoveOverClassToExistentialCast'
|
|
sil @dontMoveOverClassToExistentialCast : $@convention(thin) (@guaranteed fuzz) -> Optional<P> {
|
|
bb0(%0 : $fuzz):
|
|
strong_retain %0 : $fuzz
|
|
checked_cast_br %0 : $fuzz to P, bb1, bb2
|
|
|
|
bb1(%18 : $P):
|
|
%19 = enum $Optional<P>, #Optional.some!enumelt, %18 : $P
|
|
br bb3(%19 : $Optional<P>)
|
|
|
|
bb2:
|
|
strong_release %0 : $fuzz
|
|
%22 = enum $Optional<P>, #Optional.none!enumelt
|
|
br bb3(%22 : $Optional<P>)
|
|
|
|
bb3(%24 : $Optional<P>):
|
|
return %24 : $Optional<P>
|
|
}
|
|
|
|
// CHECK-LABEL: sil @dontMoveOverExistentialToExistentialCast : $@convention(thin) (@guaranteed AnyObject) -> Optional<P>
|
|
// CHECK: strong_retain %0
|
|
// CHECK: checked_cast_br %0
|
|
// CHECK: } // end sil function 'dontMoveOverExistentialToExistentialCast'
|
|
sil @dontMoveOverExistentialToExistentialCast : $@convention(thin) (@guaranteed AnyObject) -> Optional<P> {
|
|
bb0(%0 : $AnyObject):
|
|
strong_retain %0 : $AnyObject
|
|
checked_cast_br %0 : $AnyObject to P, bb1, bb2
|
|
|
|
bb1(%18 : $P):
|
|
%19 = enum $Optional<P>, #Optional.some!enumelt, %18 : $P
|
|
br bb3(%19 : $Optional<P>)
|
|
|
|
bb2:
|
|
strong_release %0 : $AnyObject
|
|
%22 = enum $Optional<P>, #Optional.none!enumelt
|
|
br bb3(%22 : $Optional<P>)
|
|
|
|
bb3(%24 : $Optional<P>):
|
|
return %24 : $Optional<P>
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Test EscapeAnalysis::mayReleaseContent
|
|
// -----------------------------------------------------------------------------
|
|
|
|
class Node {
|
|
var node: Node
|
|
}
|
|
struct Queue {
|
|
var node: Node
|
|
}
|
|
|
|
sil @getNode : $@convention(thin) () -> @owned Node
|
|
|
|
// testInoutRelease helper.
|
|
//
|
|
// To test a corner case in canApplyDecrementRefCount, this function
|
|
// cannot have "mayReadRC" or "global mayRelease" side effects.
|
|
sil [ossa] @setNode : $@convention(thin) (@owned Node, @inout Queue) -> () {
|
|
bb0(%0 : @owned $Node, %1 : $*Queue):
|
|
%new = struct $Queue(%0 : $Node)
|
|
store %new to [assign] %1 : $*Queue
|
|
%6 = tuple ()
|
|
return %6 : $()
|
|
}
|
|
|
|
// When analyzing this function for retain code motion,
|
|
// AliasAnalysis::canApplyDecrementRefCount will query
|
|
// EscapeAnalysis::mayReleaseContent, passing it an inout pointer for
|
|
// the "released reference".
|
|
//
|
|
// rdar://74360041 (Assertion failed:
|
|
// (!releasedReference->getType().isAddress() && "an address is never
|
|
// a reference"), function mayReleaseContent
|
|
//
|
|
// CHECK-LABEL: sil @testInoutRelease : $@convention(thin) (@inout Queue) -> () {
|
|
// CHECK: strong_retain
|
|
// CHECK: apply {{.*}} : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
// CHECK-LABEL: } // end sil function 'testInoutRelease'
|
|
sil @testInoutRelease : $@convention(thin) (@inout Queue) -> () {
|
|
bb0(%0 : $*Queue):
|
|
%get = function_ref @getNode : $@convention(thin) () -> @owned Node
|
|
%node = apply %get() : $@convention(thin) () -> @owned Node
|
|
%set = function_ref @setNode : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
strong_retain %node : $Node
|
|
%call = apply %set(%node, %0) : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
%12 = tuple ()
|
|
return %12 : $()
|
|
}
|
|
|
|
// Still cannot sink the retain because %extraNode is not unique.
|
|
//
|
|
// CHECK-LABEL: sil @testMayReleaseNoSink : $@convention(thin) (@inout Queue) -> () {
|
|
// CHECK: strong_retain
|
|
// CHECK: apply {{.*}} : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
// CHECK-LABEL: } // end sil function 'testMayReleaseNoSink'
|
|
sil @testMayReleaseNoSink : $@convention(thin) (@inout Queue) -> () {
|
|
bb0(%0 : $*Queue):
|
|
%get = function_ref @getNode : $@convention(thin) () -> @owned Node
|
|
%node = apply %get() : $@convention(thin) () -> @owned Node
|
|
%extraNode = apply %get() : $@convention(thin) () -> @owned Node
|
|
%set = function_ref @setNode : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
strong_retain %extraNode : $Node
|
|
%call = apply %set(%node, %0) : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
%12 = tuple ()
|
|
return %12 : $()
|
|
}
|
|
|
|
// The retain of extraNode can sink here because is unique within this
|
|
// function and not released by setNode.
|
|
//
|
|
// CHECK-LABEL: sil @testMayReleaseSink : $@convention(thin) (@inout Queue) -> () {
|
|
// CHECK: apply {{.*}} : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
// CHECK: strong_retain
|
|
// CHECK-LABEL: } // end sil function 'testMayReleaseSink'
|
|
sil @testMayReleaseSink : $@convention(thin) (@inout Queue) -> () {
|
|
bb0(%0 : $*Queue):
|
|
%get = function_ref @getNode : $@convention(thin) () -> @owned Node
|
|
%node = apply %get() : $@convention(thin) () -> @owned Node
|
|
%extraNode = alloc_ref $Node
|
|
%set = function_ref @setNode : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
strong_retain %extraNode : $Node
|
|
%call = apply %set(%node, %0) : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
%12 = tuple ()
|
|
return %12 : $()
|
|
}
|
|
|
|
// Both the queue and node are unique and non-escaping, but we still
|
|
// can't sink because the queue may point to the node.
|
|
//
|
|
// CHECK-LABEL: sil @testMayReleaseUniqNoSink : $@convention(thin) () -> () {
|
|
// CHECK: strong_retain
|
|
// CHECK: apply {{.*}} : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
// CHECK-LABEL: } // end sil function 'testMayReleaseUniqNoSink'
|
|
sil @testMayReleaseUniqNoSink : $@convention(thin) () -> () {
|
|
bb0:
|
|
%queue = alloc_stack $Queue
|
|
%extraNode = alloc_ref $Node
|
|
%set = function_ref @setNode : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
strong_retain %extraNode : $Node
|
|
%call = apply %set(%extraNode, %queue) : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
dealloc_stack %queue : $*Queue
|
|
%12 = tuple ()
|
|
return %12 : $()
|
|
}
|
|
|
|
// %queue is unique, but %extraNode is not. We can still sink because
|
|
// there's no connection.
|
|
//
|
|
// CHECK-LABEL: sil @testMayReleaseUniqSink : $@convention(thin) () -> () {
|
|
// CHECK: apply {{.*}} : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
// CHECK: strong_retain
|
|
// CHECK-LABEL: } // end sil function 'testMayReleaseUniqSink'
|
|
sil @testMayReleaseUniqSink : $@convention(thin) () -> () {
|
|
bb0:
|
|
%queue = alloc_stack $Queue
|
|
%get = function_ref @getNode : $@convention(thin) () -> @owned Node
|
|
%extraNode = apply %get() : $@convention(thin) () -> @owned Node
|
|
%node = alloc_ref $Node
|
|
%set = function_ref @setNode : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
strong_retain %extraNode : $Node
|
|
%call = apply %set(%node, %queue) : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
dealloc_stack %queue : $*Queue
|
|
%12 = tuple ()
|
|
return %12 : $()
|
|
}
|
|
|
|
// CG: %queue -> %node1 -> %node2
|
|
//
|
|
// If the retain of %node2 sink below the call to @setNode, then when
|
|
// @inout %queue is overwritten by setNode, both node1 and node2 are
|
|
// freed. The subsequent retain of %node2 will crash.
|
|
//
|
|
// This used to be allowed because we reasoned that a local reference
|
|
// to %node2 should have "its own retain". This is true, but it's
|
|
// still important not to sink that retain for that local reference
|
|
// below a call where that object may be indirectly released via
|
|
// another destroyed object.
|
|
//
|
|
// CHECK-LABEL: sil @testMayReleaseIndirectSink : $@convention(thin) () -> () {
|
|
// CHECK: strong_retain
|
|
// CHECK: apply {{.*}} : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
// CHECK-LABEL: } // end sil function 'testMayReleaseIndirectSink'
|
|
sil @testMayReleaseIndirectSink : $@convention(thin) () -> () {
|
|
bb0:
|
|
%set = function_ref @setNode : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
%queue = alloc_stack $Queue
|
|
%node1 = alloc_ref $Node
|
|
%node2 = alloc_ref $Node
|
|
%addr = ref_element_addr %node1 : $Node, #Node.node
|
|
store %node2 to %addr : $*Node
|
|
%call1 = apply %set(%node1, %queue) : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
%extraNode = alloc_ref $Node
|
|
strong_retain %node2 : $Node
|
|
// This call destroys %node1 which releases %node2.
|
|
%call2 = apply %set(%extraNode, %queue) : $@convention(thin) (@owned Node, @inout Queue) -> ()
|
|
dealloc_stack %queue : $*Queue
|
|
%12 = tuple ()
|
|
return %12 : $()
|
|
}
|