//! The general point of the optimizations provided here is to simplify something like: //! //! ```rust //! match x { //! Ok(x) => Ok(x), //! Err(x) => Err(x) //! } //! ``` //! //! into just `x`. use crate::transform::{MirPass, MirSource, simplify}; use rustc::ty::{TyCtxt, Ty}; use rustc::mir::*; use rustc_target::abi::VariantIdx; use itertools::Itertools as _; /// Simplifies arms of form `Variant(x) => Variant(x)` to just a move. /// /// This is done by transforming basic blocks where the statements match: /// /// ```rust /// _LOCAL_TMP = ((_LOCAL_1 as Variant ).FIELD: TY ); /// ((_LOCAL_0 as Variant).FIELD: TY) = move _LOCAL_TMP; /// discriminant(_LOCAL_0) = VAR_IDX; /// ``` /// /// into: /// /// ```rust /// _LOCAL_0 = move _LOCAL_1 /// ``` pub struct SimplifyArmIdentity; impl<'tcx> MirPass<'tcx> for SimplifyArmIdentity { fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut BodyCache<'tcx>) { let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut(); for bb in basic_blocks { // Need 3 statements: let (s0, s1, s2) = match &mut *bb.statements { [s0, s1, s2] => (s0, s1, s2), _ => continue, }; // Pattern match on the form we want: let (local_tmp_s0, local_1, vf_s0) = match match_get_variant_field(s0) { None => continue, Some(x) => x, }; let (local_tmp_s1, local_0, vf_s1) = match match_set_variant_field(s1) { None => continue, Some(x) => x, }; if local_tmp_s0 != local_tmp_s1 // The field-and-variant information match up. || vf_s0 != vf_s1 // Source and target locals have the same type. // FIXME(Centril | oli-obk): possibly relax to same layout? || local_decls[local_0].ty != local_decls[local_1].ty // We're setting the discriminant of `local_0` to this variant. || Some((local_0, vf_s0.var_idx)) != match_set_discr(s2) { continue; } // Right shape; transform! match &mut s0.kind { StatementKind::Assign(box (place, rvalue)) => { *place = local_0.into(); *rvalue = Rvalue::Use(Operand::Move(local_1.into())); } _ => unreachable!(), } s1.make_nop(); s2.make_nop(); } } } /// Match on: /// ```rust /// _LOCAL_INTO = ((_LOCAL_FROM as Variant).FIELD: TY); /// ``` fn match_get_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> { match &stmt.kind { StatementKind::Assign(box (place_into, rvalue_from)) => match rvalue_from { Rvalue::Use(Operand::Copy(pf)) | Rvalue::Use(Operand::Move(pf)) => { let local_into = place_into.as_local()?; let (local_from, vf) = match_variant_field_place(&pf)?; Some((local_into, local_from, vf)) } _ => None, }, _ => None, } } /// Match on: /// ```rust /// ((_LOCAL_FROM as Variant).FIELD: TY) = move _LOCAL_INTO; /// ``` fn match_set_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> { match &stmt.kind { StatementKind::Assign(box (place_from, rvalue_into)) => match rvalue_into { Rvalue::Use(Operand::Move(place_into)) => { let local_into = place_into.as_local()?; let (local_from, vf) = match_variant_field_place(&place_from)?; Some((local_into, local_from, vf)) } _ => None, }, _ => None, } } /// Match on: /// ```rust /// discriminant(_LOCAL_TO_SET) = VAR_IDX; /// ``` fn match_set_discr<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, VariantIdx)> { match &stmt.kind { StatementKind::SetDiscriminant { place, variant_index } => Some(( place.as_local()?, *variant_index )), _ => None, } } #[derive(PartialEq)] struct VarField<'tcx> { field: Field, field_ty: Ty<'tcx>, var_idx: VariantIdx, } /// Match on `((_LOCAL as Variant).FIELD: TY)`. fn match_variant_field_place<'tcx>(place: &Place<'tcx>) -> Option<(Local, VarField<'tcx>)> { match place.as_ref() { PlaceRef { base: &PlaceBase::Local(local), projection: &[ProjectionElem::Downcast(_, var_idx), ProjectionElem::Field(field, ty)], } => Some((local, VarField { field, field_ty: ty, var_idx })), _ => None, } } /// Simplifies `SwitchInt(_) -> [targets]`, /// where all the `targets` have the same form, /// into `goto -> target_first`. pub struct SimplifyBranchSame; impl<'tcx> MirPass<'tcx> for SimplifyBranchSame { fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut BodyCache<'tcx>) { let mut did_remove_blocks = false; let bbs = body.basic_blocks_mut(); for bb_idx in bbs.indices() { let targets = match &bbs[bb_idx].terminator().kind { TerminatorKind::SwitchInt { targets, .. } => targets, _ => continue, }; let mut iter_bbs_reachable = targets .iter() .map(|idx| (*idx, &bbs[*idx])) .filter(|(_, bb)| { // Reaching `unreachable` is UB so assume it doesn't happen. bb.terminator().kind != TerminatorKind::Unreachable // But `asm!(...)` could abort the program, // so we cannot assume that the `unreachable` terminator itself is reachable. // FIXME(Centril): use a normalization pass instead of a check. || bb.statements.iter().any(|stmt| match stmt.kind { StatementKind::InlineAsm(..) => true, _ => false, }) }) .peekable(); // We want to `goto -> bb_first`. let bb_first = iter_bbs_reachable .peek() .map(|(idx, _)| *idx) .unwrap_or(targets[0]); // All successor basic blocks should have the exact same form. let all_successors_equivalent = iter_bbs_reachable .map(|(_, bb)| bb) .tuple_windows() .all(|(bb_l, bb_r)| { bb_l.is_cleanup == bb_r.is_cleanup && bb_l.terminator().kind == bb_r.terminator().kind && bb_l.statements.iter().eq_by(&bb_r.statements, |x, y| x.kind == y.kind) }); if all_successors_equivalent { // Replace `SwitchInt(..) -> [bb_first, ..];` with a `goto -> bb_first;`. bbs[bb_idx].terminator_mut().kind = TerminatorKind::Goto { target: bb_first }; did_remove_blocks = true; } } if did_remove_blocks { // We have dead blocks now, so remove those. simplify::remove_dead_blocks(body); } } }