// RUN: %target-sil-opt -sil-print-types -enable-sil-verify-all -retain-sinking -late-release-hoisting %s | %FileCheck %s // RUN: %target-sil-opt -sil-print-types -enable-sil-verify-all -release-hoisting %s | %FileCheck --check-prefix=CHECK-RELEASE-HOISTING %s // RUN: %target-sil-opt -sil-print-types -enable-sil-verify-all -retain-sinking -retain-sinking -late-release-hoisting %s | %FileCheck --check-prefix=CHECK-MULTIPLE-RS-ROUNDS %s // REQUIRES: swift_in_compiler 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() } class Y { @_hasStorage public var c: C2 { get } } actor Act { } struct S { var ptr : Builtin.NativeObject } struct foo { var a: Int init(a: Int) init() } enum Optional { 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) -> () 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) (@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 [init] %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"(%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 any 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 Int.Type in %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, Builtin.Word, Builtin.Word) -> Builtin.RawPointer { // CHECK-RELEASE-HOISTING: bb0(%0 : $_ContiguousArrayBuffer, %1 : $Builtin.Word, %2 : $Builtin.Word): // CHECK-RELEASE-HOISTING: builtin "copyArray" // CHECK-RELEASE-HOISTING: release_value %0 : $_ContiguousArrayBuffer // CHECK-RELEASE-HOISTING-LABEL: } // end sil function 'testCopyArray' sil @testCopyArray : $@convention(thin) (_ContiguousArrayBuffer, Builtin.Word, Builtin.Word) -> Builtin.RawPointer { bb0(%0 : $_ContiguousArrayBuffer, %1 : $Builtin.Word, %2 : $Builtin.Word): %eltty = metatype $@thick AnyObject.Protocol %newptr = builtin "allocRaw"(%1 : $Builtin.Word, %1 : $Builtin.Word) : $Builtin.RawPointer %token = bind_memory %newptr : $Builtin.RawPointer, %1 : $Builtin.Word to $*AnyObject %storage = struct_extract %0 : $_ContiguousArrayBuffer, #_ContiguousArrayBuffer._storage %elements = ref_tail_addr %storage : $__ContiguousArrayStorageBase, $AnyObject %eltptr = address_to_pointer %elements : $*AnyObject to $Builtin.RawPointer %objptr = struct $UnsafePointer (%eltptr : $Builtin.RawPointer) %ptrdep = mark_dependence %objptr : $UnsafePointer on %storage : $__ContiguousArrayStorageBase %rawptr = struct_extract %ptrdep : $UnsafePointer, #UnsafePointer._rawValue %copy = builtin "copyArray"(%eltty : $@thick AnyObject.Protocol, %newptr : $Builtin.RawPointer, %rawptr : $Builtin.RawPointer, %1 : $Builtin.Word) : $() release_value %0 : $_ContiguousArrayBuffer return %newptr : $Builtin.RawPointer } // CHECK-RELEASE-HOISTING-LABEL: sil @testMemcpy // CHECK-RELEASE-HOISTING: bb0(%0 : $_ContiguousArrayBuffer, %1 : $Builtin.Word): // CHECK-RELEASE-HOISTING: builtin "int_memcpy_RawPointer_RawPointer_Word" // CHECK-RELEASE-HOISTING: release_value %0 : $_ContiguousArrayBuffer // CHECK-RELEASE-HOISTING: } // end sil function 'testMemcpy' sil @testMemcpy : $@convention(thin) (_ContiguousArrayBuffer, Builtin.Word) -> Builtin.RawPointer { bb0(%0 : $_ContiguousArrayBuffer, %1 : $Builtin.Word): %newptr = builtin "allocRaw"(%1 : $Builtin.Word, %1 : $Builtin.Word) : $Builtin.RawPointer %token = bind_memory %newptr : $Builtin.RawPointer, %1 : $Builtin.Word to $*UInt64 %storage = struct_extract %0 : $_ContiguousArrayBuffer, #_ContiguousArrayBuffer._storage %elements = ref_tail_addr %storage : $__ContiguousArrayStorageBase, $UInt64 %eltptr = address_to_pointer %elements : $*UInt64 to $Builtin.RawPointer %objptr = struct $UnsafePointer (%eltptr : $Builtin.RawPointer) %ptrdep = mark_dependence %objptr : $UnsafePointer on %storage : $__ContiguousArrayStorageBase %rawptr = struct_extract %ptrdep : $UnsafePointer, #UnsafePointer._rawValue %f = integer_literal $Builtin.Int1, 0 %move = builtin "int_memcpy_RawPointer_RawPointer_Word"(%newptr : $Builtin.RawPointer, %rawptr : $Builtin.RawPointer, %1 : $Builtin.Word, %f : $Builtin.Int1) : $() release_value %0 : $_ContiguousArrayBuffer return %newptr : $Builtin.RawPointer } // CHECK-LABEL: sil @dontMoveOverExistentialToClassCast : $@convention(thin) (@guaranteed AnyObject) -> Optional // CHECK: strong_retain %0 // CHECK: checked_cast_br AnyObject in %0 // CHECK: } // end sil function 'dontMoveOverExistentialToClassCast' sil @dontMoveOverExistentialToClassCast : $@convention(thin) (@guaranteed AnyObject) -> Optional { bb0(%0 : $AnyObject): strong_retain %0 : $AnyObject checked_cast_br AnyObject in %0 : $AnyObject to fuzz, bb1, bb2 bb1(%18 : $fuzz): %19 = enum $Optional, #Optional.some!enumelt, %18 : $fuzz br bb3(%19 : $Optional) bb2: strong_release %0 : $AnyObject %22 = enum $Optional, #Optional.none!enumelt br bb3(%22 : $Optional) bb3(%24 : $Optional): return %24 : $Optional } // CHECK-LABEL: sil @moveOverClassToExistentialCast : $@convention(thin) (@guaranteed fuzz) -> Optional // CHECK: checked_cast_br fuzz in %0 // CHECK: enum $Optional, #Optional.some!enumelt // CHECK: strong_retain %0 // CHECK-NOT: release // CHECK: } // end sil function 'moveOverClassToExistentialCast' sil @moveOverClassToExistentialCast : $@convention(thin) (@guaranteed fuzz) -> Optional

