mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
When eliminating an enum from an alloc_stack, accept "dead" inject_enum_addr instructions, which inject different enum cases.
666 lines
23 KiB
Plaintext
666 lines
23 KiB
Plaintext
// RUN: %target-sil-opt -enable-sil-verify-all %s -sil-combine | %FileCheck %s
|
|
|
|
sil_stage canonical
|
|
|
|
import Builtin
|
|
import Swift
|
|
|
|
class SomeClass {
|
|
func hash() -> Int
|
|
}
|
|
|
|
enum Numerals {
|
|
case One
|
|
case Two
|
|
case Three
|
|
case Four
|
|
}
|
|
|
|
sil @external_func: $@convention(thin) () -> ()
|
|
|
|
//CHECK-LABEL: eliminate_sw_enum_addr
|
|
//CHECK-NOT: switch_enum_addr
|
|
//CHECK: switch_enum
|
|
//CHECK: return
|
|
sil @eliminate_sw_enum_addr : $@convention(thin) () -> Int {
|
|
bb0:
|
|
%0 = alloc_stack $Optional<SomeClass>, var, name "x" // users: %2, %4, %5, %17, %19
|
|
%1 = alloc_ref $SomeClass // user: %3
|
|
%2 = init_enum_data_addr %0 : $*Optional<SomeClass>, #Optional.some!enumelt // user: %3
|
|
store %1 to %2 : $*SomeClass // id: %3
|
|
inject_enum_addr %0 : $*Optional<SomeClass>, #Optional.some!enumelt // id: %4
|
|
%5 = load %0 : $*Optional<SomeClass> // users: %6, %8, %9, %14
|
|
retain_value %5 : $Optional<SomeClass>
|
|
%7 = alloc_stack $Optional<SomeClass> // users: %9, %10, %11, %13
|
|
retain_value %5 : $Optional<SomeClass>
|
|
store %5 to %7 : $*Optional<SomeClass> // id: %9
|
|
switch_enum_addr %7 : $*Optional<SomeClass>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %10
|
|
|
|
bb1: // Preds: bb0
|
|
%11 = unchecked_take_enum_data_addr %7 : $*Optional<SomeClass>, #Optional.some!enumelt // user: %12
|
|
%12 = load %11 : $*SomeClass // users: %15, %16
|
|
dealloc_stack %7 : $*Optional<SomeClass> // id: %13
|
|
release_value %5 : $Optional<SomeClass> // id: %14
|
|
%15 = class_method %12 : $SomeClass, #SomeClass.hash : (SomeClass) -> () -> Int, $@convention(method) (@guaranteed SomeClass) -> Int // user: %16
|
|
%16 = apply %15(%12) : $@convention(method) (@guaranteed SomeClass) -> Int // user: %20
|
|
%17 = load %0 : $*Optional<SomeClass> // user: %18
|
|
release_value %17 : $Optional<SomeClass> // id: %18
|
|
dealloc_stack %0 : $*Optional<SomeClass> // id: %19
|
|
return %16 : $Int // id: %20
|
|
|
|
bb2: // Preds: bb0
|
|
unreachable // id: %23
|
|
}
|
|
|
|
// CHECK-LABEL: sil @eliminate_select_enum_addr
|
|
// CHECK-NOT: select_enum_addr
|
|
// CHECK: select_enum
|
|
// CHECK: return
|
|
|
|
sil @eliminate_select_enum_addr : $@convention(thin) () -> Int {
|
|
bb0:
|
|
%0 = alloc_stack $Optional<SomeClass>
|
|
%1 = alloc_ref $SomeClass
|
|
%2 = init_enum_data_addr %0 : $*Optional<SomeClass>, #Optional.some!enumelt
|
|
store %1 to %2 : $*SomeClass
|
|
inject_enum_addr %0 : $*Optional<SomeClass>, #Optional.some!enumelt
|
|
%5 = load %0 : $*Optional<SomeClass>
|
|
retain_value %5 : $Optional<SomeClass>
|
|
%7 = alloc_stack $Optional<SomeClass>
|
|
retain_value %5 : $Optional<SomeClass>
|
|
store %5 to %7 : $*Optional<SomeClass>
|
|
%t = integer_literal $Builtin.Int1, -1
|
|
%f = integer_literal $Builtin.Int1, 0
|
|
%b = select_enum_addr %7 : $*Optional<SomeClass>, case #Optional.some!enumelt: %t, case #Optional.none!enumelt: %f : $Builtin.Int1
|
|
cond_br %b, bb1, bb2
|
|
|
|
bb1:
|
|
%11 = unchecked_take_enum_data_addr %7 : $*Optional<SomeClass>, #Optional.some!enumelt
|
|
%12 = load %11 : $*SomeClass // users: %15, %16
|
|
dealloc_stack %7 : $*Optional<SomeClass>
|
|
release_value %5 : $Optional<SomeClass>
|
|
%15 = class_method %12 : $SomeClass, #SomeClass.hash : (SomeClass) -> () -> Int, $@convention(method) (@guaranteed SomeClass) -> Int
|
|
%16 = apply %15(%12) : $@convention(method) (@guaranteed SomeClass) -> Int
|
|
%17 = load %0 : $*Optional<SomeClass>
|
|
release_value %17 : $Optional<SomeClass>
|
|
dealloc_stack %0 : $*Optional<SomeClass>
|
|
return %16 : $Int
|
|
|
|
bb2:
|
|
// Invoke something here and jump to bb1. This prevents a cond_br(select_enum) -> switch_enum conversion,
|
|
// since it would introduce a critical edge.
|
|
%20 = function_ref @external_func: $@convention(thin) () -> ()
|
|
apply %20(): $@convention(thin) () -> ()
|
|
br bb1
|
|
}
|
|
|
|
enum E {
|
|
case E0
|
|
case E1
|
|
case E2
|
|
}
|
|
|
|
// CHECK-LABEL: sil @canonicalize_select_enum
|
|
// CHECK: select_enum {{.*}} case #E.E2!enumelt:
|
|
// CHECK: return
|
|
|
|
sil @canonicalize_select_enum : $@convention(thin) (E) -> Int32 {
|
|
bb0(%0 : $E):
|
|
%1 = integer_literal $Builtin.Int32, 0
|
|
%2 = integer_literal $Builtin.Int32, 1
|
|
%3 = integer_literal $Builtin.Int32, 2
|
|
%4 = select_enum %0 : $E, case #E.E0!enumelt: %1, case #E.E1!enumelt: %2, default %3 : $Builtin.Int32
|
|
%5 = struct $Int32 (%4 : $Builtin.Int32)
|
|
return %5 : $Int32
|
|
}
|
|
|
|
enum G<T> {
|
|
case E0
|
|
case E1(T)
|
|
case E2
|
|
}
|
|
|
|
// CHECK-LABEL: sil @canonicalize_select_enum_addr
|
|
// CHECK: select_enum_addr {{.*}} case #G.E2!enumelt:
|
|
// CHECK: return
|
|
|
|
sil @canonicalize_select_enum_addr : $@convention(thin) <T> (@in G<T>) -> Int32 {
|
|
bb0(%0 : $*G<T>):
|
|
%2 = integer_literal $Builtin.Int32, 0
|
|
%3 = integer_literal $Builtin.Int32, 1
|
|
%4 = integer_literal $Builtin.Int32, 2
|
|
%5 = select_enum_addr %0 : $*G<T>, case #G.E0!enumelt: %2, case #G.E1!enumelt: %3, default %4 : $Builtin.Int32
|
|
%6 = struct $Int32 (%5 : $Builtin.Int32)
|
|
return %6 : $Int32
|
|
}
|
|
|
|
// CHECK-LABEL: sil @canonicalize_init_enum_data_addr
|
|
// CHECK-NOT: init_enum_data_addr
|
|
// CHECK-NOT: inject_enum_addr
|
|
// CHECK: enum $Optional<Int32>, #Optional.some!enumelt
|
|
// CHECK-NOT: inject_enum_addr
|
|
// CHECK: return
|
|
sil @canonicalize_init_enum_data_addr : $@convention(thin) (@inout Int32, Builtin.Int32, Optional<Int32>) -> Int32 {
|
|
bb0(%0 : $*Int32, %1 : $Builtin.Int32, %2 : $Optional<Int32>):
|
|
%s1 = alloc_stack $Optional<Int32>
|
|
store %2 to %s1 : $*Optional<Int32>
|
|
%e1 = init_enum_data_addr %s1 : $*Optional<Int32>, #Optional.some!enumelt
|
|
%v = load %0 : $*Int32
|
|
store %v to %e1 : $*Int32
|
|
%i1 = integer_literal $Builtin.Int32, 1
|
|
%i0 = integer_literal $Builtin.Int1, 0
|
|
%a = builtin "sadd_with_overflow_Int32"(%1 : $Builtin.Int32, %i1 : $Builtin.Int32, %i0 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1)
|
|
%w = tuple_extract %a : $(Builtin.Int32, Builtin.Int1), 0
|
|
%i = struct $Int32 (%w : $Builtin.Int32)
|
|
store %i to %0 : $*Int32
|
|
inject_enum_addr %s1 : $*Optional<Int32>, #Optional.some!enumelt
|
|
dealloc_stack %s1 : $*Optional<Int32>
|
|
return %i : $Int32
|
|
}
|
|
|
|
// CHECK-LABEL: sil @canonicalize_init_enum_data_addr_diff_basic_blocks
|
|
// CHECK-NOT: init_enum_data_addr
|
|
// CHECK-NOT: inject_enum_addr
|
|
// CHECK: enum $Optional<Int32>, #Optional.some!enumelt
|
|
// CHECK-NOT: inject_enum_addr
|
|
// CHECK: return
|
|
sil @canonicalize_init_enum_data_addr_diff_basic_blocks : $@convention(thin) (@inout Int32, Builtin.Int32, Optional<Int32>) -> Int32 {
|
|
bb0(%0 : $*Int32, %1 : $Builtin.Int32, %2 : $Optional<Int32>):
|
|
%s1 = alloc_stack $Optional<Int32>
|
|
store %2 to %s1 : $*Optional<Int32>
|
|
%e1 = init_enum_data_addr %s1 : $*Optional<Int32>, #Optional.some!enumelt
|
|
%v = load %0 : $*Int32
|
|
store %v to %e1 : $*Int32
|
|
%i1 = integer_literal $Builtin.Int32, 1
|
|
%i0 = integer_literal $Builtin.Int1, 0
|
|
%a = builtin "sadd_with_overflow_Int32"(%1 : $Builtin.Int32, %i1 : $Builtin.Int32, %i0 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1)
|
|
%w = tuple_extract %a : $(Builtin.Int32, Builtin.Int1), 0
|
|
%i = struct $Int32 (%w : $Builtin.Int32)
|
|
br bb1
|
|
|
|
bb1: // Preds: bb0
|
|
store %i to %0 : $*Int32
|
|
inject_enum_addr %s1 : $*Optional<Int32>, #Optional.some!enumelt
|
|
dealloc_stack %s1 : $*Optional<Int32>
|
|
return %i : $Int32
|
|
}
|
|
|
|
// CHECK-LABEL: sil @fail_to_canonicalize_init_enum_data_addr_reach_entry
|
|
// CHECK: init_enum_data_addr
|
|
// CHECK: inject_enum_addr
|
|
// CHECK-NOT: enum $Optional<Int32>, #Optional.some!enumelt
|
|
// CHECK: return
|
|
sil @fail_to_canonicalize_init_enum_data_addr_reach_entry : $@convention(thin) (@inout Int32, Builtin.Int32, Builtin.Int1) -> Int32 {
|
|
bb0(%0 : $*Int32, %1 : $Builtin.Int32, %2 : $Builtin.Int1):
|
|
%s1 = alloc_stack $Optional<Int32>
|
|
%i2 = load %0 : $*Int32
|
|
%en = enum $Optional<Int32>, #Optional.none!enumelt
|
|
store %en to %s1 : $*Optional<Int32>
|
|
cond_br %2, bb1, bb2
|
|
|
|
bb1:
|
|
%e1 = init_enum_data_addr %s1 : $*Optional<Int32>, #Optional.some!enumelt
|
|
%v = load %0 : $*Int32
|
|
store %v to %e1 : $*Int32
|
|
%i1 = integer_literal $Builtin.Int32, 1
|
|
%i0 = integer_literal $Builtin.Int1, 0
|
|
%a = builtin "sadd_with_overflow_Int32"(%1 : $Builtin.Int32, %i1 : $Builtin.Int32, %i0 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1)
|
|
%w = tuple_extract %a : $(Builtin.Int32, Builtin.Int1), 0
|
|
%i = struct $Int32 (%w : $Builtin.Int32)
|
|
store %i to %0 : $*Int32
|
|
br bb2
|
|
|
|
bb2: // Preds: bb0 bb1
|
|
inject_enum_addr %s1 : $*Optional<Int32>, #Optional.some!enumelt
|
|
dealloc_stack %s1 : $*Optional<Int32>
|
|
return %i2 : $Int32
|
|
}
|
|
|
|
// CHECK-LABEL: sil @fail_to_canonicalize_init_enum_data_addr
|
|
// CHECK: init_enum_data_addr
|
|
// CHECK: inject_enum_addr
|
|
// CHECK-NOT: enum $Optional<Int32>, #Optional.some!enumelt
|
|
// CHECK: return
|
|
sil @fail_to_canonicalize_init_enum_data_addr : $@convention(thin) (@inout Int32, Builtin.Int32) -> Int32 {
|
|
bb0(%0 : $*Int32, %1 : $Builtin.Int32):
|
|
%s1 = alloc_stack $Optional<Int32>
|
|
%e1 = init_enum_data_addr %s1 : $*Optional<Int32>, #Optional.some!enumelt
|
|
%v = load %0 : $*Int32
|
|
store %v to %e1 : $*Int32
|
|
%i1 = integer_literal $Builtin.Int32, 1
|
|
%i0 = integer_literal $Builtin.Int1, 0
|
|
%a = builtin "sadd_with_overflow_Int32"(%1 : $Builtin.Int32, %i1 : $Builtin.Int32, %i0 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1)
|
|
%w = tuple_extract %a : $(Builtin.Int32, Builtin.Int1), 0
|
|
%i = struct $Int32 (%w : $Builtin.Int32)
|
|
br bb1
|
|
|
|
bb1:
|
|
%o = enum $Optional<Int32>, #Optional.none!enumelt
|
|
store %o to %s1 : $*Optional<Int32>
|
|
br bb2
|
|
|
|
bb2:
|
|
store %i to %0 : $*Int32
|
|
inject_enum_addr %s1 : $*Optional<Int32>, #Optional.some!enumelt
|
|
dealloc_stack %s1 : $*Optional<Int32>
|
|
return %i : $Int32
|
|
}
|
|
|
|
// Check the cond_br(select_enum) -> switch_enum conversion.
|
|
//
|
|
// CHECK-LABEL: sil @convert_select_enum_cond_br_to_switch_enum
|
|
// CHECK-NOT: select_enum
|
|
// CHECK: switch_enum
|
|
// CHECK: return
|
|
|
|
sil @convert_select_enum_cond_br_to_switch_enum : $@convention(thin) (@owned Optional<SomeClass>) -> Int {
|
|
bb0(%0 : $Optional<SomeClass>):
|
|
%1 = integer_literal $Builtin.Int1, 0
|
|
%2 = integer_literal $Builtin.Int1, -1
|
|
%3 = select_enum %0 : $Optional<SomeClass>, case #Optional.none!enumelt: %2, case #Optional.some!enumelt: %1 : $Builtin.Int1
|
|
cond_br %3, bb2, bb1
|
|
|
|
bb1:
|
|
%5 = unchecked_enum_data %0 : $Optional<SomeClass>, #Optional.some!enumelt
|
|
%6 = class_method %5 : $SomeClass, #SomeClass.hash : (SomeClass) -> () -> Int, $@convention(method) (@guaranteed SomeClass) -> Int
|
|
%7 = apply %6(%5) : $@convention(method) (@guaranteed SomeClass) -> Int
|
|
fix_lifetime %5 : $SomeClass
|
|
strong_release %5 : $SomeClass
|
|
return %7 : $Int
|
|
|
|
bb2:
|
|
cond_fail %2 : $Builtin.Int1
|
|
unreachable
|
|
}
|
|
|
|
// Check that cond_br(select_enum) is converted into switch_enum.
|
|
// CHECK-LABEL: sil @convert_select_enum_cond_br_to_switch_enum2
|
|
// CHECK: bb0
|
|
// CHECK-NOT: select_enum
|
|
// CHECK-NOT: return
|
|
// CHECK: switch_enum %0 : $Numerals, case #Numerals.Two!enumelt: bb3, default bb2
|
|
// CHECK: return
|
|
// CHECK: }
|
|
sil @convert_select_enum_cond_br_to_switch_enum2 : $@convention(thin) (Numerals) -> Builtin.Int64 {
|
|
bb0(%0 : $Numerals):
|
|
%2 = integer_literal $Builtin.Int1, 0
|
|
%3 = integer_literal $Builtin.Int1, -1
|
|
// All cases but one are the same. So, they can be made a default for the switch_enum.
|
|
%4 = select_enum %0 : $Numerals, case #Numerals.One!enumelt: %3, case #Numerals.Two!enumelt: %2, case #Numerals.Three!enumelt: %3, case #Numerals.Four!enumelt: %3 : $Builtin.Int1
|
|
|
|
cond_br %4, bb2, bb3
|
|
|
|
bb1:
|
|
%7 = integer_literal $Builtin.Int64, 10
|
|
return %7 : $Builtin.Int64
|
|
|
|
bb2:
|
|
%10 = function_ref @external_func: $@convention(thin) () -> ()
|
|
apply %10(): $@convention(thin) () -> ()
|
|
br bb1
|
|
|
|
bb3:
|
|
br bb1
|
|
}
|
|
|
|
// Check that cond_br(select_enum) is converted into switch_enum.
|
|
// This test checks that select_enum instructions with default cases are handled correctly.
|
|
// CHECK-LABEL: sil @convert_select_enum_cond_br_to_switch_enum3
|
|
// CHECK: bb0
|
|
// CHECK-NOT: select_enum
|
|
// CHECK-NOT: return
|
|
// CHECK: switch_enum %0 : $Numerals, case #Numerals.Two!enumelt: bb3, default bb2
|
|
// CHECK: return
|
|
// CHECK: }
|
|
sil @convert_select_enum_cond_br_to_switch_enum3 : $@convention(thin) (Numerals) -> Builtin.Int64 {
|
|
bb0(%0 : $Numerals):
|
|
%2 = integer_literal $Builtin.Int1, 0
|
|
%3 = integer_literal $Builtin.Int1, -1
|
|
// There is only one case, whose result is different from default and other cases
|
|
// Thus all other cases can be folded into a default cases of a switch_enum.
|
|
%4 = select_enum %0 : $Numerals, case #Numerals.One!enumelt: %3, case #Numerals.Two!enumelt: %2, case #Numerals.Three!enumelt: %3, default %3 : $Builtin.Int1
|
|
|
|
cond_br %4, bb2, bb3
|
|
|
|
bb1:
|
|
%7 = integer_literal $Builtin.Int64, 10
|
|
return %7 : $Builtin.Int64
|
|
|
|
bb2:
|
|
%10 = function_ref @external_func: $@convention(thin) () -> ()
|
|
apply %10(): $@convention(thin) () -> ()
|
|
br bb1
|
|
|
|
bb3:
|
|
br bb1
|
|
}
|
|
|
|
|
|
// Check that cond_br(select_enum) is not converted into switch_enum as it would create a critical edge, which
|
|
// is not originating from cond_br/br. And this is forbidden in a canonical SIL form.
|
|
//
|
|
// CHECK-LABEL: sil @dont_convert_select_enum_cond_br_to_switch_enum
|
|
// CHECK: select_enum
|
|
// CHECK-NOT: switch_enum
|
|
// CHECK: return
|
|
sil @dont_convert_select_enum_cond_br_to_switch_enum : $@convention(thin) (@owned Optional<SomeClass>) -> Int {
|
|
bb0(%0 : $Optional<SomeClass>):
|
|
%2 = integer_literal $Builtin.Int1, 0
|
|
%3 = integer_literal $Builtin.Int1, -1
|
|
%4 = select_enum %0 : $Optional<SomeClass>, case #Optional.none!enumelt: %3, case #Optional.some!enumelt: %2 : $Builtin.Int1
|
|
cond_br %4, bb2, bb1
|
|
|
|
bb1:
|
|
%5 = unchecked_enum_data %0 : $Optional<SomeClass>, #Optional.some!enumelt
|
|
%6 = class_method %5 : $SomeClass, #SomeClass.hash : (SomeClass) -> () -> Int, $@convention(method) (@guaranteed SomeClass) -> Int
|
|
%7 = apply %6(%5) : $@convention(method) (@guaranteed SomeClass) -> Int
|
|
fix_lifetime %5 : $SomeClass
|
|
strong_release %5 : $SomeClass
|
|
return %7 : $Int
|
|
|
|
bb2:
|
|
%10 = function_ref @external_func: $@convention(thin) () -> ()
|
|
apply %10(): $@convention(thin) () -> ()
|
|
br bb1
|
|
}
|
|
|
|
// Check that cond_br(select_enum) is not converted into switch_enum as it would create a critical edge, which
|
|
// is not originating from cond_br/br. And this is forbidden in a canonical SIL form.
|
|
//
|
|
// CHECK-LABEL: sil @dont_convert_select_enum_cond_br_to_switch_enum2
|
|
// CHECK: select_enum
|
|
// CHECK-NOT: switch_enum
|
|
// CHECK: return
|
|
sil @dont_convert_select_enum_cond_br_to_switch_enum2 : $@convention(thin) (Numerals) -> Builtin.Int64 {
|
|
bb0(%0 : $Numerals):
|
|
%2 = integer_literal $Builtin.Int1, 0
|
|
%3 = integer_literal $Builtin.Int1, -1
|
|
// There are two cases for each possible outcome.
|
|
// This means that we would always get a critical edge, if we convert it into a switch_enum.
|
|
%4 = select_enum %0 : $Numerals, case #Numerals.One!enumelt: %3, case #Numerals.Two!enumelt: %2, case #Numerals.Three!enumelt: %3, case #Numerals.Four!enumelt: %2 : $Builtin.Int1
|
|
|
|
cond_br %4, bb2, bb3
|
|
|
|
bb1:
|
|
%7 = integer_literal $Builtin.Int64, 10
|
|
return %7 : $Builtin.Int64
|
|
|
|
bb2:
|
|
%10 = function_ref @external_func: $@convention(thin) () -> ()
|
|
apply %10(): $@convention(thin) () -> ()
|
|
br bb1
|
|
|
|
bb3:
|
|
br bb1
|
|
}
|
|
|
|
// Check that cond_br(select_enum) is not converted into switch_enum,
|
|
// because the result of the default case is not an integer literal.
|
|
// CHECK-LABEL: sil @dont_convert_select_enum_cond_br_to_switch_enum3
|
|
// CHECK: select_enum
|
|
// CHECK-NOT: switch_enum
|
|
// CHECK: return
|
|
sil @dont_convert_select_enum_cond_br_to_switch_enum3 : $@convention(thin) (Numerals, Builtin.Int1) -> Builtin.Int64 {
|
|
bb0(%0 : $Numerals, %1 : $Builtin.Int1):
|
|
%2 = integer_literal $Builtin.Int1, 0
|
|
%3 = integer_literal $Builtin.Int1, -1
|
|
// All cases but one are the same. So, they can be made a default for the switch_enum.
|
|
%4 = select_enum %0 : $Numerals, case #Numerals.One!enumelt: %3, case #Numerals.Two!enumelt: %2, case #Numerals.Three!enumelt: %3, default %1 : $Builtin.Int1
|
|
|
|
cond_br %4, bb2, bb3
|
|
|
|
bb1:
|
|
%7 = integer_literal $Builtin.Int64, 10
|
|
return %7 : $Builtin.Int64
|
|
|
|
bb2:
|
|
%10 = function_ref @external_func: $@convention(thin) () -> ()
|
|
apply %10(): $@convention(thin) () -> ()
|
|
br bb1
|
|
|
|
bb3:
|
|
br bb1
|
|
}
|
|
|
|
public class C {}
|
|
public struct S {}
|
|
public struct T {
|
|
@_hasStorage let c: C
|
|
@_hasStorage let s: S
|
|
}
|
|
public enum X {
|
|
case none
|
|
case some(T)
|
|
}
|
|
public enum Y {
|
|
case none
|
|
case some(T)
|
|
}
|
|
|
|
// Verify that we do not optimize
|
|
// (unchecked_enum_data (unchecked_bitwise_cast V : $X to $Y), #Case)
|
|
// where Case holds of a payload of type P into:
|
|
// (unchecked_ref_cast V : $X to $P)
|
|
// even for single-payload enums, since we cannot know the layouts of
|
|
// the types involved, and we'll generate a trap at IRGen-time if the
|
|
// bitcasted types are not the same size.
|
|
|
|
// CHECK-LABEL: sil @keep_unchecked_enum_data
|
|
sil @keep_unchecked_enum_data : $@convention(thin) (@owned X, @owned T) -> @owned T {
|
|
// CHECK: bb0
|
|
bb0(%0 : $X, %1 : $T):
|
|
// CHECK: [[CAST:%.*]] = unchecked_bitwise_cast %0 : $X to $Y
|
|
%4 = unchecked_bitwise_cast %0 : $X to $Y
|
|
switch_enum %4 : $Y, case #Y.none!enumelt: bb1, case #Y.some!enumelt: bb2
|
|
|
|
// CHECK: bb1
|
|
bb1:
|
|
%7 = struct_extract %1 : $T, #T.c
|
|
strong_retain %7 : $C
|
|
br bb3(%7 : $C)
|
|
|
|
// CHECK: bb2
|
|
bb2(%10 : $T):
|
|
// CHECK: unchecked_enum_data [[CAST]] : $Y, #Y.some!enumelt
|
|
%11 = unchecked_enum_data %4 : $Y, #Y.some!enumelt
|
|
%12 = struct_extract %11 : $T, #T.c
|
|
br bb3(%12 : $C)
|
|
|
|
// CHECK: bb3
|
|
bb3(%14 : $C):
|
|
%15 = struct $S ()
|
|
%16 = struct $T (%14 : $C, %15 : $S)
|
|
%17 = struct_extract %1 : $T, #T.c
|
|
strong_release %17 : $C
|
|
// CHECK: return
|
|
return %16 : $T
|
|
}
|
|
|
|
// CHECK-LABEL: sil @test_inject_tuple
|
|
// CHECK: [[A:%[0-9]+]] = alloc_stack $Optional<(Int, Int)>
|
|
// CHECK: [[T:%[0-9]+]] = tuple (%0 : $Int, %1 : $Int)
|
|
// CHECK: [[E:%[0-9]+]] = enum $Optional<(Int, Int)>, #Optional.some!enumelt, [[T]] : $(Int, Int)
|
|
// CHECK: store [[E]] to [[A]]
|
|
// CHECK: [[L:%[0-9]+]] = load [[A]]
|
|
// CHECK: return [[L]]
|
|
// CHECK: } // end sil function 'test_inject_tuple'
|
|
sil @test_inject_tuple : $@convention(thin) (Int, Int) -> Optional<(Int, Int)> {
|
|
bb0(%0 : $Int, %1 : $Int):
|
|
%17 = alloc_stack $Optional<(Int, Int)>
|
|
%45 = init_enum_data_addr %17 : $*Optional<(Int, Int)>, #Optional.some!enumelt
|
|
%46 = tuple_element_addr %45 : $*(Int, Int), 0
|
|
%47 = tuple_element_addr %45 : $*(Int, Int), 1
|
|
store %0 to %46 : $*Int
|
|
store %1 to %47 : $*Int
|
|
inject_enum_addr %17 : $*Optional<(Int, Int)>, #Optional.some!enumelt
|
|
%r = load %17 : $*Optional<(Int, Int)>
|
|
dealloc_stack %17 : $*Optional<(Int, Int)>
|
|
return %r : $Optional<(Int, Int)>
|
|
}
|
|
|
|
enum MP {
|
|
case A(S)
|
|
case B(S)
|
|
}
|
|
|
|
sil @take_s : $@convention(thin) (@in S) -> ()
|
|
sil @take_t : $@convention(thin) (@in T) -> ()
|
|
sil @use_mp : $@convention(thin) (@in_guaranteed MP) -> ()
|
|
|
|
// CHECK-LABEL: sil @expand_alloc_stack_of_enum1
|
|
// CHECK: [[A:%[0-9]+]] = alloc_stack $S
|
|
// CHECK: bb1:
|
|
// CHECK-NEXT: store %0 to [[A]]
|
|
// CHECK: bb2:
|
|
// CHECK-NEXT: store %0 to [[A]]
|
|
// CHECK: bb3:
|
|
// CHECK: apply {{%[0-9]+}}([[A]])
|
|
// CHECK: } // end sil function 'expand_alloc_stack_of_enum1'
|
|
sil @expand_alloc_stack_of_enum1 : $@convention(method) (S) -> () {
|
|
bb0(%0 : $S):
|
|
%1 = alloc_stack $MP
|
|
cond_br undef, bb1, bb2
|
|
bb1:
|
|
%2 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt
|
|
store %0 to %2 : $*S
|
|
inject_enum_addr %1 : $*MP, #MP.A!enumelt
|
|
br bb3
|
|
bb2:
|
|
%3 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt
|
|
store %0 to %3 : $*S
|
|
inject_enum_addr %1 : $*MP, #MP.A!enumelt
|
|
br bb3
|
|
bb3:
|
|
%7 = unchecked_take_enum_data_addr %1 : $*MP, #MP.A!enumelt
|
|
%8 = function_ref @take_s : $@convention(thin) (@in S) -> ()
|
|
%9 = apply %8(%7) : $@convention(thin) (@in S) -> ()
|
|
dealloc_stack %1 : $*MP
|
|
%11 = tuple ()
|
|
return %11 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @expand_alloc_stack_of_enum_without_take
|
|
// CHECK: [[A:%[0-9]+]] = alloc_stack $S
|
|
// CHECK: bb1:
|
|
// CHECK-NEXT: store %0 to [[A]]
|
|
// CHECK: bb2:
|
|
// CHECK-NEXT: store %0 to [[A]]
|
|
// CHECK: bb3:
|
|
// CHECK: destroy_addr [[A]]
|
|
// CHECK: } // end sil function 'expand_alloc_stack_of_enum_without_take'
|
|
sil @expand_alloc_stack_of_enum_without_take : $@convention(method) (S) -> () {
|
|
bb0(%0 : $S):
|
|
%1 = alloc_stack $MP
|
|
cond_br undef, bb1, bb2
|
|
bb1:
|
|
%2 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt
|
|
store %0 to %2 : $*S
|
|
inject_enum_addr %1 : $*MP, #MP.A!enumelt
|
|
br bb3
|
|
bb2:
|
|
%3 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt
|
|
store %0 to %3 : $*S
|
|
inject_enum_addr %1 : $*MP, #MP.A!enumelt
|
|
br bb3
|
|
bb3:
|
|
destroy_addr %1 : $*MP
|
|
dealloc_stack %1 : $*MP
|
|
%11 = tuple ()
|
|
return %11 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @expand_alloc_stack_of_enum_multiple_cases
|
|
// CHECK: [[A:%[0-9]+]] = alloc_stack $T
|
|
// CHECK: bb1:
|
|
// CHECK-NEXT: store %0 to [[A]]
|
|
// CHECK: apply {{%[0-9]+}}([[A]])
|
|
// CHECK: bb2:
|
|
// CHECK-NEXT: br bb3
|
|
// CHECK: bb3:
|
|
// CHECK: } // end sil function 'expand_alloc_stack_of_enum_multiple_cases'
|
|
sil @expand_alloc_stack_of_enum_multiple_cases : $@convention(method) (T) -> () {
|
|
bb0(%0 : $T):
|
|
%1 = alloc_stack $X
|
|
cond_br undef, bb1, bb2
|
|
bb1:
|
|
%2 = init_enum_data_addr %1 : $*X, #X.some!enumelt
|
|
store %0 to %2 : $*T
|
|
inject_enum_addr %1 : $*X, #X.some!enumelt
|
|
%7 = unchecked_take_enum_data_addr %1 : $*X, #X.some!enumelt
|
|
%8 = function_ref @take_t : $@convention(thin) (@in T) -> ()
|
|
%9 = apply %8(%7) : $@convention(thin) (@in T) -> ()
|
|
br bb3
|
|
bb2:
|
|
inject_enum_addr %1 : $*X, #X.none!enumelt
|
|
destroy_addr %1 : $*X
|
|
br bb3
|
|
bb3:
|
|
dealloc_stack %1 : $*X
|
|
%11 = tuple ()
|
|
return %11 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @dont_expand_alloc_stack_of_enum_multiple_cases
|
|
// CHECK: alloc_stack $MP
|
|
// CHECK: } // end sil function 'dont_expand_alloc_stack_of_enum_multiple_cases'
|
|
sil @dont_expand_alloc_stack_of_enum_multiple_cases : $@convention(method) (S) -> () {
|
|
bb0(%0 : $S):
|
|
%1 = alloc_stack $MP
|
|
cond_br undef, bb1, bb2
|
|
bb1:
|
|
%2 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt
|
|
store %0 to %2 : $*S
|
|
inject_enum_addr %1 : $*MP, #MP.A!enumelt
|
|
br bb3
|
|
bb2:
|
|
%3 = init_enum_data_addr %1 : $*MP, #MP.B!enumelt
|
|
store %0 to %3 : $*S
|
|
inject_enum_addr %1 : $*MP, #MP.B!enumelt
|
|
br bb3
|
|
bb3:
|
|
destroy_addr %1 : $*MP
|
|
dealloc_stack %1 : $*MP
|
|
%11 = tuple ()
|
|
return %11 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @dont_expand_alloc_stack_of_enum_multiple_cases2
|
|
// CHECK: alloc_stack $X
|
|
// CHECK: } // end sil function 'dont_expand_alloc_stack_of_enum_multiple_cases2'
|
|
sil @dont_expand_alloc_stack_of_enum_multiple_cases2 : $@convention(method) (T) -> () {
|
|
bb0(%0 : $T):
|
|
%1 = alloc_stack $X
|
|
cond_br undef, bb1, bb2
|
|
bb1:
|
|
%2 = init_enum_data_addr %1 : $*X, #X.some!enumelt
|
|
store %0 to %2 : $*T
|
|
inject_enum_addr %1 : $*X, #X.some!enumelt
|
|
br bb3
|
|
bb2:
|
|
inject_enum_addr %1 : $*X, #X.none!enumelt
|
|
br bb3
|
|
bb3:
|
|
destroy_addr %1 : $*X
|
|
dealloc_stack %1 : $*X
|
|
%11 = tuple ()
|
|
return %11 : $()
|
|
}
|
|
|
|
// CHECK-LABEL: sil @dont_expand_alloc_stack_of_enum_unknown_use
|
|
// CHECK: alloc_stack $MP
|
|
// CHECK: } // end sil function 'dont_expand_alloc_stack_of_enum_unknown_use'
|
|
sil @dont_expand_alloc_stack_of_enum_unknown_use : $@convention(method) (S) -> () {
|
|
bb0(%0 : $S):
|
|
%1 = alloc_stack $MP
|
|
%2 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt
|
|
store %0 to %2 : $*S
|
|
inject_enum_addr %1 : $*MP, #MP.A!enumelt
|
|
%8 = function_ref @use_mp : $@convention(thin) (@in_guaranteed MP) -> ()
|
|
%9 = apply %8(%1) : $@convention(thin) (@in_guaranteed MP) -> ()
|
|
destroy_addr %1 : $*MP
|
|
dealloc_stack %1 : $*MP
|
|
%11 = tuple ()
|
|
return %11 : $()
|
|
}
|
|
|