Files
swift-mirror/test/IRGen/loadable_by_address_reg2mem.sil
Arnold Schwaighofer 1e8e3b82df LargeTypesReg2Mem: Peephole load followed by single user to reuse the load's address
For example the pattern:

```
%e = load %1 : SomeEnum
switch_enum %e
```

The switch_enum_addr should reuse the load's address operand instead of
creating a new location for the SSA value of %e.

While we are there also don't create a new stack location for extracting payload
in a switch_enum_addr case target basic block if that extracted payload is not
used. That is if the basic block arguments in all switch target blocks are
unused in the switch_enum version.

```
switch_enum %enum, case #Optional.some!enumelt: bb1,
                   case #Optional.none!enumelt: bb2

bb2(%payload : $Payload):
   // unused %payload value
```

rdar://156602951
2025-08-07 07:21:51 -07:00

466 lines
13 KiB
Plaintext

// RUN: %target-swift-frontend %s -Xllvm -sil-print-types -Xllvm -sil-print-after=loadable-address -import-objc-header %S/Inputs/large_c.h -c -o %t/t.o 2>&1 | %FileCheck %s
// wasm currently disables aggressive reg2mem
// UNSUPPORTED: CPU=wasm32
// REQUIRES: PTRSIZE=64
sil_stage canonical
import Builtin
import Swift
struct X {
var x1 : Int
var x2 : Int
var x3 : Int
var x4: Int
var x5: Int
var x6: Int
var x7: Int
var x8: Int
var x9: Int
var x10: Int
var x11: Int
var x12: Int
var x13: Int
var x14: Int
var x15: Int
var x16: Int
}
struct X2 {
var x1 : Int
var x2 : Int
var x3 : Int
var x4: Int
var x5: Int
var x6: Int
var x7: Int
var x8: Int
var x9: Int
var x10: Int
var x11: Int
var x12: Int
var x13: Int
var x14: Int
var x15: Int
var x16: Int
}
struct Y {
var y1 : X
var y2: X
}
class C1 {
}
sil_vtable C1 {
}
struct Small {
var x1 : Int
}
// CHECK: sil @test1 : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: %0 = alloc_stack $Optional<X>
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = alloc_stack $Optional<X>
// CHECK: %3 = init_enum_data_addr %0 : $*Optional<X>, #Optional.some!enumelt
// CHECK: copy_addr [take] %1 to [init] %3 : $*X
// CHECK: inject_enum_addr %0 : $*Optional<X>, #Optional.some!enumelt
// CHECK: copy_addr [take] %0 to [init] %2 : $*Optional<X>
// CHECK: dealloc_stack %2 : $*Optional<X>
// CHECK: dealloc_stack %1 : $*X
// CHECK: dealloc_stack %0 : $*Optional<X>
// CHECK: } // end sil function 'test1'
sil @test1 : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $X
%1 = alloc_stack $Optional<X>
%2 = load %0 : $*X
%3 = enum $Optional<X>, #Optional.some!enumelt, %2 : $X
store %3 to %1 : $*Optional<X>
dealloc_stack %1 : $*Optional<X>
dealloc_stack %0 : $*X
%t = tuple ()
return %t : $()
}
sil @useX : $@convention(thin) (X) -> ()
// CHECK: sil @test2 : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: %0 = alloc_stack $Optional<X>
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = alloc_stack $Optional<X>
// CHECK: copy_addr [take] %2 to [init] %0 : $*Optional<X>
// CHECK: switch_enum_addr %2 : $*Optional<X>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
// CHECK: bb1:
// CHECK: %5 = unchecked_take_enum_data_addr %0 : $*Optional<X>, #Optional.some!enumelt
// CHECK: copy_addr [take] %5 to [init] %1 : $*X
// CHECK: %7 = function_ref @useX : $@convention(thin) (@in_guaranteed X) -> ()
// CHECK: %8 = apply %7(%1) : $@convention(thin) (@in_guaranteed X) -> ()
// CHECK: br bb3
// CHECK: bb2:
// CHECK: br bb3
// CHECK: bb3:
// CHECK: dealloc_stack %2 : $*Optional<X>
// CHECK: dealloc_stack %1 : $*X
// CHECK: dealloc_stack %0 : $*Optional<X>
// CHECK: } // end sil function 'test2'
sil @test2 : $@convention(thin) () -> () {
bb0:
%1 = alloc_stack $Optional<X>
%2 = load %1 : $*Optional<X>
switch_enum %2 : $Optional<X>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
bb1(%3: $X):
%f = function_ref @useX : $@convention(thin) (X) -> ()
apply %f(%3) : $@convention(thin) (X) -> ()
br bb3
bb2:
br bb3
bb3:
dealloc_stack %1 : $*Optional<X>
%t = tuple ()
return %t : $()
}
// CHECK: sil @test3 : $@convention(thin) () -> Builtin.Int1 {
// CHECK: bb0:
// CHECK: %0 = alloc_stack $Optional<X>
// CHECK: %1 = alloc_stack $Optional<X>
// CHECK: copy_addr [take] %1 to [init] %0 : $*Optional<X>
// CHECK: %3 = integer_literal $Builtin.Int1, -1
// CHECK: %4 = integer_literal $Builtin.Int1, 0
// CHECK: %5 = select_enum_addr %0 : $*Optional<X>, case #Optional.some!enumelt: %3, case #Optional.none!enumelt: %4 : $Builtin.Int1
// CHECK: dealloc_stack %1 : $*Optional<X>
// CHECK: dealloc_stack %0 : $*Optional<X>
// CHECK: return %5 : $Builtin.Int1
// CHECK: } // end sil function 'test3'
sil @test3 : $@convention(thin) () -> Builtin.Int1 {
bb0:
%1 = alloc_stack $Optional<X>
%2 = load %1 : $*Optional<X>
%3 = integer_literal $Builtin.Int1, -1
%4 = integer_literal $Builtin.Int1, 0
%r = select_enum %2 : $Optional<X>, case #Optional.some!enumelt: %3, case #Optional.none!enumelt: %4 : $Builtin.Int1
dealloc_stack %1 : $*Optional<X>
return %r : $Builtin.Int1
}
// CHECK: sil @test4 : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: %0 = alloc_stack $(X, X)
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = alloc_stack $X
// CHECK: %3 = alloc_stack $(X, X)
// CHECK: copy_addr [take] %2 to [init] %1 : $*X
// CHECK: %5 = tuple_element_addr %0 : $*(X, X), 0
// CHECK: copy_addr [take] %1 to [init] %5 : $*X
// CHECK: %7 = tuple_element_addr %0 : $*(X, X), 1
// CHECK: copy_addr [take] %1 to [init] %7 : $*X
// CHECK: copy_addr [take] %0 to [init] %3 : $*(X, X)
// CHECK: } // end sil function 'test4'
sil @test4 : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $X
%1 = alloc_stack $(X, X)
%2 = load %0 : $*X
%3 = tuple (%2 : $X , %2 : $X)
store %3 to %1 : $*(X, X)
dealloc_stack %1 : $*(X,X)
dealloc_stack %0 : $*X
%t = tuple ()
return %t : $()
}
// CHECK: sil @test5 : $@convention(thin) (@in (X, X)) -> () {
// CHECK: bb0(%0 : $*(X, X)):
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = tuple_element_addr %0 : $*(X, X), 1
// CHECK: copy_addr [take] %2 to [init] %1 : $*X
// CHECK: } // end sil function 'test5'
sil @test5 : $@convention(thin) (@in (X, X)) -> () {
bb0(%0 : $*(X, X)):
%1 = alloc_stack $X
%2 = load %0 : $*(X, X)
%4 = tuple_extract %2 : $(X, X), 1
store %4 to %1 : $*X
dealloc_stack %1 : $*X
%t = tuple ()
return %t : $()
}
// CHECK: sil @test6 : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: %0 = alloc_stack $Y
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = alloc_stack $X
// CHECK: %3 = alloc_stack $Y
// CHECK: copy_addr [take] %2 to [init] %1 : $*X
// CHECK: %5 = struct_element_addr %0 : $*Y, #Y.y1
// CHECK: copy_addr [take] %1 to [init] %5 : $*X
// CHECK: %7 = struct_element_addr %0 : $*Y, #Y.y2
// CHECK: copy_addr [take] %1 to [init] %7 : $*X
// CHECK: copy_addr [take] %0 to [init] %3 : $*Y
// CHECK: } // end sil function 'test6'
sil @test6 : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $X
%1 = alloc_stack $Y
%2 = load %0 : $*X
%3 = struct $Y (%2 : $X , %2 : $X)
store %3 to %1 : $*Y
dealloc_stack %1 : $*Y
dealloc_stack %0 : $*X
%t = tuple ()
return %t : $()
}
// CHECK: sil @test7 : $@convention(thin) (@in Y) -> () {
// CHECK: bb0(%0 : $*Y):
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = struct_element_addr %0 : $*Y, #Y.y1
// CHECK: copy_addr [take] %2 to [init] %1 : $*X
// CHECK: } // end sil function 'test7'
sil @test7 : $@convention(thin) (@in Y) -> () {
bb0(%0 : $*Y):
%1 = alloc_stack $X
%2 = load %0 : $*Y
%4 = struct_extract %2 : $Y, #Y.y1
store %4 to %1 : $*X
dealloc_stack %1 : $*X
%t = tuple ()
return %t : $()
}
// CHECK: sil @test8
// CHECK: bb0(%0 : $SamplesType):
// CHECK: %1 = alloc_stack $SamplesType
// CHECK: store %0 to %1 : $*SamplesType
sil @helper : $@convention(thin) (SamplesType) -> ()
sil @test8 : $@convention(c) (SamplesType) -> () {
bb0(%0 : $SamplesType):
%1 = function_ref @helper : $@convention(thin) (SamplesType) -> ()
%2 = apply %1(%0) : $@convention(thin) (SamplesType) -> ()
%t = tuple ()
return %t : $()
}
// In this test case Container type is identified as not large but contained
// type is.
sil @test9 : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $_ContainerType
%1 = load %0 : $*_ContainerType
%2 = alloc_stack $(_ContainedType, _ContainedType, _ContainedType, _ContainedType, _ContainedType,
_ContainedType, _ContainedType, _ContainedType, _ContainedType, _ContainedType)
%3 = struct_extract %1 : $_ContainerType, #_ContainerType.l
store %3 to %2 : $*(_ContainedType, _ContainedType, _ContainedType, _ContainedType, _ContainedType,
_ContainedType, _ContainedType, _ContainedType, _ContainedType, _ContainedType)
dealloc_stack %2 : $*(_ContainedType, _ContainedType, _ContainedType, _ContainedType, _ContainedType,
_ContainedType, _ContainedType, _ContainedType, _ContainedType, _ContainedType)
dealloc_stack %0 : $*_ContainerType
%t = tuple ()
return %t : $()
}
sil @test10 : $@convention(thin) (@in_guaranteed C1, @in Small) -> () {
bb0(%0 : $*C1, %1 : $*Small):
%2 = load %1 : $*Small
%3 = load %0 : $*C1
retain_value %3 : $C1
store %2 to %1 : $*Small
retain_value %3 : $C1
%t = tuple ()
return %t : $()
}
sil @use : $@convention(thin) (UInt32) -> ()
// We need to make sure we use the bigger alloc_stack for the address of
// unchecked_trivial_bit_cast. A bitcast can go from bigger to smaller type.
// CHECK: sil @test11 : $@convention(thin) (@in union_t) -> () {
// CHECK-NOT: unchecked_addr_cast %1 : $*union_t.__Unnamed_struct_in to $*union_t
// CHECK: unchecked_addr_cast {{.*}} : $*union_t to $*union_t.__Unnamed_struct_in
// CHECK: } // end sil function 'test11'
sil @test11 : $@convention(thin) (@in union_t) -> () {
bb0(%0 : $*union_t):
%1 = alloc_stack $union_t
%2 = alloc_stack $union_t
copy_addr [take] %0 to [init] %2 : $*union_t
%4 = load %2 : $*union_t
%7 = unchecked_trivial_bit_cast %4 : $union_t to $union_t.__Unnamed_struct_in
%8 = struct_extract %7 : $union_t.__Unnamed_struct_in, #union_t.__Unnamed_struct_in.slot
%10 = struct_extract %8 : $enum_t, #enum_t.rawValue
%11 = function_ref @use : $@convention(thin) (UInt32) -> ()
%12 = apply %11(%10) : $@convention(thin) (UInt32) -> ()
%13 = tuple ()
dealloc_stack %2 : $*union_t
dealloc_stack %1 : $*union_t
return %13 : $()
}
// This test case used to crash because we did not handle `mark_dependence`.
sil @test12 : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $X
%1 = alloc_stack $Y
%2 = alloc_stack $X
%3 = load %0 : $*X
%4 = load %2 : $*X
%5 = mark_dependence %3 : $X on %4 : $X
%6 = struct $Y (%5 : $X , %5 : $X)
store %6 to %1 : $*Y
dealloc_stack %2 : $*X
dealloc_stack %1 : $*Y
dealloc_stack %0 : $*X
%t = tuple ()
return %t : $()
}
// CHECK: sil @test13
// CHECK: [[ADDR:%.*]] = unchecked_addr_cast %2 : $*X to $*Y
// CHECK: copy_addr [take] [[ADDR]] to [init] %1 : $*Y
// CHECK: } // end sil function 'test13'
sil @test13 : $@convention(thin) (@in X) -> () {
bb0(%0 : $*X):
%1 = alloc_stack $Y
%2 = alloc_stack $X
copy_addr [take] %0 to [init] %2 : $*X
%4 = load %2 : $*X
%7 = unchecked_bitwise_cast %4 : $X to $Y
store %7 to %1: $*Y
%13 = tuple ()
dealloc_stack %2 : $*X
dealloc_stack %1 : $*Y
return %13 : $()
}
// CHECK: sil @test14
// CHECK: [[VAL:%.*]] = load {{.*}} : $*X
// CHECK: throw [[VAL]]
// CHECK: } // end sil function 'test14'
sil @test14 : $@convention(thin) (@in X) -> @error X {
bb0(%0 : $*X):
%1 = alloc_stack $X
copy_addr [take] %0 to [init] %1 : $*X
%3 = load %1 : $*X
dealloc_stack %1 : $*X
throw %3 : $X
}
// CHECK: sil @test15
// CHECK: cond_br %1, bb1, bb2
// CHECK: bb1:
// CHECK: copy_addr [take] {{.*}} to [init] [[PHI:%[0-9]+]] : $*X
// CHECK: br bb3
// CHECK: bb2:
// CHECK: [[UNDEF:%.*]] = alloc_stack $X
// CHECK: copy_addr [take] [[UNDEF]] to [init] [[PHI]] : $*X
// CHECK: dealloc_stack [[UNDEF]] : $*X
// CHECK: br bb3
// CHECK:} // end sil function 'test15'
sil @test15 : $@convention(thin) (X, Builtin.Int1) -> () {
bb0(%0: $X, %1: $Builtin.Int1):
%2 = alloc_stack $X
cond_br %1, bb1, bb2
bb1:
br bb3(%0 : $X)
bb2:
br bb3(undef : $X)
bb3(%4 : $X):
store %4 to %2: $*X
dealloc_stack %2 : $*X
%t = tuple ()
return %t : $()
}
sil @usei8 : $@convention(thin) (UInt8) -> ()
// CHECK: sil @test16
// CHECK: bb0(%0 : $member_id_t):
// CHECK: [[T0:%.*]] = alloc_stack $member_id_t
// CHECK: store %0 to [[T0]] : $*member_id_t
// CHECK: struct_element_addr [[T0]]
// CHECK:} // end sil function 'test16'
sil @test16 : $@convention(thin) (member_id_t) -> () {
bb0(%0 : $member_id_t):
%2 = alloc_stack $X
%44 = struct_extract %0 : $member_id_t, #member_id_t.member_value // user: %45
%45 = unchecked_trivial_bit_cast %44 : $member_id_t.__Unnamed_union_member_value to $(UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
%46 = tuple_extract %45 : $(UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8), 0
%11 = function_ref @usei8 : $@convention(thin) (UInt8) -> ()
%12 = apply %11(%46) : $@convention(thin) (UInt8) -> ()
dealloc_stack %2 : $*X
%13 = tuple ()
return %13 : $()
}
struct Z {
var r: String
var p: X
}
// CHECK: sil @test17 : $@convention(thin) (@owned String) -> () {
// CHECK: bb0(%0 : $String):
// CHECK: [[T0:%.*]] = alloc_stack $String
// CHECK: store %0 to [[T0]] : $*String
// CHECK: [[T1:%.*]] = unchecked_addr_cast [[T0]] : $*String to $*Z
// CHECK: release_value_addr [[T1]] : $*Z
sil @test17: $@convention(thin) (@owned String) -> () {
bb0(%0 : $String):
%1 = unchecked_bitwise_cast %0 : $String to $Z
release_value %1 : $Z
%13 = tuple ()
return %13 : $()
}
sil_global private @global : $Optional<X>
sil @test18: $@convention(thin) () -> () {
bb0:
%0 = global_addr @global : $*Optional<X>
%1 = begin_access [modify] [static] [no_nested_conflict] %0
%2 = load %1
switch_enum %2, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb1
bb1:
%4 = integer_literal $Builtin.Int1, -1
cond_fail %4, "Unexpectedly found nil while unwrapping an Optional value"
unreachable
bb2(%7 : $X):
%8 = unchecked_take_enum_data_addr %1, #Optional.some!enumelt
end_access %1
%13 = tuple ()
return %13 : $()
}