|  | 
|  | 1 | +//! The general point of the optimizations provided here is to simplify something like: | 
|  | 2 | +//! | 
|  | 3 | +//! ```rust | 
|  | 4 | +//! match x { | 
|  | 5 | +//!     Ok(x) => Ok(x), | 
|  | 6 | +//!     Err(x) => Err(x) | 
|  | 7 | +//! } | 
|  | 8 | +//! ``` | 
|  | 9 | +//! | 
|  | 10 | +//! into just `x`. | 
|  | 11 | +
 | 
|  | 12 | +use crate::transform::{MirPass, MirSource, simplify}; | 
|  | 13 | +use rustc::ty::{TyCtxt, Ty}; | 
|  | 14 | +use rustc::mir::*; | 
|  | 15 | +use rustc_target::abi::VariantIdx; | 
|  | 16 | +use itertools::Itertools as _; | 
|  | 17 | + | 
|  | 18 | +/// Simplifies arms of form `Variant(x) => Variant(x)` to just a move. | 
|  | 19 | +/// | 
|  | 20 | +/// This is done by transforming basic blocks where the statements match: | 
|  | 21 | +/// | 
|  | 22 | +/// ```rust | 
|  | 23 | +/// _LOCAL_TMP = ((_LOCAL_1 as Variant ).FIELD: TY ); | 
|  | 24 | +/// ((_LOCAL_0 as Variant).FIELD: TY) = move _LOCAL_TMP; | 
|  | 25 | +/// discriminant(_LOCAL_0) = VAR_IDX; | 
|  | 26 | +/// ``` | 
|  | 27 | +/// | 
|  | 28 | +/// into: | 
|  | 29 | +/// | 
|  | 30 | +/// ```rust | 
|  | 31 | +/// _LOCAL_0 = move _LOCAL_1 | 
|  | 32 | +/// ``` | 
|  | 33 | +pub struct SimplifyArmIdentity; | 
|  | 34 | + | 
|  | 35 | +impl<'tcx> MirPass<'tcx> for SimplifyArmIdentity { | 
|  | 36 | +    fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut Body<'tcx>) { | 
|  | 37 | +        for bb in body.basic_blocks_mut() { | 
|  | 38 | +            // Need 3 statements: | 
|  | 39 | +            let (s0, s1, s2) = match &mut *bb.statements { | 
|  | 40 | +                [s0, s1, s2] => (s0, s1, s2), | 
|  | 41 | +                _ => continue, | 
|  | 42 | +            }; | 
|  | 43 | + | 
|  | 44 | +            // Pattern match on the form we want: | 
|  | 45 | +            let (local_tmp_s0, local_1, vf_s0) = match match_get_variant_field(s0) { | 
|  | 46 | +                None => continue, | 
|  | 47 | +                Some(x) => x, | 
|  | 48 | +            }; | 
|  | 49 | +            let (local_tmp_s1, local_0, vf_s1) = match match_set_variant_field(s1) { | 
|  | 50 | +                None => continue, | 
|  | 51 | +                Some(x) => x, | 
|  | 52 | +            }; | 
|  | 53 | +            if local_tmp_s0 != local_tmp_s1 | 
|  | 54 | +                || vf_s0 != vf_s1 | 
|  | 55 | +                || Some((local_0, vf_s0.var_idx)) != match_set_discr(s2) | 
|  | 56 | +            { | 
|  | 57 | +                continue; | 
|  | 58 | +            } | 
|  | 59 | + | 
|  | 60 | +            // Right shape; transform! | 
|  | 61 | +            match &mut s0.kind { | 
|  | 62 | +                StatementKind::Assign(box (place, rvalue)) => { | 
|  | 63 | +                    *place = local_0.into(); | 
|  | 64 | +                    *rvalue = Rvalue::Use(Operand::Move(local_1.into())); | 
|  | 65 | +                } | 
|  | 66 | +                _ => unreachable!(), | 
|  | 67 | +            } | 
|  | 68 | +            s1.make_nop(); | 
|  | 69 | +            s2.make_nop(); | 
|  | 70 | +        } | 
|  | 71 | +    } | 
|  | 72 | +} | 
|  | 73 | + | 
|  | 74 | +/// Match on: | 
|  | 75 | +/// ```rust | 
|  | 76 | +/// _LOCAL_INTO = ((_LOCAL_FROM as Variant).FIELD: TY); | 
|  | 77 | +/// ``` | 
|  | 78 | +fn match_get_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> { | 
|  | 79 | +    match &stmt.kind { | 
|  | 80 | +        StatementKind::Assign(box (place_into, rvalue_from)) => match rvalue_from { | 
|  | 81 | +            Rvalue::Use(Operand::Copy(pf)) | Rvalue::Use(Operand::Move(pf)) => { | 
|  | 82 | +                let local_into = place_into.as_local()?; | 
|  | 83 | +                let (local_from, vf) = match_variant_field_place(&pf)?; | 
|  | 84 | +                Some((local_into, local_from, vf)) | 
|  | 85 | +            } | 
|  | 86 | +            _ => None, | 
|  | 87 | +        }, | 
|  | 88 | +        _ => None, | 
|  | 89 | +    } | 
|  | 90 | +} | 
|  | 91 | + | 
|  | 92 | +/// Match on: | 
|  | 93 | +/// ```rust | 
|  | 94 | +/// ((_LOCAL_FROM as Variant).FIELD: TY) = move _LOCAL_INTO; | 
|  | 95 | +/// ``` | 
|  | 96 | +fn match_set_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> { | 
|  | 97 | +    match &stmt.kind { | 
|  | 98 | +        StatementKind::Assign(box (place_from, rvalue_into)) => match rvalue_into { | 
|  | 99 | +            Rvalue::Use(Operand::Move(place_into)) => { | 
|  | 100 | +                let local_into = place_into.as_local()?; | 
|  | 101 | +                let (local_from, vf) = match_variant_field_place(&place_from)?; | 
|  | 102 | +                Some((local_into, local_from, vf)) | 
|  | 103 | +            } | 
|  | 104 | +            _ => None, | 
|  | 105 | +        }, | 
|  | 106 | +        _ => None, | 
|  | 107 | +    } | 
|  | 108 | +} | 
|  | 109 | + | 
|  | 110 | +/// Match on: | 
|  | 111 | +/// ```rust | 
|  | 112 | +/// discriminant(_LOCAL_TO_SET) = VAR_IDX; | 
|  | 113 | +/// ``` | 
|  | 114 | +fn match_set_discr<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, VariantIdx)> { | 
|  | 115 | +    match &stmt.kind { | 
|  | 116 | +        StatementKind::SetDiscriminant { place, variant_index } => Some(( | 
|  | 117 | +            place.as_local()?, | 
|  | 118 | +            *variant_index | 
|  | 119 | +        )), | 
|  | 120 | +        _ => None, | 
|  | 121 | +    } | 
|  | 122 | +} | 
|  | 123 | + | 
|  | 124 | +#[derive(PartialEq)] | 
|  | 125 | +struct VarField<'tcx> { | 
|  | 126 | +    field: Field, | 
|  | 127 | +    field_ty: Ty<'tcx>, | 
|  | 128 | +    var_idx: VariantIdx, | 
|  | 129 | +} | 
|  | 130 | + | 
|  | 131 | +/// Match on `((_LOCAL as Variant).FIELD: TY)`. | 
|  | 132 | +fn match_variant_field_place<'tcx>(place: &Place<'tcx>) -> Option<(Local, VarField<'tcx>)> { | 
|  | 133 | +    match place.as_ref() { | 
|  | 134 | +        PlaceRef { | 
|  | 135 | +            base: &PlaceBase::Local(local), | 
|  | 136 | +            projection: &[ProjectionElem::Downcast(_, var_idx), ProjectionElem::Field(field, ty)], | 
|  | 137 | +        } => Some((local, VarField { field, field_ty: ty, var_idx })), | 
|  | 138 | +        _ => None, | 
|  | 139 | +    } | 
|  | 140 | +} | 
|  | 141 | + | 
|  | 142 | +/// Simplifies `SwitchInt(_) -> [targets]`, | 
|  | 143 | +/// where all the `targets` have the same form, | 
|  | 144 | +/// into `goto -> target_first`. | 
|  | 145 | +pub struct SimplifyBranchSame; | 
|  | 146 | + | 
|  | 147 | +impl<'tcx> MirPass<'tcx> for SimplifyBranchSame { | 
|  | 148 | +    fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut Body<'tcx>) { | 
|  | 149 | +        let bbs = body.basic_blocks_mut(); | 
|  | 150 | +        for bb_idx in bbs.indices() { | 
|  | 151 | +            let targets = match &bbs[bb_idx].terminator().kind { | 
|  | 152 | +                TerminatorKind::SwitchInt { targets, .. } => targets, | 
|  | 153 | +                _ => continue, | 
|  | 154 | +            }; | 
|  | 155 | + | 
|  | 156 | +            // Reaching `unreachable` is UB so assume it doesn't happen. | 
|  | 157 | +            let mut iter_bbs_reachable = targets | 
|  | 158 | +                .iter() | 
|  | 159 | +                .map(|idx| (*idx, &bbs[*idx])) | 
|  | 160 | +                .filter(|(_, bb)| bb.terminator().kind != TerminatorKind::Unreachable) | 
|  | 161 | +                .peekable(); | 
|  | 162 | + | 
|  | 163 | +            // We want to `goto -> bb_first`. | 
|  | 164 | +            let bb_first = iter_bbs_reachable | 
|  | 165 | +                .peek() | 
|  | 166 | +                .map(|(idx, _)| *idx) | 
|  | 167 | +                .unwrap_or(targets[0]); | 
|  | 168 | + | 
|  | 169 | +            // All successor basic blocks should have the exact same form. | 
|  | 170 | +            let all_successors_equivalent = iter_bbs_reachable | 
|  | 171 | +                .map(|(_, bb)| bb) | 
|  | 172 | +                .tuple_windows() | 
|  | 173 | +                .all(|(bb_l, bb_r)| { | 
|  | 174 | +                    bb_l.is_cleanup == bb_r.is_cleanup | 
|  | 175 | +                    && bb_l.terminator().kind == bb_r.terminator().kind | 
|  | 176 | +                    && bb_l.statements.iter().eq_by(&bb_r.statements, |x, y| x.kind == y.kind) | 
|  | 177 | +                }); | 
|  | 178 | + | 
|  | 179 | +            if all_successors_equivalent { | 
|  | 180 | +                // Replace `SwitchInt(..) -> [bb_first, ..];` with a `goto -> bb_first;`. | 
|  | 181 | +                bbs[bb_idx].terminator_mut().kind = TerminatorKind::Goto { target: bb_first }; | 
|  | 182 | +            } | 
|  | 183 | +        } | 
|  | 184 | + | 
|  | 185 | +        // We may have dead blocks now, so remvoe those. | 
|  | 186 | +        simplify::remove_dead_blocks(body); | 
|  | 187 | +    } | 
|  | 188 | +} | 
|  | 189 | + | 
|  | 190 | +/* | 
|  | 191 | +
 | 
|  | 192 | +KEEPSAKE: REMOVE IF NOT NECESSARY! | 
|  | 193 | +
 | 
|  | 194 | +fn statement_semantic_eq(sa: &StatementKind<'_>, sb: &StatementKind<'_>) -> bool { | 
|  | 195 | +    use StatementKind::*; | 
|  | 196 | +    match (sb, sa) { | 
|  | 197 | +        (AscribeUserType(pa, va), AscribeUserType(pb, vb)) => pa == pb && va == vb, | 
|  | 198 | +        (Assign(a), Assign(b)) => a == b, | 
|  | 199 | +        (FakeRead(fa, pa), FakeRead(fb, pb)) => fa == fb && pa == pb, | 
|  | 200 | +        (InlineAsm(a), InlineAsm(b)) => a == b, | 
|  | 201 | +        (Nop, StatementKind::Nop) => true, | 
|  | 202 | +        (Retag(ra, pa), Retag(rb, pb)) => ra == rb && pa == pb, | 
|  | 203 | +        ( | 
|  | 204 | +            SetDiscriminant { place: pa, variant_index: va }, | 
|  | 205 | +            SetDiscriminant { place: pb, variant_index: vb }, | 
|  | 206 | +        ) => pa == pb && va == vb, | 
|  | 207 | +        (StorageDead(a), StorageDead(b)) => a == b, | 
|  | 208 | +        (StorageLive(a), StorageLive(b)) => a == b, | 
|  | 209 | +        (AscribeUserType(..), _) | (_, AscribeUserType(..)) | 
|  | 210 | +        | (StorageDead(..), _) | (_, StorageDead(..)) | 
|  | 211 | +        | (Assign(..), _) | (_, Assign(..)) | 
|  | 212 | +        | (FakeRead(..), _) | (_, FakeRead(..)) | 
|  | 213 | +        | (InlineAsm(..), _) | (_, InlineAsm(..)) | 
|  | 214 | +        | (Nop, _) | (_, Nop) | 
|  | 215 | +        | (Retag(..), _) | (_, Retag(..)) | 
|  | 216 | +        | (SetDiscriminant { .. }, _) | (_, SetDiscriminant { .. }) => true, | 
|  | 217 | +    } | 
|  | 218 | +} | 
|  | 219 | +
 | 
|  | 220 | +fn terminator_semantic_eq(ta: &TerminatorKind<'_>, tb: &TerminatorKind<'_>) -> bool { | 
|  | 221 | +    use TerminatorKind::*; | 
|  | 222 | +    match (ta, tb) { | 
|  | 223 | +        (Goto { target: a }, Goto { target: b }) => a == b, | 
|  | 224 | +        (Resume, Resume) | 
|  | 225 | +        | (Abort, Abort) | 
|  | 226 | +        | (Return, Return) | 
|  | 227 | +        | (Unreachable, Unreachable) | 
|  | 228 | +        | (GeneratorDrop, GeneratorDrop) => true, | 
|  | 229 | +        ( | 
|  | 230 | +            SwitchInt { discr: da, switch_ty: sa, targets: ta, values: _ }, | 
|  | 231 | +            SwitchInt { discr: db, switch_ty: sb, targets: tb, values: _ }, | 
|  | 232 | +        ) => da == db && sa == sb && ta == tb, | 
|  | 233 | +        ( | 
|  | 234 | +            Drop { location: la, target: ta, unwind: ua }, | 
|  | 235 | +            Drop { location: lb, target: tb, unwind: ub }, | 
|  | 236 | +        ) => la == lb && ta == tb && ua == ub, | 
|  | 237 | +        ( | 
|  | 238 | +            DropAndReplace { location: la, target: ta, unwind: ua, value: va }, | 
|  | 239 | +            DropAndReplace { location: lb, target: tb, unwind: ub, value: vb }, | 
|  | 240 | +        ) => la == lb && ta == tb && ua == ub && va == vb, | 
|  | 241 | +        ( | 
|  | 242 | +            Call { func: fa, args: aa, destination: da, cleanup: ca, from_hir_call: _ }, | 
|  | 243 | +            Call { func: fb, args: ab, destination: db, cleanup: cb, from_hir_call: _ }, | 
|  | 244 | +        ) => fa == fb && aa == ab && da == db && ca == cb, | 
|  | 245 | +        ( | 
|  | 246 | +            Assert { cond: coa, expected: ea, msg: ma, target: ta, cleanup: cla }, | 
|  | 247 | +            Assert { cond: cob, expected: eb, msg: mb, target: tb, cleanup: clb }, | 
|  | 248 | +        ) => coa == cob && ea == eb && ma == mb && ta == tb && cla == clb, | 
|  | 249 | +        ( | 
|  | 250 | +            Yield { value: va, resume: ra, drop: da }, | 
|  | 251 | +            Yield { value: vb, resume: rb, drop: db }, | 
|  | 252 | +        ) => va == vb && ra == rb && da == db, | 
|  | 253 | +        ( | 
|  | 254 | +            FalseEdges { real_target: ra, imaginary_target: ia }, | 
|  | 255 | +            FalseEdges { real_target: rb, imaginary_target: ib }, | 
|  | 256 | +        ) => ra == rb && ia == ib, | 
|  | 257 | +        ( | 
|  | 258 | +            FalseUnwind { real_target: ra, unwind: ua }, | 
|  | 259 | +            FalseUnwind { real_target: rb, unwind: ub }, | 
|  | 260 | +        ) => ra == rb && ua == ub, | 
|  | 261 | +        (Goto { .. }, _) | (_, Goto { .. }) | 
|  | 262 | +        | (Resume, _) | (_, Resume) | 
|  | 263 | +        | (Abort, _) | (_, Abort) | 
|  | 264 | +        | (Return, _) | (_, Return) | 
|  | 265 | +        | (Unreachable, _) | (_, Unreachable) | 
|  | 266 | +        | (GeneratorDrop, _) | (_, GeneratorDrop) | 
|  | 267 | +        | (SwitchInt { .. }, _) | (_, SwitchInt { .. }) | 
|  | 268 | +        | (Drop { .. }, _) | (_, Drop { .. }) | 
|  | 269 | +        | (DropAndReplace { .. }, _) | (_, DropAndReplace { .. }) | 
|  | 270 | +        | (Call { .. }, _) | (_, Call { .. }) | 
|  | 271 | +        | (Assert { .. }, _) | (_, Assert { .. }) | 
|  | 272 | +        | (Yield { .. }, _) | (_, Yield { .. }) | 
|  | 273 | +        | (FalseEdges { .. }, _) | (_, FalseEdges { .. }) => false, | 
|  | 274 | +    } | 
|  | 275 | +} | 
|  | 276 | +*/ | 
0 commit comments