{ bb0(%0 : $fuzz): strong_retain %0 : $fuzz checked_cast_br fuzz in %0 : $fuzz to P, bb1, bb2 bb1(%18 : $P): %19 = enum $Optional

, #Optional.some!enumelt, %18 : $P br bb3(%19 : $Optional

) bb2: strong_release %0 : $fuzz %22 = enum $Optional

, #Optional.none!enumelt br bb3(%22 : $Optional

) bb3(%24 : $Optional

): return %24 : $Optional

} // CHECK-LABEL: sil @dontMoveOverExistentialToExistentialCast : $@convention(thin) (@guaranteed AnyObject) -> Optional // CHECK: strong_retain %0 // CHECK: checked_cast_br AnyObject in %0 // CHECK: } // end sil function 'dontMoveOverExistentialToExistentialCast' sil @dontMoveOverExistentialToExistentialCast : $@convention(thin) (@guaranteed AnyObject) -> Optional

{ bb0(%0 : $AnyObject): strong_retain %0 : $AnyObject checked_cast_br AnyObject in %0 : $AnyObject to P, bb1, bb2 bb1(%18 : $P): %19 = enum $Optional

, #Optional.some!enumelt, %18 : $P br bb3(%19 : $Optional

) bb2: strong_release %0 : $AnyObject %22 = enum $Optional

, #Optional.none!enumelt br bb3(%22 : $Optional

) bb3(%24 : $Optional

): return %24 : $Optional

} // ----------------------------------------------------------------------------- // 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 : $() } sil [ossa] @destroyNode : $@convention(thin) (@owned Node, @inout Queue) -> () { bb0(%0 : @owned $Node, %1 : $*Queue): destroy_value %0 : $Node %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 %destroy = function_ref @destroyNode : $@convention(thin) (@owned Node, @inout Queue) -> () strong_retain %extraNode : $Node %call = apply %destroy(%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-NOT-SUPPORTED-YET: apply {{.*}} : $@convention(thin) (@owned Node, @inout Queue) -> () // CHECK-NOT-SUPPORTED-YET: 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 : $() } // CHECK-LABEL: sil @test_hop_to_executor : // CHECK: strong_retain // CHECK: hop_to_executor // CHECK: strong_release // CHECK: } // end sil function 'test_hop_to_executor' sil @test_hop_to_executor : $@convention(thin) @async (@guaranteed Y, @guaranteed Act) -> A { bb0(%0 : $Y, %1 : $Act): %2 = ref_element_addr %0 : $Y, #Y.c %3 = load %2 : $*C2 strong_retain %3 : $C2 // This is a synchronization point and any kind of other code might run here, // which potentially can release C2. hop_to_executor %1 : $Act %6 = ref_element_addr %3 : $C2, #C2.current %7 = load %6 : $*A strong_release %3 : $C2 return %7 : $A } // CHECK-RELEASE-HOISTING-LABEL: sil @hoist_release_over_end_init_let_ref : // CHECK-RELEASE-HOISTING: alloc_ref // CHECK-RELEASE-HOISTING-NEXT: strong_release // CHECK-RELEASE-HOISTING-NEXT: br bb1 // CHECK-RELEASE-HOISTING: } // end sil function 'hoist_release_over_end_init_let_ref' sil @hoist_release_over_end_init_let_ref : $@convention(thin) (@guaranteed B) -> () { bb0(%0 : $B): %1 = alloc_ref $C br bb1 bb1: %3 = end_init_let_ref %0 : $B strong_release %1 : $C %5 = tuple() return %5 : $() } // CHECK: sil @dont_hoist_release_accross_cast // CHECK: retain // CHECK: apply // CHECK: unconditional_checked_cast // CHECK: release sil @dont_hoist_release_accross_cast : $@convention(thin) (B, B) -> () { bb0(%0 : $B, %1: $B): strong_retain %0: $B apply undef() : $@convention(thin) () -> () %3 = unconditional_checked_cast %0 : $B to Builtin.NativeObject strong_release %0: $B %5 = tuple() return %5 : $() } enum NCEnum : ~Copyable { case some([Int]) case none } sil @use_ncenum : $@convention(thin) (@guaranteed Array) -> () // CHECK-LABEL: sil hidden @testNoncopyable : $@convention(thin) (@guaranteed NCEnum) -> () { // CHECK-NOT: retain_value %0 // CHECK: retain_value // CHECK-LABEL: } // end sil function 'testNoncopyable' sil hidden @testNoncopyable : $@convention(thin) (@guaranteed NCEnum) -> () { bb0(%0 : $NCEnum): switch_enum %0, case #NCEnum.none!enumelt: bb1, case #NCEnum.some!enumelt: bb2 bb1: br bb3 bb2(%4 : $Array): %5 = alloc_stack [var_decl] $Array, var, name "array" retain_value %4 store %4 to %5 %11 = function_ref @use_ncenum : $@convention(thin) (@guaranteed Array) -> () %12 = apply %11(%4) : $@convention(thin) (@guaranteed Array) -> () destroy_addr %5 dealloc_stack %5 br bb3 bb3: %16 = tuple () return %16 }