diff options
Diffstat (limited to '')
-rw-r--r-- | tests/mir-opt/jump_threading.rs | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/tests/mir-opt/jump_threading.rs b/tests/mir-opt/jump_threading.rs new file mode 100644 index 000000000..852dcd0db --- /dev/null +++ b/tests/mir-opt/jump_threading.rs @@ -0,0 +1,480 @@ +// unit-test: JumpThreading +// compile-flags: -Zmir-enable-passes=+Inline +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY + +#![feature(control_flow_enum)] +#![feature(try_trait_v2)] +#![feature(custom_mir, core_intrinsics, rustc_attrs)] + +use std::intrinsics::mir::*; +use std::ops::ControlFlow; + +fn too_complex(x: Result<i32, usize>) -> Option<i32> { + // CHECK-LABEL: fn too_complex( + // CHECK: bb0: { + // CHECK: switchInt(move {{_.*}}) -> [0: bb3, 1: bb1, otherwise: bb2]; + // CHECK: bb1: { + // CHECK: [[controlflow:_.*]] = ControlFlow::<usize, i32>::Break( + // CHECK: goto -> bb8; + // CHECK: bb2: { + // CHECK: unreachable; + // CHECK: bb3: { + // CHECK: [[controlflow]] = ControlFlow::<usize, i32>::Continue( + // CHECK: goto -> bb4; + // CHECK: bb4: { + // CHECK: goto -> bb6; + // CHECK: bb5: { + // CHECK: {{_.*}} = (([[controlflow]] as Break).0: usize); + // CHECK: _0 = Option::<i32>::None; + // CHECK: goto -> bb7; + // CHECK: bb6: { + // CHECK: {{_.*}} = (([[controlflow]] as Continue).0: i32); + // CHECK: _0 = Option::<i32>::Some( + // CHECK: goto -> bb7; + // CHECK: bb7: { + // CHECK: return; + // CHECK: bb8: { + // CHECK: goto -> bb5; + match { + match x { + Ok(v) => ControlFlow::Continue(v), + Err(r) => ControlFlow::Break(r), + } + } { + ControlFlow::Continue(v) => Some(v), + ControlFlow::Break(r) => None, + } +} + +fn identity(x: Result<i32, i32>) -> Result<i32, i32> { + // CHECK-LABEL: fn identity( + // CHECK: bb0: { + // CHECK: [[x:_.*]] = _1; + // CHECK: switchInt(move {{_.*}}) -> [0: bb8, 1: bb6, otherwise: bb7]; + // CHECK: bb1: { + // CHECK: goto -> bb2; + // CHECK: bb2: { + // CHECK: {{_.*}} = (([[controlflow:_.*]] as Continue).0: i32); + // CHECK: _0 = Result::<i32, i32>::Ok( + // CHECK: goto -> bb5; + // CHECK: bb3: { + // CHECK: unreachable; + // CHECK: bb4: { + // CHECK: {{_.*}} = (([[controlflow]] as Break).0: std::result::Result<std::convert::Infallible, i32>); + // CHECK: _0 = Result::<i32, i32>::Err( + // CHECK: goto -> bb5; + // CHECK: bb5: { + // CHECK: return; + // CHECK: bb6: { + // CHECK: {{_.*}} = move (([[x]] as Err).0: i32); + // CHECK: [[controlflow]] = ControlFlow::<Result<Infallible, i32>, i32>::Break( + // CHECK: goto -> bb9; + // CHECK: bb7: { + // CHECK: unreachable; + // CHECK: bb8: { + // CHECK: {{_.*}} = move (([[x]] as Ok).0: i32); + // CHECK: [[controlflow]] = ControlFlow::<Result<Infallible, i32>, i32>::Continue( + // CHECK: goto -> bb1; + // CHECK: bb9: { + // CHECK: goto -> bb4; + Ok(x?) +} + +enum DFA { + A, + B, + C, + D, +} + +/// Check that we do not thread through a loop header, +/// to avoid creating an irreducible CFG. +fn dfa() { + // CHECK-LABEL: fn dfa( + // CHECK: bb0: { + // CHECK: {{_.*}} = DFA::A; + // CHECK: goto -> bb1; + // CHECK: bb1: { + // CHECK: switchInt({{.*}}) -> [0: bb4, 1: bb5, 2: bb6, 3: bb2, otherwise: bb3]; + // CHECK: bb2: { + // CHECK: return; + // CHECK: bb3: { + // CHECK: unreachable; + // CHECK: bb4: { + // CHECK: {{_.*}} = DFA::B; + // CHECK: goto -> bb1; + // CHECK: bb5: { + // CHECK: {{_.*}} = DFA::C; + // CHECK: goto -> bb1; + // CHECK: bb6: { + // CHECK: {{_.*}} = DFA::D; + // CHECK: goto -> bb1; + let mut state = DFA::A; + loop { + match state { + DFA::A => state = DFA::B, + DFA::B => state = DFA::C, + DFA::C => state = DFA::D, + DFA::D => return, + } + } +} + +#[repr(u8)] +enum CustomDiscr { + A = 35, + B = 73, + C = 99, +} + +/// Verify that we correctly match the discriminant value, and not its index. +fn custom_discr(x: bool) -> u8 { + // CHECK-LABEL: fn custom_discr( + // CHECK: bb0: { + // CHECK: switchInt({{.*}}) -> [0: bb2, otherwise: bb1]; + // CHECK: bb1: { + // CHECK: {{_.*}} = CustomDiscr::A; + // CHECK: goto -> bb7; + // CHECK: bb2: { + // CHECK: {{_.*}} = CustomDiscr::B; + // CHECK: goto -> bb3; + // CHECK: bb3: { + // CHECK: goto -> bb4; + // CHECK: bb4: { + // CHECK: _0 = const 13_u8; + // CHECK: goto -> bb6; + // CHECK: bb5: { + // CHECK: _0 = const 5_u8; + // CHECK: goto -> bb6; + // CHECK: bb6: { + // CHECK: return; + // CHECK: bb7: { + // CHECK: goto -> bb5; + match if x { CustomDiscr::A } else { CustomDiscr::B } { + CustomDiscr::A => 5, + _ => 13, + } +} + +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +fn multiple_match(x: u8) -> u8 { + // CHECK-LABEL: fn multiple_match( + mir!( + { + // CHECK: bb0: { + // CHECK: switchInt([[x:_.*]]) -> [3: bb1, otherwise: bb2]; + match x { 3 => bb1, _ => bb2 } + } + bb1 = { + // We know `x == 3`, so we can take `bb3`. + // CHECK: bb1: { + // CHECK: {{_.*}} = [[x]]; + // CHECK: goto -> bb3; + let y = x; + match y { 3 => bb3, _ => bb4 } + } + bb2 = { + // We know `x != 3`, so we can take `bb6`. + // CHECK: bb2: { + // CHECK: [[z:_.*]] = [[x]]; + // CHECK: goto -> bb6; + let z = x; + match z { 3 => bb5, _ => bb6 } + } + bb3 = { + // CHECK: bb3: { + // CHECK: _0 = const 5_u8; + // CHECK: return; + RET = 5; + Return() + } + bb4 = { + // CHECK: bb4: { + // CHECK: _0 = const 7_u8; + // CHECK: return; + RET = 7; + Return() + } + bb5 = { + // CHECK: bb5: { + // CHECK: _0 = const 9_u8; + // CHECK: return; + RET = 9; + Return() + } + bb6 = { + // We know `z != 3`, so we CANNOT take `bb7`. + // CHECK: bb6: { + // CHECK: switchInt([[z]]) -> [1: bb7, otherwise: bb8]; + match z { 1 => bb7, _ => bb8 } + } + bb7 = { + // CHECK: bb7: { + // CHECK: _0 = const 9_u8; + // CHECK: return; + RET = 9; + Return() + } + bb8 = { + // CHECK: bb8: { + // CHECK: _0 = const 11_u8; + // CHECK: return; + RET = 11; + Return() + } + ) +} + +/// Both 1-3-4 and 2-3-4 are threadable. As 1 and 2 are the only predecessors of 3, +/// verify that we only thread the 3-4 part. +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +fn duplicate_chain(x: bool) -> u8 { + // CHECK-LABEL: fn duplicate_chain( + mir!( + let a: u8; + { + // CHECK: bb0: { + // CHECK: switchInt({{.*}}) -> [1: bb1, otherwise: bb2]; + match x { true => bb1, _ => bb2 } + } + bb1 = { + // CHECK: bb1: { + // CHECK: [[a:_.*]] = const 5_u8; + // CHECK: goto -> bb3; + a = 5; + Goto(bb3) + } + bb2 = { + // CHECK: bb2: { + // CHECK: [[a]] = const 5_u8; + // CHECK: goto -> bb3; + a = 5; + Goto(bb3) + } + bb3 = { + // CHECK: bb3: { + // CHECK: {{_.*}} = const 13_i32; + // CHECK: goto -> bb4; + let b = 13; + Goto(bb4) + } + bb4 = { + // CHECK: bb4: { + // CHECK: {{_.*}} = const 15_i32; + // CHECK-NOT: switchInt( + // CHECK: goto -> bb5; + let c = 15; + match a { 5 => bb5, _ => bb6 } + } + bb5 = { + // CHECK: bb5: { + // CHECK: _0 = const 7_u8; + // CHECK: return; + RET = 7; + Return() + } + bb6 = { + // CHECK: bb6: { + // CHECK: _0 = const 9_u8; + // CHECK: return; + RET = 9; + Return() + } + ) +} + +#[rustc_layout_scalar_valid_range_start(1)] +#[rustc_nonnull_optimization_guaranteed] +struct NonZeroUsize(usize); + +/// Verify that we correctly discard threads that may mutate a discriminant by aliasing. +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +fn mutate_discriminant() -> u8 { + // CHECK-LABEL: fn mutate_discriminant( + // CHECK-NOT: goto -> {{bb.*}}; + // CHECK: switchInt( + // CHECK-NOT: goto -> {{bb.*}}; + mir!( + let x: Option<NonZeroUsize>; + { + SetDiscriminant(x, 1); + // This assignment overwrites the niche in which the discriminant is stored. + place!(Field(Field(Variant(x, 1), 0), 0)) = 0_usize; + // So we cannot know the value of this discriminant. + let a = Discriminant(x); + match a { + 0 => bb1, + _ => bad, + } + } + bb1 = { + RET = 1; + Return() + } + bad = { + RET = 2; + Unreachable() + } + ) +} + +/// Verify that we do not try to reason when there are mutable pointers involved. +fn mutable_ref() -> bool { + // CHECK-LABEL: fn mutable_ref( + // CHECK-NOT: goto -> {{bb.*}}; + // CHECK: switchInt( + // CHECK: goto -> [[bbret:bb.*]]; + // CHECK: goto -> [[bbret]]; + // CHECK: [[bbret]]: { + // CHECK-NOT: {{bb.*}}: { + // CHECK: return; + let mut x = 5; + let a = std::ptr::addr_of_mut!(x); + x = 7; + unsafe { *a = 8 }; + if x == 7 { + true + } else { + false + } +} + +/// This function has 2 TOs: 1-3-4 and 0-1-3-4-6. +/// We verify that the second TO does not modify 3 once the first has been applied. +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +fn renumbered_bb(x: bool) -> u8 { + // CHECK-LABEL: fn renumbered_bb( + mir!( + let a: bool; + let b: bool; + { + // CHECK: bb0: { + // CHECK: switchInt({{.*}}) -> [1: bb1, otherwise: bb2]; + b = false; + match x { true => bb1, _ => bb2 } + } + bb1 = { + // CHECK: bb1: { + // CHECK: goto -> bb8; + a = false; + Goto(bb3) + } + bb2 = { + // CHECK: bb2: { + // CHECK: goto -> bb3; + a = x; + b = x; + Goto(bb3) + } + bb3 = { + // CHECK: bb3: { + // CHECK: switchInt({{.*}}) -> [0: bb4, otherwise: bb5]; + match a { false => bb4, _ => bb5 } + } + bb4 = { + // CHECK: bb4: { + // CHECK: switchInt({{.*}}) -> [0: bb6, otherwise: bb7]; + match b { false => bb6, _ => bb7 } + } + bb5 = { + // CHECK: bb5: { + // CHECK: _0 = const 7_u8; + RET = 7; + Return() + } + bb6 = { + // CHECK: bb6: { + // CHECK: _0 = const 9_u8; + RET = 9; + Return() + } + bb7 = { + // CHECK: bb7: { + // CHECK: _0 = const 11_u8; + RET = 11; + Return() + } + // Duplicate of bb3. + // CHECK: bb8: { + // CHECK-NEXT: goto -> bb9; + // Duplicate of bb4. + // CHECK: bb9: { + // CHECK-NEXT: goto -> bb6; + ) +} + +/// This function has 3 TOs: 1-4-5, 0-1-4-7-5-8 and 3-4-7-5-6 +/// After applying the first TO, we create bb9 to replace 4, and rename 1-4 edge by 1-9. The +/// second TO may try to thread non-existing edge 9-4. +/// This test verifies that we preserve semantics by bailing out of this second TO. +#[custom_mir(dialect = "runtime", phase = "post-cleanup")] +fn disappearing_bb(x: u8) -> u8 { + // CHECK-LABEL: fn disappearing_bb( + mir!( + let a: bool; + let b: bool; + { + a = true; + b = true; + match x { 0 => bb3, 1 => bb3, 2 => bb1, _ => bb2 } + } + bb1 = { + // CHECK: bb1: { + // CHECK: goto -> bb9; + b = false; + Goto(bb4) + } + bb2 = { + Unreachable() + } + bb3 = { + // CHECK: bb3: { + // CHECK: goto -> bb10; + a = false; + Goto(bb4) + } + bb4 = { + match b { false => bb5, _ => bb7 } + } + bb5 = { + match a { false => bb6, _ => bb8 } + } + bb6 = { + Return() + } + bb7 = { + Goto(bb5) + } + bb8 = { + Goto(bb6) + } + // CHECK: bb9: { + // CHECK: goto -> bb5; + // CHECK: bb10: { + // CHECK: goto -> bb6; + ) +} + +fn main() { + too_complex(Ok(0)); + identity(Ok(0)); + custom_discr(false); + dfa(); + multiple_match(5); + duplicate_chain(false); + mutate_discriminant(); + mutable_ref(); + renumbered_bb(true); + disappearing_bb(7); +} + +// EMIT_MIR jump_threading.too_complex.JumpThreading.diff +// EMIT_MIR jump_threading.identity.JumpThreading.diff +// EMIT_MIR jump_threading.custom_discr.JumpThreading.diff +// EMIT_MIR jump_threading.dfa.JumpThreading.diff +// EMIT_MIR jump_threading.multiple_match.JumpThreading.diff +// EMIT_MIR jump_threading.duplicate_chain.JumpThreading.diff +// EMIT_MIR jump_threading.mutate_discriminant.JumpThreading.diff +// EMIT_MIR jump_threading.mutable_ref.JumpThreading.diff +// EMIT_MIR jump_threading.renumbered_bb.JumpThreading.diff +// EMIT_MIR jump_threading.disappearing_bb.JumpThreading.diff |