1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
// RUN: %target-sil-opt -test-runner %s -o /dev/null 2>&1 | %FileCheck %s
//
// These tests rely on "incomplete" OSSA lifetimes. Incomplete OSSA
// lifetimes are invalid SIL, but the OSSA liveness utilities still
// need to handle them! SILGen and textual SIL is allowed to produce
// incomplete lifetimes. The OSSA liveness utilities need to be able
// to fixup those incomplete lifetimes. So the utilities need to
// handle invalid SIL in order to produce valid SIL.
sil_stage canonical
import Builtin
enum FakeOptional<T> {
case none
case some(T)
}
enum Never {}
class C {}
// CHECK-LABEL: testDeadTerminatorResult: ssa_liveness
// CHECK-LABEL: SSA lifetime analysis: %{{.*}} = argument of bb1 : $C
// CHECK: bb1: LiveWithin
// CHECK-NOT: "last user"
// CHECK-NOT: "boundary edge"
// CHECK: dead def: %{{.*}} = argument of bb1 : $C
sil [ossa] @testDeadTerminatorResult : $@convention(thin) (@owned FakeOptional<C>) -> () {
bb0(%0 : @owned $FakeOptional<C>):
switch_enum %0 : $FakeOptional<C>, case #FakeOptional.some!enumelt: bb1, case #FakeOptional.none!enumelt: bb2
bb1(%payload : @owned $C):
specify_test "ssa_liveness @trace[0]"
debug_value [trace] %payload : $C
unreachable
bb2:
%99 = tuple()
return %99 : $()
}
// CHECK-LABEL: testDeadPhi: ssa_liveness
// CHECK: SSA lifetime analysis: %{{.*}} = argument of bb3 : $C
// CHECK: bb3: LiveWithin
// CHECK: dead def: %{{.*}} = argument of bb3 : $C
sil [ossa] @testDeadPhi : $@convention(thin) (@owned C, @owned C) -> () {
bb0(%0 : @owned $C, %1 : @owned $C):
cond_br undef, bb1, bb2
bb1:
br bb3(%0 : $C)
bb2:
br bb3(%1 : $C)
bb3(%2 : @owned $C):
specify_test "ssa_liveness @trace[0]"
debug_value [trace] %2 : $C
unreachable
}
// CHECK-LABEL: testDeadInstruction: ssa_liveness
// CHECK: SSA lifetime analysis: %{{.*}} = copy_value %0 : $C
// CHECK: bb0: LiveWithin
// CHECK: dead def: %{{.*}} = copy_value %0 : $C
sil [ossa] @testDeadInstruction : $@convention(thin) (@guaranteed C) -> () {
bb0(%0 : @guaranteed $C):
%1 = copy_value %0 : $C
specify_test "ssa_liveness @trace[0]"
debug_value [trace] %1 : $C
unreachable
}
// A single instruction occurs twice on the same liveness
// boundary. Once as a last use, and once as a dead def.
// This is a particularly problematic corner case.
//
// CHECK-LABEL: testDeadSelfKill: multidef_liveness
// CHECK: MultiDef lifetime analysis:
// CHECK: def value: %1 = argument of bb1 : $C
// CHECK: def value: [[V:%.*]] = move_value %1 : $C
// CHECK: bb1: LiveWithin
// CHECK: lifetime-ending user: [[V]] = move_value %1 : $C
// CHECK: last user: [[V]] = move_value %1 : $C
// CHECK: dead def: [[V]] = move_value %1 : $C
sil [ossa] @testDeadSelfKill : $@convention(thin) () -> () {
bb0:
br bb3
bb1(%1 : @owned $C):
specify_test "multidef_liveness @trace[0] @trace[1]"
debug_value [trace] %1 : $C
%2 = move_value %1 : $C
debug_value [trace] %2 : $C
unreachable
bb3:
%99 = tuple()
return %99 : $()
}
sil @genericReturn : $@convention(thin) <τ_0_0> (@guaranteed C) -> @out τ_0_0
// The store_borrow scope has an inner load_borrow scope, which is
// incomplete. Since the store_borrow produces an address, the load
// borrow is considered a ScopedAddressUse, and all of its uses,
// including the Apply will contribute to the store_borrow liveness.
// CHECK-LABEL: testInnerUnreachable: scoped-address-liveness
// CHECK: Scoped address analysis: [[SB:%.*]] = store_borrow %0
// CHECK: bb0: LiveWithin
// CHECK-NEXT: regular user: %{{.*}} = apply
// CHECK-NEXT: last user: %{{.*}} = apply
sil shared [ossa] @testInnerUnreachable : $@convention(thin) (@guaranteed C, @thick Never.Type) -> () {
bb0(%0 : @guaranteed $C, %1 : $@thick Never.Type):
%2 = alloc_stack $C
%3 = store_borrow %0 to %2 : $*C
specify_test "scoped-address-liveness @trace[0]"
debug_value [trace] %3 : $*C
%5 = load_borrow %3 : $*C
%6 = function_ref @genericReturn : $@convention(thin) <τ_0_0> (@guaranteed C) -> @out τ_0_0
%7 = alloc_stack $Never
%8 = apply %6<Never>(%7, %5) : $@convention(thin) <τ_0_0> (@guaranteed C) -> @out τ_0_0
unreachable
}
// A dead-end block with a def can still be a boundary edge. This can
// only happen in OSSA with incomplete lifetimes.
//
// CHECK-LABEL: testMultiDefDeadDefBoundaryEdge: multidef_liveness
// CHECK: MultiDef lifetime analysis:
// CHECK: def value: [[CP0:%.*]] = copy_value %0 : $C
// CHECK: def value: [[CP3:%.*]] = copy_value %0 : $C
// CHECK: bb0: LiveOut
// CHECK: bb1: LiveWithin
// CHECK: bb2: LiveWithin
// CHECK: last user: destroy_value [[CP0]] : $C
// CHECK-NEXT: boundary edge: bb1
// CHECK-NEXT: dead def: [[CP3]] = copy_value %0 : $C
sil [ossa] @testMultiDefDeadDefBoundaryEdge : $@convention(thin) (@guaranteed C) -> () {
bb0(%0 : @guaranteed $C):
%copy0 = copy_value %0 : $C
specify_test "multidef_liveness @trace[0] @trace[1]"
debug_value [trace] %copy0 : $C
cond_br undef, bb1, bb3
bb1:
%dead = copy_value %0 : $C
debug_value [trace] %dead : $C
unreachable
bb3:
destroy_value %copy0 : $C
%99 = tuple()
return %99 : $()
}
|