mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Avoid introducing new critical edges. Passes will end up resplitting them, forcing SimplifyCFG to continually rerun. Also, we want to allow SIL utilities to assume no critical edges, and avoid the need for several passes to internally split edges and modify the CFG for no reason. Also, handle arbitrary block arguments which may be trivial and unrelated to the real optimizations that trampoline removal exposes, such as "unwrapping" enumeration-type arguments. The previous code was an example of how to write an unstable optimization. It could be defeated by other code in the function that isn't directly related to the SSA graph being optimized. In general, when an optimization can be defeated by unrelated code in the function, that leads to instability which can be very hard to track down (I spent multiple full days on this one). In this case, we have enum-type branch args which need to be simplified by unwrapping them. But, the existence of a trivial and entirely unrelated block argument would defeat the optimization.
233 lines
7.2 KiB
Plaintext
233 lines
7.2 KiB
Plaintext
// RUN: %target-sil-opt -enable-sil-verify-all %s -jumpthread-simplify-cfg | %FileCheck %s
|
|
|
|
sil_stage canonical
|
|
|
|
import Builtin
|
|
import Swift
|
|
import SwiftShims
|
|
|
|
// CHECK-LABEL: sil @test_jump_threading
|
|
// CHECK: bb5(%{{[0-9]+}} : $Builtin.Int64):
|
|
// CHECK-NEXT: br bb1
|
|
sil @test_jump_threading : $@convention(thin) (Builtin.Int1) -> () {
|
|
bb0(%0 : $Builtin.Int1):
|
|
cond_br %0, bb2, bb3
|
|
|
|
// Blocks are handled from last to first. Block bb1 is placed here so that its argument
|
|
// is not optimized before jump threading is done in bb2 and bb3.
|
|
bb1(%i4 : $Builtin.Int64):
|
|
%f3 = function_ref @get_condition : $@convention(thin) (Builtin.Int64) -> Builtin.Int1
|
|
%c1 = apply %f3(%i3) : $@convention(thin) (Builtin.Int64) -> Builtin.Int1
|
|
%i5 = integer_literal $Builtin.Int64, 27
|
|
cond_br %c1, bb1(%i5 : $Builtin.Int64), bb5
|
|
|
|
bb2:
|
|
%f1 = function_ref @get_int1 : $@convention(thin) () -> Builtin.Int64
|
|
%i1 = apply %f1() : $@convention(thin) () -> Builtin.Int64
|
|
br bb4(%i1 : $Builtin.Int64)
|
|
|
|
bb3:
|
|
%f2 = function_ref @get_int1 : $@convention(thin) () -> Builtin.Int64
|
|
%i2 = apply %f2() : $@convention(thin) () -> Builtin.Int64
|
|
br bb4(%i2 : $Builtin.Int64)
|
|
|
|
// Jump threading must not be done for this block because the argument %i3 is also
|
|
// used in bb1.
|
|
bb4(%i3 : $Builtin.Int64):
|
|
br bb1(%i3 : $Builtin.Int64)
|
|
|
|
bb5:
|
|
%r1 = tuple ()
|
|
return %r1 : $()
|
|
|
|
}
|
|
|
|
sil @get_int1 : $@convention(thin) () -> Builtin.Int64
|
|
sil @get_int2 : $@convention(thin) () -> Builtin.Int64
|
|
sil @get_condition : $@convention(thin) (Builtin.Int64) -> Builtin.Int1
|
|
|
|
|
|
public final class AA {
|
|
}
|
|
public final class BB {
|
|
@_hasStorage internal weak final var n: BB!
|
|
@_hasStorage internal final var o: AA!
|
|
}
|
|
|
|
// Test that SimplifyCFG does not hang when compiling an infinite loop with switch_enum.
|
|
// CHECK-LABEL: test_inifite_loop
|
|
sil hidden @test_inifite_loop : $@convention(method) (@owned BB) -> () {
|
|
bb0(%0 : $BB):
|
|
%31 = enum $Optional<BB>, #Optional.some!enumelt, %0 : $BB
|
|
br bb4(%31 : $Optional<BB>)
|
|
|
|
bb4(%36 : $Optional<BB>):
|
|
switch_enum %36 : $Optional<BB>, case #Optional.some!enumelt: bb6, default bb5
|
|
|
|
bb5:
|
|
br bb7
|
|
|
|
bb6:
|
|
%39 = unchecked_enum_data %36 : $Optional<BB>, #Optional.some!enumelt
|
|
%40 = ref_element_addr %39 : $BB, #BB.o
|
|
%41 = load %40 : $*Optional<AA>
|
|
release_value %41 : $Optional<AA>
|
|
br bb7
|
|
|
|
bb7:
|
|
switch_enum %36 : $Optional<BB>, case #Optional.none!enumelt: bb8, case #Optional.some!enumelt: bb9
|
|
|
|
bb8:
|
|
br bb4(%36 : $Optional<BB>)
|
|
|
|
bb9:
|
|
%48 = unchecked_enum_data %36 : $Optional<BB>, #Optional.some!enumelt
|
|
%49 = ref_element_addr %48 : $BB, #BB.n
|
|
%50 = load_weak %49 : $*@sil_weak Optional<BB>
|
|
release_value %36 : $Optional<BB>
|
|
switch_enum %50 : $Optional<BB>, case #Optional.some!enumelt: bb11, case #Optional.none!enumelt: bb10
|
|
|
|
bb10:
|
|
br bb4(%50 : $Optional<BB>)
|
|
|
|
bb11:
|
|
%54 = unchecked_enum_data %50 : $Optional<BB>, #Optional.some!enumelt
|
|
%55 = ref_to_raw_pointer %54 : $BB to $Builtin.RawPointer
|
|
%56 = ref_to_raw_pointer %0 : $BB to $Builtin.RawPointer
|
|
%57 = builtin "cmp_eq_RawPointer"(%55 : $Builtin.RawPointer, %56 : $Builtin.RawPointer) : $Builtin.Int1
|
|
cond_br %57, bb13, bb12
|
|
|
|
bb12:
|
|
br bb4(%50 : $Optional<BB>)
|
|
|
|
bb13:
|
|
release_value %50 : $Optional<BB>
|
|
strong_release %0 : $BB
|
|
%65 = tuple ()
|
|
return %65 : $()
|
|
}
|
|
|
|
sil @some_function : $@convention(thin) (AA) -> Optional<AA>
|
|
|
|
// Another test for checking that SimplifyCFG does not hang.
|
|
// CHECK-LABEL: test_other_infinite_loop
|
|
sil hidden @test_other_infinite_loop : $@convention(method) (@owned AA) -> () {
|
|
bb0(%5 : $AA):
|
|
strong_retain %5 : $AA
|
|
%6 = enum $Optional<AA>, #Optional.some!enumelt, %5 : $AA
|
|
br bb1(%6 : $Optional<AA>)
|
|
|
|
bb1(%8 : $Optional<AA>):
|
|
retain_value %8 : $Optional<AA>
|
|
switch_enum %8 : $Optional<AA>, case #Optional.some!enumelt: bb3, default bb2
|
|
|
|
bb2:
|
|
release_value %8 : $Optional<AA>
|
|
br bb6
|
|
|
|
bb3:
|
|
cond_br undef, bb4, bb5
|
|
|
|
bb4:
|
|
%85 = tuple ()
|
|
return %85 : $()
|
|
|
|
bb5:
|
|
br bb6
|
|
|
|
bb6:
|
|
switch_enum %8 : $Optional<AA>, case #Optional.none!enumelt: bb7, default bb8
|
|
|
|
bb7:
|
|
br bb9(%8 : $Optional<AA>)
|
|
|
|
bb8:
|
|
%23 = unchecked_enum_data %8 : $Optional<AA>, #Optional.some!enumelt
|
|
strong_retain %23 : $AA
|
|
%25 = function_ref @some_function : $@convention(thin) (AA) -> Optional<AA>
|
|
%26 = apply %25(%23) : $@convention(thin) (AA) -> Optional<AA>
|
|
strong_release %23 : $AA
|
|
br bb9(%26 : $Optional<AA>)
|
|
|
|
bb9(%29 : $Optional<AA>):
|
|
release_value %8 : $Optional<AA>
|
|
br bb1(%29 : $Optional<AA>)
|
|
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Test jump-threading through a non-pure address producer.
|
|
//
|
|
// BB3 cannot (currently) be cloned because the block cloner does not
|
|
// know how to sink address producers unless they are pure address
|
|
// projections. init_existential_addr is not a pure projection. It's
|
|
// address is transitively used outside bb3 via %17 =
|
|
// tuple_element_addr. Test that cloning is inhibited. If cloning did
|
|
// happen, then it would either need to sink init_existential_addr, or
|
|
// SSA would be incorrectly updated.
|
|
|
|
enum FakeOptional<T> {
|
|
case some(T)
|
|
case none
|
|
}
|
|
|
|
struct S {
|
|
}
|
|
|
|
struct T {
|
|
let s: S
|
|
}
|
|
|
|
class C {
|
|
func method() -> Any
|
|
func f() -> FakeOptional<T>
|
|
}
|
|
|
|
// Make BB3 is not jump-threaded. And init_existential_addr is not cloned
|
|
//
|
|
// CHECK-LABEL: sil hidden @nonPureAddressProducer : $@convention(method) (@guaranteed C) -> @out Any {
|
|
// CHECK: bb0(%0 : $*Any, %1 : $C):
|
|
// CHECK: switch_enum %{{.*}} : $FakeOptional<T>, case #FakeOptional.some!enumelt: bb1, case #FakeOptional.none!enumelt: bb2
|
|
// CHECK: bb3(%{{.*}} : $FakeOptional<Int64>):
|
|
// CHECK: init_existential_addr %0 : $*Any, $(FakeOptional<Int64>, FakeOptional<S>)
|
|
// CHECK: switch_enum %{{.*}} : $FakeOptional<T>, case #FakeOptional.some!enumelt: bb4, case #FakeOptional.none!enumelt: bb6
|
|
// CHECK-LABEL: } // end sil function 'nonPureAddressProducer'
|
|
sil hidden @nonPureAddressProducer : $@convention(method) (@guaranteed C) -> @out Any {
|
|
bb0(%0 : $*Any, %1 : $C):
|
|
%3 = class_method %1 : $C, #C.f : (C) -> () -> FakeOptional<T>, $@convention(method) (@guaranteed C) -> FakeOptional<T>
|
|
%4 = apply %3(%1) : $@convention(method) (@guaranteed C) -> FakeOptional<T>
|
|
switch_enum %4 : $FakeOptional<T>, case #FakeOptional.some!enumelt: bb1, case #FakeOptional.none!enumelt: bb2
|
|
|
|
bb1:
|
|
%7 = integer_literal $Builtin.Int64, 1
|
|
%8 = struct $Int64 (%7 : $Builtin.Int64)
|
|
%9 = enum $FakeOptional<Int64>, #FakeOptional.some!enumelt, %8 : $Int64
|
|
br bb3(%9 : $FakeOptional<Int64>)
|
|
|
|
bb2:
|
|
%11 = enum $FakeOptional<Int64>, #FakeOptional.none!enumelt
|
|
br bb3(%11 : $FakeOptional<Int64>)
|
|
|
|
bb3(%13 : $FakeOptional<Int64>):
|
|
%15 = init_existential_addr %0 : $*Any, $(FakeOptional<Int64>, FakeOptional<S>)
|
|
%16 = tuple_element_addr %15 : $*(FakeOptional<Int64>, FakeOptional<S>), 0
|
|
%17 = tuple_element_addr %15 : $*(FakeOptional<Int64>, FakeOptional<S>), 1
|
|
store %13 to %16 : $*FakeOptional<Int64>
|
|
switch_enum %4 : $FakeOptional<T>, case #FakeOptional.some!enumelt: bb4, case #FakeOptional.none!enumelt: bb6
|
|
|
|
bb4(%20 : $T):
|
|
%21 = struct_extract %20 : $T, #T.s
|
|
%22 = enum $FakeOptional<S>, #FakeOptional.some!enumelt, %21 : $S
|
|
store %22 to %17 : $*FakeOptional<S>
|
|
br bb5
|
|
|
|
bb5:
|
|
%25 = tuple ()
|
|
return %25 : $()
|
|
|
|
bb6:
|
|
%27 = enum $FakeOptional<S>, #FakeOptional.none!enumelt
|
|
store %27 to %17 : $*FakeOptional<S>
|
|
br bb5
|
|
}
|