From 0ccf8ba11507bd72d801eb1632e34021934dcf4f Mon Sep 17 00:00:00 2001 From: DianQK Date: Sun, 22 Sep 2024 21:49:39 +0800 Subject: [PATCH 1/4] Revert "tests: allow trunc/select instructions to be missing" This reverts commit 9692513b7e71ea84d00adc3e0174ea865138aac6. --- tests/codegen/try_question_mark_nop.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/codegen/try_question_mark_nop.rs b/tests/codegen/try_question_mark_nop.rs index 65167f5c5af97..321067d1b904c 100644 --- a/tests/codegen/try_question_mark_nop.rs +++ b/tests/codegen/try_question_mark_nop.rs @@ -1,10 +1,7 @@ //@ compile-flags: -O -Z merge-functions=disabled --edition=2021 //@ only-x86_64 // FIXME: Remove the `min-llvm-version`. -//@ revisions: NINETEEN TWENTY -//@[NINETEEN] min-llvm-version: 19 -//@[NINETEEN] ignore-llvm-version: 20-99 -//@[TWENTY] min-llvm-version: 20 +//@ min-llvm-version: 19 #![crate_type = "lib"] #![feature(try_blocks)] @@ -12,14 +9,14 @@ use std::ops::ControlFlow::{self, Break, Continue}; use std::ptr::NonNull; +// FIXME: The `trunc` and `select` instructions can be eliminated. // CHECK-LABEL: @option_nop_match_32 #[no_mangle] pub fn option_nop_match_32(x: Option) -> Option { // CHECK: start: - // NINETEEN-NEXT: [[TRUNC:%.*]] = trunc nuw i32 %0 to i1 - // NINETEEN-NEXT: [[FIRST:%.*]] = select i1 [[TRUNC]], i32 %0 - // NINETEEN-NEXT: insertvalue { i32, i32 } poison, i32 [[FIRST]], 0 - // TWENTY-NEXT: insertvalue { i32, i32 } poison, i32 %0, 0 + // CHECK-NEXT: [[TRUNC:%.*]] = trunc nuw i32 %0 to i1 + // CHECK-NEXT: [[FIRST:%.*]] = select i1 [[TRUNC]], i32 %0 + // CHECK-NEXT: insertvalue { i32, i32 } poison, i32 [[FIRST]] // CHECK-NEXT: insertvalue { i32, i32 } // CHECK-NEXT: ret { i32, i32 } match x { From 9bb8cb847fa9257b0f17fd7d2ed860374224fb07 Mon Sep 17 00:00:00 2001 From: DianQK Date: Sun, 22 Sep 2024 21:49:46 +0800 Subject: [PATCH 2/4] Revert "Update try_question_mark_nop.rs test" This reverts commit 25d434b254bf45347210095600c0a47f65bcaa54. --- tests/codegen/try_question_mark_nop.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/codegen/try_question_mark_nop.rs b/tests/codegen/try_question_mark_nop.rs index 321067d1b904c..c23f41f546716 100644 --- a/tests/codegen/try_question_mark_nop.rs +++ b/tests/codegen/try_question_mark_nop.rs @@ -1,7 +1,5 @@ //@ compile-flags: -O -Z merge-functions=disabled --edition=2021 //@ only-x86_64 -// FIXME: Remove the `min-llvm-version`. -//@ min-llvm-version: 19 #![crate_type = "lib"] #![feature(try_blocks)] @@ -9,14 +7,11 @@ use std::ops::ControlFlow::{self, Break, Continue}; use std::ptr::NonNull; -// FIXME: The `trunc` and `select` instructions can be eliminated. // CHECK-LABEL: @option_nop_match_32 #[no_mangle] pub fn option_nop_match_32(x: Option) -> Option { // CHECK: start: - // CHECK-NEXT: [[TRUNC:%.*]] = trunc nuw i32 %0 to i1 - // CHECK-NEXT: [[FIRST:%.*]] = select i1 [[TRUNC]], i32 %0 - // CHECK-NEXT: insertvalue { i32, i32 } poison, i32 [[FIRST]] + // CHECK-NEXT: insertvalue { i32, i32 } // CHECK-NEXT: insertvalue { i32, i32 } // CHECK-NEXT: ret { i32, i32 } match x { From 70392ec82d7d7705e7ce24a448328d8ebe467386 Mon Sep 17 00:00:00 2001 From: DianQK Date: Sun, 20 Oct 2024 16:54:45 +0800 Subject: [PATCH 3/4] mir-opt: Allow other passes to use DSE --- .../src/dead_store_elimination.rs | 95 ++++++++++++------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_mir_transform/src/dead_store_elimination.rs b/compiler/rustc_mir_transform/src/dead_store_elimination.rs index edffe6ce78f67..46fc98bdab71e 100644 --- a/compiler/rustc_mir_transform/src/dead_store_elimination.rs +++ b/compiler/rustc_mir_transform/src/dead_store_elimination.rs @@ -12,18 +12,71 @@ //! will still not cause any further changes. //! +use rustc_index::bit_set::BitSet; use rustc_middle::bug; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; -use rustc_mir_dataflow::Analysis; use rustc_mir_dataflow::debuginfo::debuginfo_locals; use rustc_mir_dataflow::impls::{ LivenessTransferFunction, MaybeTransitiveLiveLocals, borrowed_locals, }; +use rustc_mir_dataflow::{Analysis, ResultsCursor}; use crate::util::is_within_packed; +pub(crate) struct DeadStoreAnalysis<'tcx, 'mir, 'a> { + live: ResultsCursor<'mir, 'tcx, MaybeTransitiveLiveLocals<'a>>, + always_live: &'a BitSet, +} + +impl<'tcx, 'mir, 'a> DeadStoreAnalysis<'tcx, 'mir, 'a> { + pub(crate) fn new( + tcx: TyCtxt<'tcx>, + body: &'mir Body<'tcx>, + always_live: &'a BitSet, + ) -> Self { + let live = MaybeTransitiveLiveLocals::new(&always_live) + .into_engine(tcx, body) + .iterate_to_fixpoint() + .into_results_cursor(body); + Self { live, always_live } + } + + pub(crate) fn is_dead_store(&mut self, loc: Location, stmt_kind: &StatementKind<'tcx>) -> bool { + if let StatementKind::Assign(assign) = stmt_kind { + if !assign.1.is_safe_to_remove() { + return false; + } + } + match stmt_kind { + StatementKind::Assign(box (place, _)) + | StatementKind::SetDiscriminant { place: box place, .. } + | StatementKind::Deinit(box place) => { + if !place.is_indirect() && !self.always_live.contains(place.local) { + self.live.seek_before_primary_effect(loc); + !self.live.get().contains(place.local) + } else { + false + } + } + + StatementKind::Retag(_, _) + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Coverage(_) + | StatementKind::Intrinsic(_) + | StatementKind::ConstEvalCounter + | StatementKind::PlaceMention(_) + | StatementKind::Nop => false, + + StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => { + bug!("{:?} not found in this MIR phase!", stmt_kind) + } + } + } +} + /// Performs the optimization on the body /// /// The `borrowed` set must be a `BitSet` of all the locals that are ever borrowed in this body. It @@ -36,10 +89,7 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let mut always_live = debuginfo_locals(body); always_live.union(&borrowed_locals); - let mut live = MaybeTransitiveLiveLocals::new(&always_live) - .into_engine(tcx, body) - .iterate_to_fixpoint() - .into_results_cursor(body); + let mut analysis = DeadStoreAnalysis::new(tcx, body, &always_live); // For blocks with a call terminator, if an argument copy can be turned into a move, // record it as (block, argument index). @@ -51,8 +101,8 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let loc = Location { block: bb, statement_index: bb_data.statements.len() }; // Position ourselves between the evaluation of `args` and the write to `destination`. - live.seek_to_block_end(bb); - let mut state = live.get().clone(); + analysis.live.seek_to_block_end(bb); + let mut state = analysis.live.get().clone(); for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() { if let Operand::Copy(place) = *arg @@ -74,37 +124,10 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { LivenessTransferFunction(&mut state).visit_operand(arg, loc); } } - for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() { let loc = Location { block: bb, statement_index }; - if let StatementKind::Assign(assign) = &statement.kind { - if !assign.1.is_safe_to_remove() { - continue; - } - } - match &statement.kind { - StatementKind::Assign(box (place, _)) - | StatementKind::SetDiscriminant { place: box place, .. } - | StatementKind::Deinit(box place) => { - if !place.is_indirect() && !always_live.contains(place.local) { - live.seek_before_primary_effect(loc); - if !live.get().contains(place.local) { - patch.push(loc); - } - } - } - StatementKind::Retag(_, _) - | StatementKind::StorageLive(_) - | StatementKind::StorageDead(_) - | StatementKind::Coverage(_) - | StatementKind::Intrinsic(_) - | StatementKind::ConstEvalCounter - | StatementKind::PlaceMention(_) - | StatementKind::Nop => (), - - StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => { - bug!("{:?} not found in this MIR phase!", statement.kind) - } + if analysis.is_dead_store(loc, &statement.kind) { + patch.push(loc); } } } From 99eed4bbf1619c9cb62e14baea4833a9540c8dbc Mon Sep 17 00:00:00 2001 From: DianQK Date: Sat, 5 Oct 2024 09:33:21 +0800 Subject: [PATCH 4/4] mir-opt: Merge all branch BBs into a single copy statement for enum --- compiler/rustc_mir_transform/src/lib.rs | 2 + .../rustc_mir_transform/src/merge_branches.rs | 240 ++++++++++++++++++ tests/codegen/match-optimizes-away.rs | 14 +- ...r.no_fields.MergeBranchSimplification.diff | 34 +++ ...elds_failed.MergeBranchSimplification.diff | 32 +++ ...type_failed.MergeBranchSimplification.diff | 32 +++ ..._fields_ref.MergeBranchSimplification.diff | 34 +++ ...e_br.option.MergeBranchSimplification.diff | 41 +++ ..._dse_failed.MergeBranchSimplification.diff | 34 +++ tests/mir-opt/merge_br/merge_br.rs | 87 +++++++ ...py.enum_clone_as_copy.PreCodegen.after.mir | 38 +-- tests/mir-opt/pre-codegen/clone_as_copy.rs | 4 +- .../try_identity.old.PreCodegen.after.mir | 29 +-- 13 files changed, 560 insertions(+), 61 deletions(-) create mode 100644 compiler/rustc_mir_transform/src/merge_branches.rs create mode 100644 tests/mir-opt/merge_br/merge_br.no_fields.MergeBranchSimplification.diff create mode 100644 tests/mir-opt/merge_br/merge_br.no_fields_failed.MergeBranchSimplification.diff create mode 100644 tests/mir-opt/merge_br/merge_br.no_fields_mismatch_type_failed.MergeBranchSimplification.diff create mode 100644 tests/mir-opt/merge_br/merge_br.no_fields_ref.MergeBranchSimplification.diff create mode 100644 tests/mir-opt/merge_br/merge_br.option.MergeBranchSimplification.diff create mode 100644 tests/mir-opt/merge_br/merge_br.option_dse_failed.MergeBranchSimplification.diff create mode 100644 tests/mir-opt/merge_br/merge_br.rs diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index d184328748f84..57513857695e5 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -86,6 +86,7 @@ mod lower_intrinsics; mod lower_slice_len; mod match_branches; mod mentioned_items; +mod merge_branches; mod multiple_return_terminators; mod nrvo; mod post_drop_elaboration; @@ -611,6 +612,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { &dead_store_elimination::DeadStoreElimination::Initial, &gvn::GVN, &simplify::SimplifyLocals::AfterGVN, + &merge_branches::MergeBranchSimplification, &dataflow_const_prop::DataflowConstProp, &single_use_consts::SingleUseConsts, &o1(simplify_branches::SimplifyConstCondition::AfterConstProp), diff --git a/compiler/rustc_mir_transform/src/merge_branches.rs b/compiler/rustc_mir_transform/src/merge_branches.rs new file mode 100644 index 0000000000000..7f337ce20b46c --- /dev/null +++ b/compiler/rustc_mir_transform/src/merge_branches.rs @@ -0,0 +1,240 @@ +//! This pass attempts to merge all branches to eliminate switch terminator. +//! Ideally, we could combine it with `MatchBranchSimplification`, as these two passes +//! match and merge statements with different patterns. Given the compile time and +//! code complexity, we have not merged them into a more general pass for now. +use rustc_const_eval::const_eval::mk_eval_cx_for_const_val; +use rustc_index::bit_set::BitSet; +use rustc_middle::mir::patch::MirPatch; +use rustc_middle::mir::*; +use rustc_middle::ty; +use rustc_middle::ty::util::Discr; +use rustc_middle::ty::{ParamEnv, TyCtxt}; +use rustc_mir_dataflow::impls::borrowed_locals; + +use crate::dead_store_elimination::DeadStoreAnalysis; + +pub(super) struct MergeBranchSimplification; + +impl<'tcx> crate::MirPass<'tcx> for MergeBranchSimplification { + fn is_enabled(&self, sess: &rustc_session::Session) -> bool { + sess.mir_opt_level() >= 2 + } + + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + let def_id = body.source.def_id(); + let param_env = tcx.param_env_reveal_all_normalized(def_id); + + let borrowed_locals = borrowed_locals(body); + let mut dead_store_analysis = DeadStoreAnalysis::new(tcx, body, &borrowed_locals); + + for switch_bb_idx in body.basic_blocks.indices() { + let bbs = &*body.basic_blocks; + let Some((switch_discr, targets)) = bbs[switch_bb_idx].terminator().kind.as_switch() + else { + continue; + }; + // Check that destinations are identical, and if not, then don't optimize this block. + let mut targets_iter = targets.iter(); + let first_terminator_kind = &bbs[targets_iter.next().unwrap().1].terminator().kind; + if targets_iter.any(|(_, other_target)| { + first_terminator_kind != &bbs[other_target].terminator().kind + }) { + continue; + } + // We require that the possible target blocks all be distinct. + if !targets.is_distinct() { + continue; + } + if !bbs[targets.otherwise()].is_empty_unreachable() { + continue; + } + // Check if the copy source matches the following pattern. + // _2 = discriminant(*_1); // "*_1" is the expected the copy source. + // switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1]; + let Some(&Statement { + kind: StatementKind::Assign(box (discr_place, Rvalue::Discriminant(src_place))), + .. + }) = bbs[switch_bb_idx].statements.last() + else { + continue; + }; + if switch_discr.place() != Some(discr_place) { + continue; + } + let src_ty = src_place.ty(body.local_decls(), tcx); + if let Some(dest_place) = can_simplify_to_copy( + tcx, + param_env, + body, + targets, + src_place, + src_ty, + &mut dead_store_analysis, + ) { + let statement_index = bbs[switch_bb_idx].statements.len(); + let parent_end = Location { block: switch_bb_idx, statement_index }; + let mut patch = MirPatch::new(body); + patch.add_assign(parent_end, dest_place, Rvalue::Use(Operand::Copy(src_place))); + patch.patch_terminator(switch_bb_idx, first_terminator_kind.clone()); + patch.apply(body); + super::simplify::remove_dead_blocks(body); + // After modifying the MIR, the result of `MaybeTransitiveLiveLocals` may become invalid, + // keeping it simple to process only once. + break; + } + } + } +} + +/// The GVN simplified +/// ```ignore (syntax-highlighting-only) +/// match a { +/// Foo::A(x) => Foo::A(*x), +/// Foo::B => Foo::B +/// } +/// ``` +/// to +/// ```ignore (syntax-highlighting-only) +/// match a { +/// Foo::A(_x) => a, // copy a +/// Foo::B => Foo::B +/// } +/// ``` +/// This function answers whether it can be simplified to a copy statement +/// by returning the copy destination. +fn can_simplify_to_copy<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + body: &Body<'tcx>, + targets: &SwitchTargets, + src_place: Place<'tcx>, + src_ty: tcx::PlaceTy<'tcx>, + dead_store_analysis: &mut DeadStoreAnalysis<'tcx, '_, '_>, +) -> Option> { + let mut targets_iter = targets.iter(); + let (first_index, first_target) = targets_iter.next()?; + let dest_place = find_copy_assign( + tcx, + param_env, + body, + first_index, + first_target, + src_place, + src_ty, + dead_store_analysis, + )?; + let dest_ty = dest_place.ty(body.local_decls(), tcx); + if dest_ty.ty != src_ty.ty { + return None; + } + for (other_index, other_target) in targets_iter { + if dest_place + != find_copy_assign( + tcx, + param_env, + body, + other_index, + other_target, + src_place, + src_ty, + dead_store_analysis, + )? + { + return None; + } + } + Some(dest_place) +} + +// Find the single assignment statement where the source of the copy is from the source. +// All other statements are dead statements or have no effect that can be eliminated. +fn find_copy_assign<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + body: &Body<'tcx>, + index: u128, + target_block: BasicBlock, + src_place: Place<'tcx>, + src_ty: tcx::PlaceTy<'tcx>, + dead_store_analysis: &mut DeadStoreAnalysis<'tcx, '_, '_>, +) -> Option> { + let statements = &body.basic_blocks[target_block].statements; + if statements.is_empty() { + return None; + } + let assign_stmt = if statements.len() == 1 { + 0 + } else { + let mut lived_stmts: BitSet = BitSet::new_filled(statements.len()); + let mut expected_assign_stmt = None; + for (statement_index, statement) in statements.iter().enumerate().rev() { + let loc = Location { block: target_block, statement_index }; + if dead_store_analysis.is_dead_store(loc, &statement.kind) { + lived_stmts.remove(statement_index); + } else if matches!( + statement.kind, + StatementKind::StorageLive(_) | StatementKind::StorageDead(_) + ) { + } else if matches!(statement.kind, StatementKind::Assign(_)) + && expected_assign_stmt.is_none() + { + // There is only one assign statement that cannot be ignored + // that can be used as an expected copy statement. + expected_assign_stmt = Some(statement_index); + lived_stmts.remove(statement_index); + } else { + return None; + } + } + let expected_assign = expected_assign_stmt?; + if !lived_stmts.is_empty() { + // We can ignore the paired StorageLive and StorageDead. + let mut storage_live_locals: BitSet = BitSet::new_empty(body.local_decls.len()); + for stmt_index in lived_stmts.iter() { + let statement = &statements[stmt_index]; + match &statement.kind { + StatementKind::StorageLive(local) if storage_live_locals.insert(*local) => {} + StatementKind::StorageDead(local) if storage_live_locals.remove(*local) => {} + _ => return None, + } + } + if !storage_live_locals.is_empty() { + return None; + } + } + expected_assign + }; + let &(dest_place, ref rvalue) = statements[assign_stmt].kind.as_assign()?; + let dest_ty = dest_place.ty(body.local_decls(), tcx); + if dest_ty.ty != src_ty.ty { + return None; + } + let ty::Adt(def, _) = dest_ty.ty.kind() else { + return None; + }; + match rvalue { + // Check if `_3 = const Foo::B` can be transformed to `_3 = copy *_1`. + Rvalue::Use(Operand::Constant(box constant)) + if let Const::Val(const_, ty) = constant.const_ => + { + let (ecx, op) = mk_eval_cx_for_const_val(tcx.at(constant.span), param_env, const_, ty)?; + let variant = ecx.read_discriminant(&op).discard_err()?; + if !def.variants()[variant].fields.is_empty() { + return None; + } + let Discr { val, .. } = ty.discriminant_for_variant(tcx, variant)?; + if val != index { + return None; + } + } + Rvalue::Use(Operand::Copy(place)) if *place == src_place => {} + // Check if `_3 = Foo::B` can be transformed to `_3 = copy *_1`. + Rvalue::Aggregate(box AggregateKind::Adt(_, variant_index, _, _, None), fields) + if fields.is_empty() + && let Some(Discr { val, .. }) = + src_ty.ty.discriminant_for_variant(tcx, *variant_index) + && val == index => {} + _ => return None, + } + Some(dest_place) +} diff --git a/tests/codegen/match-optimizes-away.rs b/tests/codegen/match-optimizes-away.rs index 82ab5718b3750..3784da153efca 100644 --- a/tests/codegen/match-optimizes-away.rs +++ b/tests/codegen/match-optimizes-away.rs @@ -1,5 +1,5 @@ -// -//@ compile-flags: -O +//@ compile-flags: -O -Cno-prepopulate-passes + #![crate_type = "lib"] pub enum Three { @@ -19,8 +19,9 @@ pub enum Four { #[no_mangle] pub fn three_valued(x: Three) -> Three { // CHECK-LABEL: @three_valued - // CHECK-NEXT: {{^.*:$}} - // CHECK-NEXT: ret i8 %0 + // CHECK-SAME: (i8{{.*}} [[X:%x]]) + // CHECK-NEXT: start: + // CHECK-NEXT: ret i8 [[X]] match x { Three::A => Three::A, Three::B => Three::B, @@ -31,8 +32,9 @@ pub fn three_valued(x: Three) -> Three { #[no_mangle] pub fn four_valued(x: Four) -> Four { // CHECK-LABEL: @four_valued - // CHECK-NEXT: {{^.*:$}} - // CHECK-NEXT: ret i16 %0 + // CHECK-SAME: (i16{{.*}} [[X:%x]]) + // CHECK-NEXT: start: + // CHECK-NEXT: ret i16 [[X]] match x { Four::A => Four::A, Four::B => Four::B, diff --git a/tests/mir-opt/merge_br/merge_br.no_fields.MergeBranchSimplification.diff b/tests/mir-opt/merge_br/merge_br.no_fields.MergeBranchSimplification.diff new file mode 100644 index 0000000000000..1e28211bebf24 --- /dev/null +++ b/tests/mir-opt/merge_br/merge_br.no_fields.MergeBranchSimplification.diff @@ -0,0 +1,34 @@ +- // MIR for `no_fields` before MergeBranchSimplification ++ // MIR for `no_fields` after MergeBranchSimplification + + fn no_fields(_1: NoFields) -> NoFields { + debug a => _1; + let mut _0: NoFields; + let mut _2: isize; + + bb0: { + _2 = discriminant(_1); +- switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1]; ++ _0 = copy _1; ++ goto -> bb1; + } + + bb1: { +- unreachable; +- } +- +- bb2: { +- _0 = NoFields::B; +- goto -> bb4; +- } +- +- bb3: { +- _0 = NoFields::A; +- goto -> bb4; +- } +- +- bb4: { + return; + } + } + diff --git a/tests/mir-opt/merge_br/merge_br.no_fields_failed.MergeBranchSimplification.diff b/tests/mir-opt/merge_br/merge_br.no_fields_failed.MergeBranchSimplification.diff new file mode 100644 index 0000000000000..eb26af945ecde --- /dev/null +++ b/tests/mir-opt/merge_br/merge_br.no_fields_failed.MergeBranchSimplification.diff @@ -0,0 +1,32 @@ +- // MIR for `no_fields_failed` before MergeBranchSimplification ++ // MIR for `no_fields_failed` after MergeBranchSimplification + + fn no_fields_failed(_1: NoFields) -> NoFields { + debug a => _1; + let mut _0: NoFields; + let mut _2: isize; + + bb0: { + _2 = discriminant(_1); + switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1]; + } + + bb1: { + unreachable; + } + + bb2: { + _0 = NoFields::A; + goto -> bb4; + } + + bb3: { + _0 = NoFields::B; + goto -> bb4; + } + + bb4: { + return; + } + } + diff --git a/tests/mir-opt/merge_br/merge_br.no_fields_mismatch_type_failed.MergeBranchSimplification.diff b/tests/mir-opt/merge_br/merge_br.no_fields_mismatch_type_failed.MergeBranchSimplification.diff new file mode 100644 index 0000000000000..5beb268a253ce --- /dev/null +++ b/tests/mir-opt/merge_br/merge_br.no_fields_mismatch_type_failed.MergeBranchSimplification.diff @@ -0,0 +1,32 @@ +- // MIR for `no_fields_mismatch_type_failed` before MergeBranchSimplification ++ // MIR for `no_fields_mismatch_type_failed` after MergeBranchSimplification + + fn no_fields_mismatch_type_failed(_1: NoFields) -> NoFields2 { + debug a => _1; + let mut _0: NoFields2; + let mut _2: isize; + + bb0: { + _2 = discriminant(_1); + switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1]; + } + + bb1: { + unreachable; + } + + bb2: { + _0 = NoFields2::B; + goto -> bb4; + } + + bb3: { + _0 = NoFields2::A; + goto -> bb4; + } + + bb4: { + return; + } + } + diff --git a/tests/mir-opt/merge_br/merge_br.no_fields_ref.MergeBranchSimplification.diff b/tests/mir-opt/merge_br/merge_br.no_fields_ref.MergeBranchSimplification.diff new file mode 100644 index 0000000000000..a55cb5a0e6961 --- /dev/null +++ b/tests/mir-opt/merge_br/merge_br.no_fields_ref.MergeBranchSimplification.diff @@ -0,0 +1,34 @@ +- // MIR for `no_fields_ref` before MergeBranchSimplification ++ // MIR for `no_fields_ref` after MergeBranchSimplification + + fn no_fields_ref(_1: &NoFields) -> NoFields { + debug a => _1; + let mut _0: NoFields; + let mut _2: isize; + + bb0: { + _2 = discriminant((*_1)); +- switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1]; ++ _0 = copy (*_1); ++ goto -> bb1; + } + + bb1: { +- unreachable; +- } +- +- bb2: { +- _0 = NoFields::B; +- goto -> bb4; +- } +- +- bb3: { +- _0 = NoFields::A; +- goto -> bb4; +- } +- +- bb4: { + return; + } + } + diff --git a/tests/mir-opt/merge_br/merge_br.option.MergeBranchSimplification.diff b/tests/mir-opt/merge_br/merge_br.option.MergeBranchSimplification.diff new file mode 100644 index 0000000000000..dbcc219d0adb8 --- /dev/null +++ b/tests/mir-opt/merge_br/merge_br.option.MergeBranchSimplification.diff @@ -0,0 +1,41 @@ +- // MIR for `option` before MergeBranchSimplification ++ // MIR for `option` after MergeBranchSimplification + + fn option(_1: Option) -> Option { + debug a => _1; + let mut _0: std::option::Option; + let mut _2: isize; + let _3: i32; + scope 1 { + debug _b => _3; + } + + bb0: { + _2 = discriminant(_1); +- switchInt(move _2) -> [0: bb2, 1: bb3, otherwise: bb1]; +- } +- +- bb1: { +- unreachable; +- } +- +- bb2: { +- _0 = Option::::None; +- goto -> bb4; +- } +- +- bb3: { +- StorageLive(_3); +- _3 = copy ((_1 as Some).0: i32); + _0 = copy _1; +- StorageDead(_3); +- goto -> bb4; ++ goto -> bb1; + } + +- bb4: { ++ bb1: { + return; + } + } + diff --git a/tests/mir-opt/merge_br/merge_br.option_dse_failed.MergeBranchSimplification.diff b/tests/mir-opt/merge_br/merge_br.option_dse_failed.MergeBranchSimplification.diff new file mode 100644 index 0000000000000..284374b80fddc --- /dev/null +++ b/tests/mir-opt/merge_br/merge_br.option_dse_failed.MergeBranchSimplification.diff @@ -0,0 +1,34 @@ +- // MIR for `option_dse_failed` before MergeBranchSimplification ++ // MIR for `option_dse_failed` after MergeBranchSimplification + + fn option_dse_failed(_1: Option, _2: &mut i32) -> Option { + debug a => _1; + debug b => _2; + let mut _0: std::option::Option; + let mut _3: isize; + + bb0: { + _3 = discriminant(_1); + switchInt(move _3) -> [0: bb2, 1: bb3, otherwise: bb1]; + } + + bb1: { + unreachable; + } + + bb2: { + _0 = Option::::None; + goto -> bb4; + } + + bb3: { + (*_2) = const 1_i32; + _0 = copy _1; + goto -> bb4; + } + + bb4: { + return; + } + } + diff --git a/tests/mir-opt/merge_br/merge_br.rs b/tests/mir-opt/merge_br/merge_br.rs new file mode 100644 index 0000000000000..f8feb9f5aab58 --- /dev/null +++ b/tests/mir-opt/merge_br/merge_br.rs @@ -0,0 +1,87 @@ +//@ test-mir-pass: MergeBranchSimplification +//@ compile-flags: -Cdebuginfo=2 + +pub enum NoFields { + A, + B, +} + +pub enum NoFields2 { + A, + B, +} + +// EMIT_MIR merge_br.no_fields.MergeBranchSimplification.diff +pub fn no_fields(a: NoFields) -> NoFields { + // CHECK-LABEL: no_fields( + // CHECK: bb0: { + // CHECK-NEXT: _{{.*}} = discriminant([[SRC:_1]]); + // CHECK-NEXT: _0 = copy [[SRC]]; + match a { + NoFields::A => NoFields::A, + NoFields::B => NoFields::B, + } +} + +// EMIT_MIR merge_br.no_fields_ref.MergeBranchSimplification.diff +pub fn no_fields_ref(a: &NoFields) -> NoFields { + // CHECK-LABEL: no_fields_ref( + // CHECK: bb0: { + // CHECK-NEXT: _{{.*}} = discriminant([[SRC:\(\*_1\)]]); + // CHECK-NEXT: _0 = copy [[SRC]]; + match a { + NoFields::A => NoFields::A, + NoFields::B => NoFields::B, + } +} + +// EMIT_MIR merge_br.no_fields_mismatch_type_failed.MergeBranchSimplification.diff +pub fn no_fields_mismatch_type_failed(a: NoFields) -> NoFields2 { + // CHECK-LABEL: no_fields_mismatch_type_failed( + // CHECK: bb0: { + // CHECK-NEXT: _{{.*}} = discriminant([[SRC:_1]]); + // CHECK-NOT: _0 = copy [[SRC]]; + match a { + NoFields::A => NoFields2::A, + NoFields::B => NoFields2::B, + } +} + +// EMIT_MIR merge_br.no_fields_failed.MergeBranchSimplification.diff +pub fn no_fields_failed(a: NoFields) -> NoFields { + // CHECK-LABEL: no_fields_failed( + // CHECK: bb0: { + // CHECK-NEXT: _{{.*}} = discriminant([[SRC:_1]]); + // CHECK-NOT: _0 = copy [[SRC]]; + match a { + NoFields::A => NoFields::B, + NoFields::B => NoFields::A, + } +} + +// EMIT_MIR merge_br.option.MergeBranchSimplification.diff +pub fn option(a: Option) -> Option { + // CHECK-LABEL: option( + // CHECK: bb0: { + // CHECK-NEXT: _{{.*}} = discriminant([[SRC:_1]]); + // CHECK-NEXT: _0 = copy [[SRC]]; + match a { + Some(_b) => a, + None => None, + } +} + +// EMIT_MIR merge_br.option_dse_failed.MergeBranchSimplification.diff +pub fn option_dse_failed(a: Option, b: &mut i32) -> Option { + // CHECK-LABEL: option_dse_failed( + // CHECK: bb0: { + // CHECK-NEXT: [[DISCR:_.*]] = discriminant([[SRC:_1]]); + // CHECK-NEXT: switchInt(move [[DISCR]]) + match a { + Some(_) => { + *b = 1; + a + } + None => None, + } +} diff --git a/tests/mir-opt/pre-codegen/clone_as_copy.enum_clone_as_copy.PreCodegen.after.mir b/tests/mir-opt/pre-codegen/clone_as_copy.enum_clone_as_copy.PreCodegen.after.mir index 9f88e1961ec88..b36475008efa9 100644 --- a/tests/mir-opt/pre-codegen/clone_as_copy.enum_clone_as_copy.PreCodegen.after.mir +++ b/tests/mir-opt/pre-codegen/clone_as_copy.enum_clone_as_copy.PreCodegen.after.mir @@ -5,11 +5,12 @@ fn enum_clone_as_copy(_1: &Enum1) -> Enum1 { let mut _0: Enum1; scope 1 (inlined ::clone) { debug self => _1; - let mut _2: isize; + let _2: &AllCopy; let mut _3: &AllCopy; - let mut _4: &NestCopy; + let _4: &NestCopy; + let mut _5: &NestCopy; scope 2 { - debug __self_0 => _3; + debug __self_0 => _2; scope 6 (inlined ::clone) { debug self => _3; } @@ -17,10 +18,10 @@ fn enum_clone_as_copy(_1: &Enum1) -> Enum1 { scope 3 { debug __self_0 => _4; scope 4 (inlined ::clone) { - debug self => _4; - let _5: &AllCopy; + debug self => _5; + let _6: &AllCopy; scope 5 (inlined ::clone) { - debug self => _5; + debug self => _6; } } } @@ -30,33 +31,14 @@ fn enum_clone_as_copy(_1: &Enum1) -> Enum1 { StorageLive(_2); StorageLive(_3); StorageLive(_4); - _2 = discriminant((*_1)); - switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb4]; - } - - bb1: { - _3 = &(((*_1) as A).0: AllCopy); - _0 = copy (*_1); - goto -> bb3; - } - - bb2: { - _4 = &(((*_1) as B).0: NestCopy); StorageLive(_5); - _5 = &((((*_1) as B).0: NestCopy).1: AllCopy); - StorageDead(_5); + StorageLive(_6); _0 = copy (*_1); - goto -> bb3; - } - - bb3: { + StorageDead(_6); + StorageDead(_5); StorageDead(_4); StorageDead(_3); StorageDead(_2); return; } - - bb4: { - unreachable; - } } diff --git a/tests/mir-opt/pre-codegen/clone_as_copy.rs b/tests/mir-opt/pre-codegen/clone_as_copy.rs index f5ff1854d387d..c5ab43002560c 100644 --- a/tests/mir-opt/pre-codegen/clone_as_copy.rs +++ b/tests/mir-opt/pre-codegen/clone_as_copy.rs @@ -32,12 +32,12 @@ fn clone_as_copy(v: &NestCopy) -> NestCopy { v.clone() } -// FIXME: We can merge into exactly one assignment statement. +// We can merge into exactly one assignment statement. // EMIT_MIR clone_as_copy.enum_clone_as_copy.PreCodegen.after.mir fn enum_clone_as_copy(v: &Enum1) -> Enum1 { // CHECK-LABEL: fn enum_clone_as_copy( // CHECK-NOT: = Enum1:: // CHECK: _0 = copy (*_1); - // CHECK: _0 = copy (*_1); + // CHECK-NOT: _0 = copy (*_1); v.clone() } diff --git a/tests/mir-opt/pre-codegen/try_identity.old.PreCodegen.after.mir b/tests/mir-opt/pre-codegen/try_identity.old.PreCodegen.after.mir index 889e80d26e1cc..f7b6cbc855f59 100644 --- a/tests/mir-opt/pre-codegen/try_identity.old.PreCodegen.after.mir +++ b/tests/mir-opt/pre-codegen/try_identity.old.PreCodegen.after.mir @@ -3,38 +3,17 @@ fn old(_1: Result) -> Result { debug x => _1; let mut _0: std::result::Result; - let mut _2: isize; - let _3: T; - let _4: E; + let _2: T; + let _3: E; scope 1 { - debug v => _3; + debug v => _2; } scope 2 { - debug e => _4; + debug e => _3; } bb0: { - _2 = discriminant(_1); - switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb4]; - } - - bb1: { - _3 = copy ((_1 as Ok).0: T); - _0 = copy _1; - goto -> bb3; - } - - bb2: { - _4 = copy ((_1 as Err).0: E); _0 = copy _1; - goto -> bb3; - } - - bb3: { return; } - - bb4: { - unreachable; - } }