Files
swift-mirror/test/SILOptimizer/temp_rvalue_rle.sil
Erik Eckstein c6b1e3e854 TempRValueElimination: re-implement the pass in swift
Beside cleaning up the source code, the motivation for the translation into Swift is to make it easier to improve the pass for some InlineArray specific optimizations (though I'm not sure, yet if we really need those).
Also, the new implementation doesn't contain the optimize-store-into-temp optimization anymore, because this is covered by redundant load elimination.
2025-05-06 13:08:09 +02:00

509 lines
20 KiB
Plaintext

// RUN: %target-sil-opt %s -redundant-load-elimination -deadobject-elim | %FileCheck %s
sil_stage canonical
import Builtin
import Swift
//===----------------------------------------------------------------------===//
// Tests eliminating stores to temporary "r-values", which were
// originally handled by TempRValueOpt.
//===----------------------------------------------------------------------===//
struct GS<Base> {
var _base: Base
var _value: Builtin.Int64
}
class Klass {
@_hasStorage var i: Int
init()
}
class OtherClass {
var klass: Klass
}
struct Two {
var a: Klass
var b: Klass
}
struct NonTrivialStruct {
var val:Klass
}
public enum FakeOptional<T> {
case none
case some(T)
}
struct MOS: ~Copyable {}
protocol P {
func foo()
}
sil [ossa] @getKlass : $@convention(thin) () -> @owned Klass
sil [ossa] @getNonTrivialStruct : $@convention(thin) () -> @owned NonTrivialStruct
sil [ossa] @getMOS : $@convention(thin) () -> @owned MOS
sil @unknown : $@convention(thin) () -> ()
sil [readonly] [ossa] @read_only_coroutine : $@yield_once @convention(thin) () -> @yields @in_guaranteed P
sil [ossa] @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
sil [ossa] @guaranteed_user_with_result : $@convention(thin) (@guaranteed Klass) -> @out Klass
sil [ossa] @inguaranteed_user_without_result_NTS : $@convention(thin) (@in_guaranteed NonTrivialStruct) -> ()
sil [ossa] @inguaranteed_user_without_result_MOS : $@convention(thin) (@in_guaranteed MOS) -> ()
sil @use_gsbase_builtinnativeobject : $@convention(thin) (@guaranteed GS<Builtin.NativeObject>) -> ()
sil @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> ()
sil @throwing_function : $@convention(thin) (@in_guaranteed Klass) -> ((), @error Error)
sil @inguaranteed_user_with_result : $@convention(thin) (@in_guaranteed Klass) -> @out Klass
sil @$createKlass : $@convention(thin) () -> @out Klass
sil @$appendKlass : $@convention(method) (@in_guaranteed Klass, @inout Klass) -> ()
sil @takeGuaranteedObj : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
// Make sure that when we convert the load [take] to a load [copy], we hoist the
// load of src /before/ the destroy of %0.
// CHECK-LABEL: sil [ossa] @hoist_load_copy_to_src_copy_addr_site : $@convention(thin) (@owned GS<Builtin.NativeObject>) -> @owned GS<Builtin.NativeObject> {
// CHECK: bb0([[ARG:%.*]] : @owned
// CHECK: apply {{%.*}}([[ARG]])
// CHECK: return [[ARG]]
// CHECK: } // end sil function 'hoist_load_copy_to_src_copy_addr_site'
sil [ossa] @hoist_load_copy_to_src_copy_addr_site : $@convention(thin) (@owned GS<Builtin.NativeObject>) -> @owned GS<Builtin.NativeObject> {
bb0(%0 : @owned $GS<Builtin.NativeObject>):
%f = function_ref @use_gsbase_builtinnativeobject : $@convention(thin) (@guaranteed GS<Builtin.NativeObject>) -> ()
%stk = alloc_stack $GS<Builtin.NativeObject>
store %0 to [init] %stk : $*GS<Builtin.NativeObject>
%obj = load [take] %stk : $*GS<Builtin.NativeObject>
dealloc_stack %stk : $*GS<Builtin.NativeObject>
apply %f(%obj) : $@convention(thin) (@guaranteed GS<Builtin.NativeObject>) -> ()
return %obj : $GS<Builtin.NativeObject>
}
// We do not support this today while we are bringing up store support.
//
// CHECK-LABEL: sil [ossa] @store_rvalue_simple
// CHECK: alloc_stack
// CHECK-LABEL: } // end sil function 'store_rvalue_simple'
sil [ossa] @store_rvalue_simple : $@convention(thin) (@in_guaranteed GS<Klass>, @owned GS<Klass>) -> () {
bb0(%0 : $*GS<Klass>, %1 : @owned $GS<Klass>):
%2 = struct_element_addr %0 : $*GS<Klass>, #GS._value
%3 = load [trivial] %2 : $*Builtin.Int64
%4 = alloc_stack $GS<Klass>
store %1 to [init] %4 : $*GS<Klass>
%6 = struct_element_addr %4 : $*GS<Klass>, #GS._value
%7 = load [trivial] %6 : $*Builtin.Int64
%8 = builtin "cmp_slt_Int64"(%3 : $Builtin.Int64, %7 : $Builtin.Int64) : $Builtin.Int1
destroy_addr %4 : $*GS<Klass>
dealloc_stack %4 : $*GS<Klass>
%9999 = tuple()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @store_copy_from_temp :
// CHECK: bb0([[RESULT:%.*]] : $*GS<Klass>, [[ARG0:%.*]] : @owned $GS<Klass>,
// CHECK: [[ARG0_COPY:%.*]] = copy_value [[ARG0]]
// CHECK: builtin
// CHECK: store [[ARG0_COPY]] to [init] [[RESULT]]
// CHECK: } // end sil function 'store_copy_from_temp'
sil [ossa] @store_copy_from_temp : $@convention(thin) (@owned GS<Klass>, Builtin.Int64) -> @out GS<Klass> {
bb0(%0 : $*GS<Klass>, %1 : @owned $GS<Klass>, %2 : $Builtin.Int64):
%4 = alloc_stack $GS<Klass>
store %1 to [init] %4 : $*GS<Klass>
%8 = builtin "cmp_slt_Int64"(%2 : $Builtin.Int64, %2 : $Builtin.Int64) : $Builtin.Int1
copy_addr %4 to [init] %0 : $*GS<Klass>
destroy_addr %4 : $*GS<Klass>
dealloc_stack %4 : $*GS<Klass>
%9999 = tuple()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @store_take_from_temp :
// CHECK: ([[S1:%.*]], [[S2:%.*]]) = destructure_struct %1
// CHECK: store [[S1]] to [assign] %0
// CHECK: } // end sil function 'store_take_from_temp'
sil [ossa] @store_take_from_temp : $@convention(thin) (@inout Klass, @owned GS<Klass>) -> () {
bb0(%0 : $*Klass, %1 : @owned $GS<Klass>):
%4 = alloc_stack $GS<Klass>
store %1 to [init] %4 : $*GS<Klass>
%7 = struct_element_addr %4 : $*GS<Klass>, #GS._base
copy_addr [take] %7 to %0 : $*Klass
dealloc_stack %4 : $*GS<Klass>
%9999 = tuple()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @store_load_in_differnt_block :
// CHECK-NOT: alloc_stack
// CHECK: } // end sil function 'store_load_in_differnt_block'
sil [ossa] @store_load_in_differnt_block : $@convention(thin) (@guaranteed GS<Klass>) -> () {
bb0(%0 : @guaranteed $GS<Klass>):
%4 = alloc_stack $GS<Klass>
%1 = copy_value %0 : $GS<Klass>
store %1 to [init] %4 : $*GS<Klass>
%6 = struct_element_addr %4 : $*GS<Klass>, #GS._value
br bb1
bb1:
%7 = load [trivial] %6 : $*Builtin.Int64
%8 = builtin "cmp_slt_Int64"(%7 : $Builtin.Int64, %7 : $Builtin.Int64) : $Builtin.Int1
destroy_addr %4 : $*GS<Klass>
dealloc_stack %4 : $*GS<Klass>
%9999 = tuple()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @store_projection_in_different_block :
// CHECK: bb0(%0 : @guaranteed $GS<Klass>):
// CHECK-NOT: alloc_stack
// CHECK: } // end sil function 'store_projection_in_different_block'
sil [ossa] @store_projection_in_different_block : $@convention(thin) (@guaranteed GS<Klass>) -> () {
bb0(%0 : @guaranteed $GS<Klass>):
%4 = alloc_stack $GS<Klass>
%0a = copy_value %0 : $GS<Klass>
store %0a to [init] %4 : $*GS<Klass>
br bb1
bb1:
%6 = struct_element_addr %4 : $*GS<Klass>, #GS._value
%7 = load [trivial] %6 : $*Builtin.Int64
%8 = builtin "cmp_slt_Int64"(%7 : $Builtin.Int64, %7 : $Builtin.Int64) : $Builtin.Int1
destroy_addr %4 : $*GS<Klass>
dealloc_stack %4 : $*GS<Klass>
%9999 = tuple()
return %9999 : $()
}
// CHECK-LABEL: sil [ossa] @store_store_after_load :
// CHECK-NOT: alloc_stack
// CHECK: } // end sil function 'store_store_after_load'
sil [ossa] @store_store_after_load : $@convention(thin) (@guaranteed GS<Klass>, Builtin.Int64) -> () {
bb0(%1 : @guaranteed $GS<Klass>, %2 : $Builtin.Int64):
%3 = struct_extract %1 : $GS<Klass>, #GS._value
%4 = alloc_stack $GS<Klass>
%1a = copy_value %1 : $GS<Klass>
store %1a to [init] %4 : $*GS<Klass>
%6 = struct_element_addr %4 : $*GS<Klass>, #GS._value
%7 = load [trivial] %6 : $*Builtin.Int64
%8 = builtin "cmp_slt_Int64"(%7 : $Builtin.Int64, %7 : $Builtin.Int64) : $Builtin.Int1
destroy_addr %4 : $*GS<Klass>
dealloc_stack %4 : $*GS<Klass>
%9999 = tuple()
return %9999 : $()
}
// TODO: We do not support this today due to the in_guaranteed parameter. Maybe
// instead we use store_borrow just around the call site?
//
// Make sure that we can eliminate temporaries passed via a temporary rvalue to
// an @guaranteed function.
//
// CHECK-LABEL: sil [ossa] @store_inguaranteed_no_result : $@convention(thin) (@guaranteed Klass) -> () {
// CHECK: bb0([[ARG:%.*]] : @guaranteed $Klass):
// CHECK: alloc_stack
// CHECK: } // end sil function 'store_inguaranteed_no_result'
sil [ossa] @store_inguaranteed_no_result : $@convention(thin) (@guaranteed Klass) -> () {
bb0(%0 : @guaranteed $Klass):
%1 = alloc_stack $Klass
%0a = copy_value %0 : $Klass
store %0a to [init] %1 : $*Klass
%5 = function_ref @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> ()
%6 = apply %5(%1) : $@convention(thin) (@in_guaranteed Klass) -> ()
destroy_addr %1 : $*Klass
dealloc_stack %1 : $*Klass
%9 = tuple ()
return %9 : $()
}
// We do not support this today since we need to make it so that we can use
// store_borrow to pass to the in_guaranteed function.
//
// CHECK-LABEL: sil [ossa] @store_try_apply_argument : $@convention(thin) (@guaranteed Klass) -> () {
// CHECK: alloc_stack
// CHECK: } // end sil function 'store_try_apply_argument'
sil [ossa] @store_try_apply_argument : $@convention(thin) (@guaranteed Klass) -> () {
bb0(%0 : @guaranteed $Klass):
%1 = alloc_stack $Klass
%0copy = copy_value %0 : $Klass
store %0copy to [init] %1 : $*Klass
%5 = function_ref @throwing_function : $@convention(thin) (@in_guaranteed Klass) -> ((), @error Error)
try_apply %5(%1) : $@convention(thin) (@in_guaranteed Klass) -> ((), @error Error), normal bb1, error bb2
bb1(%r : $()):
br bb3
bb2(%e : $Error):
br bb3
bb3:
destroy_addr %1 : $*Klass
dealloc_stack %1 : $*Klass
%9 = tuple ()
return %9 : $()
}
// TODO: We need to support using store_borrow to shrink the lifetime here.
//
// Make sure that we can eliminate temporaries passed via a temporary rvalue to
// an @guaranteed function.
//
// CHECK-LABEL: sil [ossa] @store_inguaranteed_with_result : $@convention(thin) (@owned Klass) -> () {
// CHECK: bb0([[ARG:%.*]] : @owned $Klass):
// CHECK: alloc_stack
// CHECK: alloc_stack
// CHECK: } // end sil function 'store_inguaranteed_with_result'
sil [ossa] @store_inguaranteed_with_result : $@convention(thin) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
%1 = alloc_stack $Klass
%1a = alloc_stack $Klass
store %0 to [init] %1 : $*Klass
%5 = function_ref @inguaranteed_user_with_result : $@convention(thin) (@in_guaranteed Klass) -> @out Klass
%6 = apply %5(%1a, %1) : $@convention(thin) (@in_guaranteed Klass) -> @out Klass
destroy_addr %1a : $*Klass
destroy_addr %1 : $*Klass
dealloc_stack %1a : $*Klass
dealloc_stack %1 : $*Klass
%9 = tuple ()
return %9 : $()
}
// TODO: Once we are able to use store_borrow to shrink lifetimes, we will have
// no alloc_stack in this function.
//
// CHECK-LABEL: sil [ossa] @store_non_overlapping_lifetime : $@convention(thin) (@owned Klass) -> () {
// CHECK: [[S:%.*]] = alloc_stack
// CHECK: [[C1:%.*]] = copy_value %0
// CHECK: [[C2:%.*]] = copy_value [[C1]]
// CHECK: store [[C2]] to [init] [[S]]
// CHECK: } // end sil function 'store_non_overlapping_lifetime'
sil [ossa] @store_non_overlapping_lifetime : $@convention(thin) (@owned Klass) -> () {
bb0(%0 : @owned $Klass):
%1a = alloc_stack $Klass
%1 = alloc_stack $Klass
%2 = alloc_stack $Klass
%0a = copy_value %0 : $Klass
store %0a to [init] %2 : $*Klass
copy_addr [take] %2 to [init] %1 : $*Klass
dealloc_stack %2 : $*Klass
copy_addr %1 to [init] %1a : $*Klass
destroy_value %0 : $Klass
destroy_addr %1 : $*Klass
dealloc_stack %1 : $*Klass
%3 = function_ref @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> ()
apply %3(%1a) : $@convention(thin) (@in_guaranteed Klass) -> ()
destroy_addr %1a : $*Klass
dealloc_stack %1a : $*Klass
%9999 = tuple()
return %9999 : $()
}
sil [ossa] @store_$createKlass : $@convention(thin) () -> @out Klass
sil [ossa] @store_$appendKlass : $@convention(method) (@guaranteed Klass, @inout Klass) -> ()
// TODO: With time we should be able to shrink the lifetime of the first
// argument here with time.
//
// CHECK-LABEL: sil [ossa] @store_overlapping_lifetime_in_function_all : $@convention(thin) () -> @out Klass {
// CHECK: [[S1:%.*]] = alloc_stack $Klass
// CHECK: [[S2:%.*]] = alloc_stack $Klass
// CHECK: [[S1_LOADED:%.*]] = load [copy] [[S1]]
// CHECK: store [[S1_LOADED]] to [init] [[S2]]
// CHECK: apply {{%.*}}([[S2]], [[S1]])
// CHECK: } // end sil function 'store_overlapping_lifetime_in_function_all'
sil [ossa] @store_overlapping_lifetime_in_function_all : $@convention(thin) () -> @out Klass {
bb0(%0 : $*Klass):
%1 = alloc_stack $Klass
%2 = function_ref @$createKlass : $@convention(thin) () -> @out Klass
%3 = apply %2(%1) : $@convention(thin) () -> @out Klass
%4 = alloc_stack $Klass
%2a = load [copy] %1 : $*Klass
store %2a to [init] %4 : $*Klass
%6 = function_ref @$appendKlass : $@convention(method) (@in_guaranteed Klass, @inout Klass) -> ()
%7 = apply %6(%4, %1) : $@convention(method) (@in_guaranteed Klass, @inout Klass) -> ()
destroy_addr %4 : $*Klass
dealloc_stack %4 : $*Klass
copy_addr [take] %1 to [init] %0 : $*Klass
dealloc_stack %1 : $*Klass
%12 = tuple ()
return %12 : $()
}
sil [ossa] @store_getP : $@convention(thin) () -> @out Optional<P>
sil [ossa] @store_takeGuaranteedObj : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
// Now that we support ossa, eliminate the alloc_stack and change the load
// [take] to a load [copy] in the process.
//
// CHECK-LABEL: sil [ossa] @store_copyWithLoadRelease : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
// CHECK: bb0(%0 : @guaranteed $Builtin.NativeObject):
// CHECK-NOT: alloc_stack
// CHECK-LABEL: } // end sil function 'store_copyWithLoadRelease'
sil [ossa] @store_copyWithLoadRelease : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () {
bb0(%0 : @guaranteed $Builtin.NativeObject):
%stk = alloc_stack $Builtin.NativeObject
%0copy = copy_value %0 : $Builtin.NativeObject
store %0copy to [init] %stk : $*Builtin.NativeObject
%obj = load [take] %stk : $*Builtin.NativeObject
%f = function_ref @takeGuaranteedObj : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
%call = apply %f(%obj) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
destroy_value %obj : $Builtin.NativeObject
dealloc_stack %stk : $*Builtin.NativeObject
%v = tuple ()
return %v : $()
}
// Remove a copy that is released via a load. Leave the load [take] alone since
// our copy_addr is taking from source.
//
// CHECK-LABEL: sil [ossa] @store_takeWithLoadRelease : $@convention(thin) (@owned Builtin.NativeObject) -> () {
// CHECK-NOT: alloc_stack
// CHECK-LABEL: } // end sil function 'store_takeWithLoadRelease'
sil [ossa] @store_takeWithLoadRelease : $@convention(thin) (@owned Builtin.NativeObject) -> () {
bb0(%0 : @owned $Builtin.NativeObject):
%stk = alloc_stack $Builtin.NativeObject
store %0 to [init] %stk : $*Builtin.NativeObject
%obj = load [take] %stk : $*Builtin.NativeObject
%f = function_ref @takeGuaranteedObj : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
%call = apply %f(%obj) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
destroy_value %obj : $Builtin.NativeObject
dealloc_stack %stk : $*Builtin.NativeObject
%v = tuple ()
return %v : $()
}
// CHECK-LABEL: sil [ossa] @store_takeWithLoadReleaseOfProjection : $@convention(thin) (@owned GS<Builtin.NativeObject>) -> () {
// CHECK: ([[S1:%.*]], [[S2:%.*]]) = destructure_struct %0
// CHECK: apply %{{[0-9]+}}([[S1]])
// CHECK: } // end sil function 'store_takeWithLoadReleaseOfProjection'
sil [ossa] @store_takeWithLoadReleaseOfProjection : $@convention(thin) (@owned GS<Builtin.NativeObject>) -> () {
bb0(%0 : @owned $GS<Builtin.NativeObject>):
%stk = alloc_stack $GS<Builtin.NativeObject>
store %0 to [init] %stk : $*GS<Builtin.NativeObject>
%proj = struct_element_addr %stk : $*GS<Builtin.NativeObject>, #GS._base
%obj = load [take] %proj : $*Builtin.NativeObject
%f = function_ref @takeGuaranteedObj : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
%call = apply %f(%obj) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> ()
destroy_value %obj : $Builtin.NativeObject
dealloc_stack %stk : $*GS<Builtin.NativeObject>
%v = tuple ()
return %v : $()
}
// We don't support liferange exits currently
//
// CHECK-LABEL: sil [ossa] @test_optimize_store_of_enum1
// CHECK: alloc_stack
// CHECK: } // end sil function 'test_optimize_store_of_enum1'
sil [ossa] @test_optimize_store_of_enum1 : $@convention(method) <Element> (@guaranteed Optional<Klass>) -> () {
bb0(%0 : @guaranteed $Optional<Klass>):
%1 = copy_value %0 : $Optional<Klass>
%32 = alloc_stack $Optional<Klass>
store %1 to [init] %32 : $*Optional<Klass>
switch_enum %0 : $Optional<Klass>, case #Optional.some!enumelt: bb6, case #Optional.none!enumelt: bb5
bb5:
dealloc_stack %32 : $*Optional<Klass>
br bb7
bb6(%50 : @guaranteed $Klass):
%53 = load [take] %32 : $*Optional<Klass>
destroy_value %53 : $Optional<Klass>
dealloc_stack %32 : $*Optional<Klass>
br bb7
bb7:
%r = tuple ()
return %r : $()
}
// We don't support liferange exits currently
//
// CHECK-LABEL: sil [ossa] @test_optimize_store_of_enum2
// CHECK: alloc_stack
// CHECK: } // end sil function 'test_optimize_store_of_enum2'
sil [ossa] @test_optimize_store_of_enum2 : $@convention(method) <Element> (@owned Optional<Klass>) -> () {
bb0(%0 : @owned $Optional<Klass>):
%1 = copy_value %0 : $Optional<Klass>
%32 = alloc_stack $Optional<Klass>
store %0 to [init] %32 : $*Optional<Klass>
switch_enum %1 : $Optional<Klass>, case #Optional.some!enumelt: bb6, case #Optional.none!enumelt: bb5
bb5:
dealloc_stack %32 : $*Optional<Klass>
br bb7
bb6(%50 : @owned $Klass):
%53 = load [take] %32 : $*Optional<Klass>
destroy_value %53 : $Optional<Klass>
destroy_value %50 : $Klass
dealloc_stack %32 : $*Optional<Klass>
br bb7
bb7:
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @store_of_enum_must_be_in_same_block
// CHECK-NOT: alloc_stack
// CHECK: } // end sil function 'store_of_enum_must_be_in_same_block'
sil [ossa] @store_of_enum_must_be_in_same_block : $@convention(method) <Element> (@guaranteed Optional<Klass>) -> () {
bb0(%0 : @guaranteed $Optional<Klass>):
%32 = alloc_stack $Optional<Klass>
switch_enum %0 : $Optional<Klass>, case #Optional.some!enumelt: bb6, case #Optional.none!enumelt: bb5
bb5:
dealloc_stack %32 : $*Optional<Klass>
br bb7
bb6(%50 : @guaranteed $Klass):
%1 = copy_value %0 : $Optional<Klass>
store %1 to [init] %32 : $*Optional<Klass>
%53 = load [take] %32 : $*Optional<Klass>
destroy_value %53 : $Optional<Klass>
dealloc_stack %32 : $*Optional<Klass>
br bb7
bb7:
%r = tuple ()
return %r : $()
}
// CHECK-LABEL: sil [ossa] @eliminate_fix_lifetime_on_dest_store : $@convention(thin) (@inout Klass) -> () {
// CHECK-NOT: alloc_stack
// CHECK: } // end sil function 'eliminate_fix_lifetime_on_dest_store'
sil [ossa] @eliminate_fix_lifetime_on_dest_store : $@convention(thin) (@inout Klass) -> () {
bb0(%0 : $*Klass):
%2 = load [copy] %0 : $*Klass
%3 = alloc_stack $Klass
store %2 to [init] %3 : $*Klass
fix_lifetime %3 : $*Klass
destroy_addr %3 : $*Klass
dealloc_stack %3 : $*Klass
%9999 = tuple()
return %9999 : $()
}
// Check that we don't crash with this.
// CHECK-LABEL: sil [ossa] @unhandled_user : $@convention(thin) (@owned Klass) -> @owned Klass {
// CHECK: alloc_stack
// CHECK: store
// CHECK: begin_access
// CHECK: load
// CHECK: } // end sil function 'unhandled_user'
sil [ossa] @unhandled_user : $@convention(thin) (@owned Klass) -> @owned Klass {
bb0(%0 : @owned $Klass):
%5 = alloc_stack $Klass
store %0 to [init] %5 : $*Klass
%104 = begin_access [read] [static] %5 : $*Klass
%105 = load [take] %104 : $*Klass
end_access %104 : $*Klass
dealloc_stack %5 : $*Klass
return %105 : $Klass
}