blob: cfa2047ec12cce9af78dd6aff8b6c604a5702712 [file] [log] [blame]
// RUN: %target-sil-opt -assume-parsing-unqualified-ownership-sil -enable-sil-verify-all -retain-sinking -late-release-hoisting %s | %FileCheck %s
// RUN: %target-sil-opt -assume-parsing-unqualified-ownership-sil -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 { }
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)
}
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: strong_retain
// CHECK-NEXT: unreachable
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 {
@sil_stored var dummyElements: Int32
init()
}
// CHECK-LABEL: sil @builtin_does_not_block_locally_allocated_ref
// CHECK: builtin
// CHECK-NEXT: strong_retain
// CHECK-NEXT: strong_release
// CHECK: 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: strong_retain
// CHECK: strong_release
// CHECK: 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: strong_release
// CHECK: 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: strong_retain
// CHECK: strong_release
// CHECK: 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: retain_value %2 : $S
/// CHECK-NEXT: release_value %2 : $S
/// 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: retain_value %2 : $S
/// CHECK-MULTIPLE-RS-ROUNDS-NEXT: release_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 : $()
}