Files
swift-mirror/test/SILOptimizer/redundant_load_elim_with_casts.sil
Erik Eckstein 4d20423e00 Optimizer: re-implement the RedundantLoadElimination pass in Swift
The new implementation has several benefits compared to the old C++ implementation:

* It is significantly simpler. It optimizes each load separately instead of all at once with bit-field based dataflow.
* It's using alias analysis more accurately which enables more loads to be optimized
* It avoids inserting additional copies in OSSA

The algorithm is a data flow analysis which starts at the original load and searches for preceding stores or loads by following the control flow in backward direction.
The preceding stores and loads provide the "available values" with which the original load can be replaced.
2023-07-21 07:19:56 +02:00

550 lines
23 KiB
Plaintext

// RUN: %target-sil-opt -enable-sil-verify-all %s -module-name Swift -redundant-load-elimination | %FileCheck -check-prefix=CHECK-FUTURE %s
//
// FIXME: Contains tests which are handled by old RLE, but not current one. Mostly due to casting. Eventually we probably should
// handle these cases if they turn out to be important.
//
// The problem is the current RLE uses type projection tree/path to model fields in an object. This makes it difficult for it to
// reason about casts, i.e. 1 memory with different types.
//
// Tracked by rdar://23023366
import Builtin
struct A {
var i : Builtin.Int32
}
struct A2 {
var i : Builtin.Int32
}
struct AA {
var a : A
var i : Builtin.Int32
}
class B {
var i : Builtin.Int32
init()
}
struct X {
var c : B
init()
}
enum Optional<T> {
case none
case some(T)
}
class E : B { }
struct C {
var i : Builtin.Int16
}
struct D {
var p : Builtin.RawPointer
}
sil @use : $@convention(thin) (Builtin.Int32) -> ()
sil @use_a : $@convention(thin) (@in A) -> ()
sil @escaped_a_ptr : $@convention(thin) () -> @out A
sil @escaped_a : $@convention(thin) () -> Builtin.RawPointer
// *NOTE* This does not handle raw pointer since raw pointer is only layout compatible with heap references.
// CHECK-FUTURE: sil @store_to_load_forward_unchecked_addr_cast_struct : $@convention(thin) (Optional<A>) -> () {
// CHECK: bb0([[INPUT:%[0-9]+]]
// CHECK-NEXT: [[LOCAL:%[0-9]+]] = alloc_stack
// CHECK: unchecked_trivial_bit_cast [[INPUT]] : $Optional<A> to $Builtin.Int32
// CHECK: unchecked_trivial_bit_cast [[INPUT]] : $Optional<A> to $A
// CHECK: unchecked_addr_cast [[LOCAL]] : $*Optional<A> to $*Builtin.RawPointer
// CHECK: unchecked_addr_cast [[LOCAL]] : $*Optional<A> to $*Builtin.NativeObject
// CHECK: unchecked_trivial_bit_cast [[INPUT]] : $Optional<A> to $Optional<Builtin.Int32>
// CHECK: unchecked_addr_cast [[LOCAL]] : $*Optional<A> to $*Optional<Builtin.RawPointer>
// CHECK: unchecked_addr_cast [[LOCAL]] : $*Optional<A> to $*Optional<Builtin.NativeObject>
// CHECK: return
sil @store_to_load_forward_unchecked_addr_cast_struct : $@convention(thin) (Optional<A>) -> () {
bb0(%0 : $Optional<A>):
%1 = alloc_stack $Optional<A>
store %0 to %1 : $*Optional<A>
%2 = unchecked_addr_cast %1 : $*Optional<A> to $*Builtin.Int32
%3 = load %2 : $*Builtin.Int32
%4 = unchecked_addr_cast %1 : $*Optional<A> to $*A
%5 = load %4 : $*A
%6 = unchecked_addr_cast %1 : $*Optional<A> to $*Builtin.RawPointer
%7 = load %6 : $*Builtin.RawPointer
%8 = unchecked_addr_cast %1 : $*Optional<A> to $*Builtin.NativeObject
%9 = load %8 : $*Builtin.NativeObject
%10 = unchecked_addr_cast %1 : $*Optional<A> to $*Optional<Builtin.Int32>
%11 = load %10 : $*Optional<Builtin.Int32>
%12 = unchecked_addr_cast %1 : $*Optional<A> to $*Optional<Builtin.RawPointer>
%13 = load %12 : $*Optional<Builtin.RawPointer>
%14 = unchecked_addr_cast %1 : $*Optional<A> to $*Optional<Builtin.NativeObject>
%15 = load %14 : $*Optional<Builtin.NativeObject>
dealloc_stack %1 : $*Optional<A>
%9999 = tuple()
return %9999 : $()
}
struct IntPtr {
var Val: Builtin.Int32
var Ptr: Builtin.RawPointer
}
// unchecked_addr_cast must not be promoted unless the size of the
// source type is known to be greater or equal to the size of the
// destination type.
//
// CHECK-FUTURE: sil @store_to_load_forward_unchecked_addr_cast_nopromote : $@convention(thin) (@inout IntPtr) -> () {
// CHECK: bb0([[INPUT:%[0-9]+]] : $*IntPtr)
// CHECK-NEXT: [[LOCAL:%[0-9]+]] = alloc_stack
// CHECK: [[CAST:%[0-9]+]] = unchecked_addr_cast [[INPUT]] : $*IntPtr to $*Builtin.Int32
// CHECK: store %{{.*}} to [[CAST]]
// CHECK: unchecked_addr_cast [[CAST]] : $*Builtin.Int32 to $*IntPtr
// CHECK: return
sil @store_to_load_forward_unchecked_addr_cast_nopromote : $@convention(thin) (@inout IntPtr) -> () {
bb0(%0 : $*IntPtr):
%1 = alloc_stack $Builtin.Int32
%2 = unchecked_addr_cast %0 : $*IntPtr to $*Builtin.Int32
%3 = integer_literal $Builtin.Int32, 3
store %3 to %2 : $*Builtin.Int32
%5 = unchecked_addr_cast %2 : $*Builtin.Int32 to $*IntPtr
%6 = load %5 : $*IntPtr
dealloc_stack %1 : $*Builtin.Int32
%9999 = tuple()
return %9999 : $()
}
// *NOTE* This does not handle raw pointer since raw pointer is layout
// compatible with heap references, but does not have reference
// semantics b/c it is a trivial type. We currently do not handle such a case.
// CHECK-FUTURE: sil @store_to_load_forward_unchecked_addr_cast_class : $@convention(thin) (Optional<B>) -> () {
// CHECK: bb0([[INPUT:%[0-9]+]]
// CHECK-NEXT: [[LOCAL:%[0-9]+]] = alloc_stack
// CHECK: unchecked_trivial_bit_cast [[INPUT]] : $Optional<B> to $Builtin.Int32
// CHECK: unchecked_ref_cast [[INPUT]] : $Optional<B> to $B
// CHECK: unchecked_trivial_bit_cast [[INPUT]] : $Optional<B> to $Builtin.RawPointer
// CHECK: unchecked_ref_cast [[INPUT]] : $Optional<B> to $Builtin.NativeObject
// CHECK: unchecked_trivial_bit_cast [[INPUT]] : $Optional<B> to $Optional<Builtin.Int32>
// CHECK: unchecked_trivial_bit_cast [[INPUT]] : $Optional<B> to $Optional<Builtin.RawPointer>
// CHECK: unchecked_ref_cast [[INPUT]] : $Optional<B> to $Optional<Builtin.NativeObject>
// CHECK: return
sil @store_to_load_forward_unchecked_addr_cast_class : $@convention(thin) (Optional<B>) -> () {
bb0(%0 : $Optional<B>):
%1 = alloc_stack $Optional<B>
store %0 to %1 : $*Optional<B>
%2 = unchecked_addr_cast %1 : $*Optional<B> to $*Builtin.Int32
%3 = load %2 : $*Builtin.Int32
%4 = unchecked_addr_cast %1 : $*Optional<B> to $*B
%5 = load %4 : $*B
%6 = unchecked_addr_cast %1 : $*Optional<B> to $*Builtin.RawPointer
%7 = load %6 : $*Builtin.RawPointer
%8 = unchecked_addr_cast %1 : $*Optional<B> to $*Builtin.NativeObject
%9 = load %8 : $*Builtin.NativeObject
%10 = unchecked_addr_cast %1 : $*Optional<B> to $*Optional<Builtin.Int32>
%11 = load %10 : $*Optional<Builtin.Int32>
%12 = unchecked_addr_cast %1 : $*Optional<B> to $*Optional<Builtin.RawPointer>
%13 = load %12 : $*Optional<Builtin.RawPointer>
%14 = unchecked_addr_cast %1 : $*Optional<B> to $*Optional<Builtin.NativeObject>
%15 = load %14 : $*Optional<Builtin.NativeObject>
dealloc_stack %1 : $*Optional<B>
%9999 = tuple()
return %9999 : $()
}
// *NOTE* This does not handle raw pointer since raw pointer is only layout compatible with heap references.
// CHECK-FUTURE: sil @load_to_load_forward_unchecked_addr_cast_struct : $@convention(thin) (@inout Optional<A>) -> () {
// CHECK: bb0(
// CHECK: unchecked_trivial_bit_cast {{%[0-9]+}} : $Optional<A> to $Builtin.Int32
// CHECK: unchecked_trivial_bit_cast {{%[0-9]+}} : $Optional<A> to $A
// CHECK: unchecked_addr_cast %{{.*}} : $*Optional<A> to $*Builtin.RawPointer
// CHECK: unchecked_addr_cast %{{.*}} : $*Optional<A> to $*Builtin.NativeObject
// CHECK: unchecked_trivial_bit_cast {{%[0-9]+}} : $Optional<A> to $Optional<Builtin.Int32>
// CHECK: unchecked_addr_cast {{%[0-9]+}} : $*Optional<A> to $*Optional<Builtin.RawPointer>
// CHECK: unchecked_addr_cast
sil @load_to_load_forward_unchecked_addr_cast_struct : $@convention(thin) (@inout Optional<A>) -> () {
bb0(%0 : $*Optional<A>):
%1 = load %0 : $*Optional<A>
%2 = unchecked_addr_cast %0 : $*Optional<A> to $*Builtin.Int32
%3 = load %2 : $*Builtin.Int32
%4 = unchecked_addr_cast %0 : $*Optional<A> to $*A
%5 = load %4 : $*A
%6 = unchecked_addr_cast %0 : $*Optional<A> to $*Builtin.RawPointer
%7 = load %6 : $*Builtin.RawPointer
%8 = unchecked_addr_cast %0 : $*Optional<A> to $*Builtin.NativeObject
%9 = load %8 : $*Builtin.NativeObject
%10 = unchecked_addr_cast %0 : $*Optional<A> to $*Optional<Builtin.Int32>
%11 = load %10 : $*Optional<Builtin.Int32>
%12 = unchecked_addr_cast %0 : $*Optional<A> to $*Optional<Builtin.RawPointer>
%13 = load %12 : $*Optional<Builtin.RawPointer>
%14 = unchecked_addr_cast %0 : $*Optional<A> to $*Optional<Builtin.NativeObject>
%15 = load %14 : $*Optional<Builtin.NativeObject>
%9999 = tuple()
return %9999 : $()
}
// *NOTE* This does not handle raw pointer since raw pointer is layout
// compatible with heap references, but does not have reference
// semantics b/c it is a trivial type. We currently do not handle such a case.
// CHECK-FUTURE: sil @load_to_load_forward_unchecked_addr_cast_class : $@convention(thin) (@inout Optional<B>) -> () {
// CHECK: bb0({{%[0-9]+}} : $*Optional<B>):
// CHECK: unchecked_trivial_bit_cast {{%[0-9]+}} : $Optional<B> to $Builtin.Int32
// CHECK: unchecked_ref_bit_cast {{%[0-9]+}} : $Optional<B> to $B
// CHECK: unchecked_trivial_bit_cast {{%[0-9]+}} : $Optional<B> to $Builtin.RawPointer
// CHECK: unchecked_ref_bit_cast {{%[0-9]+}} : $Optional<B> to $Builtin.NativeObject
// CHECK: unchecked_trivial_bit_cast {{%[0-9]+}} : $Optional<B> to $Optional<Builtin.Int32>
// CHECK: unchecked_trivial_bit_cast {{%[0-9]+}} : $Optional<B> to $Optional<Builtin.RawPointer>
// CHECK: unchecked_ref_bit_cast {{%[0-9]+}} : $Optional<B> to $Optional<Builtin.NativeObject>
sil @load_to_load_forward_unchecked_addr_cast_class : $@convention(thin) (@inout Optional<B>) -> () {
bb0(%0 : $*Optional<B>):
%1 = load %0 : $*Optional<B>
%2 = unchecked_addr_cast %0 : $*Optional<B> to $*Builtin.Int32
%3 = load %2 : $*Builtin.Int32
%4 = unchecked_addr_cast %0 : $*Optional<B> to $*B
%5 = load %4 : $*B
%6 = unchecked_addr_cast %0 : $*Optional<B> to $*Builtin.RawPointer
%7 = load %6 : $*Builtin.RawPointer
%8 = unchecked_addr_cast %0 : $*Optional<B> to $*Builtin.NativeObject
%9 = load %8 : $*Builtin.NativeObject
%10 = unchecked_addr_cast %0 : $*Optional<B> to $*Optional<Builtin.Int32>
%11 = load %10 : $*Optional<Builtin.Int32>
%12 = unchecked_addr_cast %0 : $*Optional<B> to $*Optional<Builtin.RawPointer>
%13 = load %12 : $*Optional<Builtin.RawPointer>
%14 = unchecked_addr_cast %0 : $*Optional<B> to $*Optional<Builtin.NativeObject>
%15 = load %14 : $*Optional<Builtin.NativeObject>
%9999 = tuple()
return %9999 : $()
}
// Don't bitcast differently sized structs.
// CHECK-FUTURE: sil @store_to_load_forward_unchecked_addr_cast_different_sized_struct
// CHECK-NOT: unchecked_trivial_bit_cast
// CHECK: return
sil @store_to_load_forward_unchecked_addr_cast_different_sized_struct : $@convention(thin) (C) -> () {
bb0(%0 : $C):
%1 = alloc_stack $C
store %0 to %1 : $*C
%2 = unchecked_addr_cast %1 : $*C to $*A
%3 = load %2 : $*A
dealloc_stack %1 : $*C
%9999 = tuple()
return %9999 : $()
}
/// Make sure that we don't crash and don't optimize here.
// CHECK-FUTURE: sil @covering_store_with_unchecked_addr : $@convention(thin) (C, C) -> () {
// CHECK-NOT: unchecked_trivial_bit_cast
// CHECK: unchecked_addr_cast
// CHECK-NOT: unchecked_trivial_bit_cast
sil @covering_store_with_unchecked_addr : $@convention(thin) (C, C) -> () {
bb0(%0 : $C, %1 : $C):
%2 = alloc_stack $C
cond_br undef, bb1, bb2
bb1:
store %0 to %2 : $*C
br bb3
bb2:
store %0 to %2 : $*C
br bb3
bb3:
%3 = unchecked_addr_cast %2 : $*C to $*A
%4 = load %3 : $*A
dealloc_stack %2 : $*C
%9999 = tuple()
return %9999 : $()
}
/// Make sure that we properly invalidate %3 in the following situation.
///
/// 1. We store %3 into the load map.
/// 2. We see that we are storing in %4 something we just loaded meaning that we
/// would have a dead store. We delete that store and through recursion delete %3
/// since %3's only use is %4.
/// 3. When we delete %3, we do not remove it from the load list.
/// 4. %5 can write to memory, so we try to check if it can alias %0#1. We look
/// up the load that was erased and will use it in a memory unsafe way.
//
// CHECK-FUTURE: sil @invalidate_dead_loads_with_only_store_user_correctly : $@convention(thin) () -> () {
// CHECK-NOT: load
// CHECK-NOT: {{%.*}} = store
sil @invalidate_dead_loads_with_only_store_user_correctly : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $Optional<Builtin.Int32>
%1 = integer_literal $Builtin.Int32, 0
%2 = enum $Optional<Builtin.Int32>, #Optional.some!enumelt, %1 : $Builtin.Int32
%3 = load %0 : $*Optional<Builtin.Int32>
store %3 to %0 : $*Optional<Builtin.Int32>
%5 = unchecked_take_enum_data_addr %0 : $*Optional<Builtin.Int32>, #Optional.some!enumelt
dealloc_stack %0 : $*Optional<Builtin.Int32>
%9999 = tuple()
return %9999 : $()
}
class Empty {}
struct HoldsRef { @_hasStorage var c: Empty }
sil @mutator : $@convention(method) (@inout HoldsRef) -> ()
// Ensure we don't forward the stored value from the switch_enum
// branches past the inout appearance of the stored-to location.
// CHECK-FUTURE: sil @bad_store_forward
sil @bad_store_forward : $@convention(thin) (@guaranteed Optional<HoldsRef>) -> () {
// CHECK: bb0
bb0(%0 : $Optional<HoldsRef>):
// CHECK: [[LOC:%.*]] = alloc_stack
%1 = alloc_stack $HoldsRef
switch_enum %0 : $Optional<HoldsRef>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
// CHECK: bb1([[ARG:%.*]] : ${{.*}}):
bb1(%3 : $HoldsRef):
// CHECK: store [[ARG]] to [[LOC]]
store %3 to %1 : $*HoldsRef
// CHECK-NOT: br bb3(
br bb3
// CHECK-NOT: bb2(
bb2:
%6 = alloc_ref $Empty
// CHECK: [[STRUCT:%.*]] = struct
%7 = struct $HoldsRef (%6 : $Empty)
// CHECK: store [[STRUCT]] to [[LOC]]
store %7 to %1 : $*HoldsRef
// CHECK-NOT: br bb3(
br bb3
// CHECK-NOT: bb3(
// CHECK: bb3
bb3:
// CHECK: [[FNREF:%.*]] = function_ref
%10 = function_ref @mutator : $@convention(method) (@inout HoldsRef) -> ()
retain_value %0 : $Optional<HoldsRef>
// CHECK: apply [[FNREF]]([[LOC]])
%12 = apply %10(%1) : $@convention(method) (@inout HoldsRef) -> ()
// CHECK: [[ELEM:%.*]] = struct_element_addr [[LOC]]
%13 = struct_element_addr %1 : $*HoldsRef, #HoldsRef.c
// CHECK: [[REF:%.*]] = load [[ELEM]]
%14 = load %13 : $*Empty
// CHECK: strong_release [[REF]]
strong_release %14 : $Empty
%16 = tuple ()
dealloc_stack %1 : $*HoldsRef
return %16 : $()
}
// We internally use a map vector to represent stores. This means that when we iterate over
// the stores it should be in insertion order. Use this to test whether or not we only set
// the no-dependency bit if we do not forward a load.
// CHECK-FUTURE: sil @test_unchecked_addr_cast_3
// CHECK: bb0([[ARG1:%.*]] : $D, [[ARG2:%.*]] : $D):
// CHECK-NEXT: [[BOX1:%.*]] = alloc_stack $D
// CHECK-NEXT: [[BOX2:%.*]] = alloc_stack $D
// CHECK-NEXT: store [[ARG2]] to [[BOX2]] : $*D
// CHECK-NEXT: [[RESULT:%.*]] = unchecked_trivial_bit_cast [[ARG2]]
// CHECK-NEXT: store [[ARG2]] to [[BOX1]] : $*D
// CHECK-NEXT: dealloc_stack
// CHECK-NEXT: dealloc_stack
// CHECK-NEXT: return [[RESULT]]
sil @test_unchecked_addr_cast_3 : $@convention(thin) (D, D) -> Builtin.RawPointer {
bb0(%x : $D, %y : $D):
%1 = alloc_stack $D
%2 = alloc_stack $D
store %x to %1 : $*D
store %y to %2 : $*D
%3 = unchecked_addr_cast %2 : $*D to $*Builtin.RawPointer
%l1 = load %3 : $*Builtin.RawPointer
store %y to %1 : $*D
dealloc_stack %2 : $*D
dealloc_stack %1 : $*D
return %l1 : $Builtin.RawPointer
}
typealias I32 = Builtin.Int32
// Promote unchecked_addr_cast of tuples.
// (A, B, C) -> (A, B) is safe
// ((A, B), C) -> (A, B) is safe
// ((A, B), C) -> (A, B, C) is NOT safe
//
// CHECK-FUTURE: sil @unchecked_addr_cast_tuple_promote
// CHECK: bb0(%0 : $*(Builtin.Int32, Builtin.Int32, Builtin.Int32), %1 : $*((Builtin.Int32, Builtin.Int32), Builtin.Int32)):
// CHECK: load %0 : $*(Builtin.Int32, Builtin.Int32, Builtin.Int32)
// CHECK: alloc_stack $(Builtin.Int32, Builtin.Int32, Builtin.Int32)
// CHECK: store %{{.*}} to %{{.*}}#1 : $*(Builtin.Int32, Builtin.Int32, Builtin.Int32)
// CHECK: unchecked_trivial_bit_cast %{{.*}} : $(Builtin.Int32, Builtin.Int32, Builtin.Int32) to $(Builtin.Int32, Builtin.Int32)
// CHECK: tuple_extract %{{.*}} : $(Builtin.Int32, Builtin.Int32), 0
// CHECK: unchecked_trivial_bit_cast %{{.*}} : $(Builtin.Int32, Builtin.Int32, Builtin.Int32) to $(Builtin.Int32, Builtin.Int32)
// CHECK: tuple_extract %{{.*}} : $(Builtin.Int32, Builtin.Int32), 1
// CHECK: dealloc_stack %{{.*}}#0 : $*(Builtin.Int32, Builtin.Int32, Builtin.Int32)
// CHECK: load %1 : $*((Builtin.Int32, Builtin.Int32), Builtin.Int32)
// CHECK: alloc_stack $((Builtin.Int32, Builtin.Int32), Builtin.Int32)
// CHECK: store %{{.*}} to %{{.*}}#1 : $*((Builtin.Int32, Builtin.Int32), Builtin.Int32)
// CHECK: unchecked_trivial_bit_cast %{{.*}} : $((Builtin.Int32, Builtin.Int32), Builtin.Int32) to $(Builtin.Int32, Builtin.Int32)
// CHECK: tuple_extract %{{.*}} : $(Builtin.Int32, Builtin.Int32), 0
// CHECK: unchecked_trivial_bit_cast %{{.*}} : $((Builtin.Int32, Builtin.Int32), Builtin.Int32) to $(Builtin.Int32, Builtin.Int32)
// CHECK: tuple_extract %{{.*}} : $(Builtin.Int32, Builtin.Int32), 1
// CHECK: dealloc_stack %{{.*}}#0 : $*((Builtin.Int32, Builtin.Int32), Builtin.Int32)
// CHECK: alloc_stack $((Builtin.Int32, Builtin.Int32), Builtin.Int32)
// CHECK: store %{{.*}} to %{{.*}}#1 : $*((Builtin.Int32, Builtin.Int32), Builtin.Int32)
// CHECK: unchecked_addr_cast %{{.*}}#1 : $*((Builtin.Int32, Builtin.Int32), Builtin.Int32) to $*(Builtin.Int32, Builtin.Int32, Builtin.Int32)
// CHECK: tuple_element_addr %{{.*}} : $*(Builtin.Int32, Builtin.Int32, Builtin.Int32), 0
// CHECK: tuple_element_addr %{{.*}} : $*(Builtin.Int32, Builtin.Int32, Builtin.Int32), 1
// CHECK: tuple_element_addr %{{.*}} : $*(Builtin.Int32, Builtin.Int32, Builtin.Int32), 2
// CHECK: load %{{.*}} : $*Builtin.Int32
// CHECK: load %{{.*}} : $*Builtin.Int32
// CHECK: load %{{.*}} : $*Builtin.Int32
// CHECK: dealloc_stack %{{.*}}#0 : $*((Builtin.Int32, Builtin.Int32), Builtin.Int32)
// CHECK: return %{{.*}} : $((Builtin.Int32, Builtin.Int32), (Builtin.Int32, Builtin.Int32), (Builtin.Int32, Builtin.Int32, Builtin.Int32))
sil @unchecked_addr_cast_tuple_promote : $@convention(thin) (@inout (I32, I32, I32), @inout ((I32, I32), I32)) -> ((I32, I32), (I32, I32), (I32, I32, I32)) {
bb0(%0 : $*(I32, I32, I32), %1 : $*((I32, I32), I32)):
%2 = load %0 : $*(I32, I32, I32)
%3 = alloc_stack $(I32, I32, I32)
store %2 to %3 : $*(I32, I32, I32)
%5 = unchecked_addr_cast %3 : $*(I32, I32, I32) to $*(I32, I32)
%6 = tuple_element_addr %5 : $*(I32, I32), 0
%7 = tuple_element_addr %5 : $*(I32, I32), 1
%8 = load %6 : $*I32
%9 = load %7 : $*I32
dealloc_stack %3 : $*(I32, I32, I32)
%11 = load %1 : $*((I32, I32), I32)
%12 = alloc_stack $((I32, I32), I32)
store %11 to %12 : $*((I32, I32), I32)
%14 = unchecked_addr_cast %12 : $*((I32, I32), I32) to $*(I32, I32)
%15 = tuple_element_addr %14 : $*(I32, I32), 0
%16 = tuple_element_addr %14 : $*(I32, I32), 1
%17 = load %15 : $*I32
%18 = load %16 : $*I32
dealloc_stack %12 : $*((I32, I32), I32)
%20 = alloc_stack $((I32, I32), I32)
store %11 to %20 : $*((I32, I32), I32)
%22 = unchecked_addr_cast %20 : $*((I32, I32), I32) to $*(I32, I32, I32)
%23 = tuple_element_addr %22 : $*(I32, I32, I32), 0
%24 = tuple_element_addr %22 : $*(I32, I32, I32), 1
%25 = tuple_element_addr %22 : $*(I32, I32, I32), 2
%26 = load %23 : $*I32
%27 = load %24 : $*I32
%28 = load %25 : $*I32
dealloc_stack %20 : $*((I32, I32), I32)
%30 = tuple (%8 : $I32, %9 : $I32)
%31 = tuple (%17 : $I32, %18 : $I32)
%32 = tuple (%26 : $I32, %27 : $I32, %28 : $I32)
%33 = tuple (%30 : $(I32, I32), %31 : $(I32, I32), %32 : $(I32, I32, I32))
return %33 : $((I32, I32), (I32, I32), (I32, I32, I32))
}
// Promote unchecked_addr_cast of tuple elements.
// (A, B, C) -> (A, B) is safe
// ((A, B), C) -> (A, B) is safe
// ((A, B), C) -> (A, B, C) is NOT safe
//
// CHECK-FUTURE: sil @forward_tuple_elements
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: unchecked_addr_cast
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: unchecked_addr_cast
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: unchecked_addr_cast
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: tuple_element_addr
// CHECK: return
//
// FIXME: LoadStore optimization should be able to forward these
// stores but cannot see through projections on the store side. (I
// decided not to brute force fix this, because it may be better to
// change the canonical form of tuple load/store in this case).
sil @forward_tuple_elements : $@convention(thin) (@inout (I32, I32, I32), @inout ((I32, I32), I32)) -> ((I32, I32), (I32, I32), (I32, I32, I32)) {
bb0(%0 : $*(I32, I32, I32), %1 : $*((I32, I32), I32)):
%4 = tuple_element_addr %0 : $*(I32, I32, I32), 0
%5 = load %4 : $*I32
%6 = tuple_element_addr %0 : $*(I32, I32, I32), 1
%7 = load %6 : $*I32
%8 = tuple_element_addr %0 : $*(I32, I32, I32), 2
%9 = load %8 : $*I32
%10 = alloc_stack $(I32, I32, I32)
%11 = tuple_element_addr %10 : $*(I32, I32, I32), 0
%12 = tuple_element_addr %10 : $*(I32, I32, I32), 1
%13 = tuple_element_addr %10 : $*(I32, I32, I32), 2
store %5 to %11 : $*I32
store %7 to %12 : $*I32
store %9 to %13 : $*I32
%17 = unchecked_addr_cast %10 : $*(I32, I32, I32) to $*(I32, I32)
%18 = tuple_element_addr %17 : $*(I32, I32), 0
%19 = tuple_element_addr %17 : $*(I32, I32), 1
%20 = load %18 : $*I32
%21 = load %19 : $*I32
dealloc_stack %10 : $*(I32, I32, I32)
%23 = tuple_element_addr %1 : $*((I32, I32), I32), 0
%24 = tuple_element_addr %23 : $*(I32, I32), 0
%25 = load %24 : $*I32
%26 = tuple_element_addr %23 : $*(I32, I32), 1
%27 = load %26 : $*I32
%28 = tuple_element_addr %1 : $*((I32, I32), I32), 1
%29 = load %28 : $*I32
%30 = alloc_stack $((I32, I32), I32)
%31 = tuple_element_addr %30 : $*((I32, I32), I32), 0
%32 = tuple_element_addr %30 : $*((I32, I32), I32), 1
%33 = tuple_element_addr %31 : $*(I32, I32), 0
%34 = tuple_element_addr %31 : $*(I32, I32), 1
store %25 to %33 : $*I32
store %27 to %34 : $*I32
store %29 to %32 : $*I32
%38 = unchecked_addr_cast %30 : $*((I32, I32), I32) to $*(I32, I32)
%39 = tuple_element_addr %38 : $*(I32, I32), 0
%40 = tuple_element_addr %38 : $*(I32, I32), 1
%41 = load %39 : $*I32
%42 = load %40 : $*I32
dealloc_stack %30 : $*((I32, I32), I32)
%44 = alloc_stack $((I32, I32), I32)
%45 = tuple_element_addr %44 : $*((I32, I32), I32), 0
%46 = tuple_element_addr %44 : $*((I32, I32), I32), 1
%47 = tuple_element_addr %45 : $*(I32, I32), 0
%48 = tuple_element_addr %45 : $*(I32, I32), 1
store %25 to %47 : $*I32
store %27 to %48 : $*I32
store %29 to %46 : $*I32
%52 = unchecked_addr_cast %44 : $*((I32, I32), I32) to $*(I32, I32, I32)
%53 = tuple_element_addr %52 : $*(I32, I32, I32), 0
%54 = tuple_element_addr %52 : $*(I32, I32, I32), 1
%55 = tuple_element_addr %52 : $*(I32, I32, I32), 2
%56 = load %53 : $*I32
%57 = load %54 : $*I32
%58 = load %55 : $*I32
dealloc_stack %44 : $*((I32, I32), I32)
%60 = tuple (%20 : $I32, %21 : $I32)
%61 = tuple (%41 : $I32, %42 : $I32)
%62 = tuple (%56 : $I32, %57 : $I32, %58 : $I32)
%63 = tuple (%60 : $(I32, I32), %61 : $(I32, I32), %62 : $(I32, I32, I32))
return %63 : $((I32, I32), (I32, I32), (I32, I32, I32))
}