cranelift_codegen/
simple_preopt.rs

1//! A pre-legalization rewriting pass.
2//!
3//! This module provides early-stage optimizations. The optimizations found
4//! should be useful for already well-optimized code.
5
6use crate::cursor::{Cursor, FuncCursor};
7use crate::divconst_magic_numbers::{magic_s32, magic_s64, magic_u32, magic_u64};
8use crate::divconst_magic_numbers::{MS32, MS64, MU32, MU64};
9use crate::ir::{
10    condcodes::IntCC,
11    instructions::Opcode,
12    types::{I128, I32, I64},
13    DataFlowGraph, Function, Inst, InstBuilder, InstructionData, Type, Value,
14};
15use crate::isa::TargetIsa;
16use crate::timing;
17
18#[inline]
19/// Replaces the unique result of the instruction inst to an alias of the given value, and
20/// replaces the instruction with a nop. Can be used only on instructions producing one unique
21/// result, otherwise will assert.
22fn replace_single_result_with_alias(dfg: &mut DataFlowGraph, inst: Inst, value: Value) {
23    // Replace the result value by an alias.
24    let results = dfg.detach_results(inst);
25    debug_assert!(results.len(&dfg.value_lists) == 1);
26    let result = results.get(0, &dfg.value_lists).unwrap();
27    dfg.change_to_alias(result, value);
28
29    // Replace instruction by a nop.
30    dfg.replace(inst).nop();
31}
32
33//----------------------------------------------------------------------
34//
35// Pattern-match helpers and transformation for div and rem by constants.
36
37// Simple math helpers
38
39/// if `x` is a power of two, or the negation thereof, return the power along
40/// with a boolean that indicates whether `x` is negative. Else return None.
41#[inline]
42fn i32_is_power_of_two(x: i32) -> Option<(bool, u32)> {
43    // We have to special-case this because abs(x) isn't representable.
44    if x == -0x8000_0000 {
45        return Some((true, 31));
46    }
47    let abs_x = i32::wrapping_abs(x) as u32;
48    if abs_x.is_power_of_two() {
49        return Some((x < 0, abs_x.trailing_zeros()));
50    }
51    None
52}
53
54/// Same comments as for i32_is_power_of_two apply.
55#[inline]
56fn i64_is_power_of_two(x: i64) -> Option<(bool, u32)> {
57    // We have to special-case this because abs(x) isn't representable.
58    if x == -0x8000_0000_0000_0000 {
59        return Some((true, 63));
60    }
61    let abs_x = i64::wrapping_abs(x) as u64;
62    if abs_x.is_power_of_two() {
63        return Some((x < 0, abs_x.trailing_zeros()));
64    }
65    None
66}
67
68/// Representation of an instruction that can be replaced by a single division/remainder operation
69/// between a left Value operand and a right immediate operand.
70#[derive(Debug)]
71enum DivRemByConstInfo {
72    DivU32(Value, u32),
73    DivU64(Value, u64),
74    DivS32(Value, i32),
75    DivS64(Value, i64),
76    RemU32(Value, u32),
77    RemU64(Value, u64),
78    RemS32(Value, i32),
79    RemS64(Value, i64),
80}
81
82/// Possibly create a DivRemByConstInfo from the given components, by figuring out which, if any,
83/// of the 8 cases apply, and also taking care to sanity-check the immediate.
84fn package_up_divrem_info(
85    value: Value,
86    value_type: Type,
87    imm_i64: i64,
88    is_signed: bool,
89    is_rem: bool,
90) -> Option<DivRemByConstInfo> {
91    let imm_u64 = imm_i64 as u64;
92
93    match (is_signed, value_type) {
94        (false, I32) => {
95            if imm_u64 < 0x1_0000_0000 {
96                if is_rem {
97                    Some(DivRemByConstInfo::RemU32(value, imm_u64 as u32))
98                } else {
99                    Some(DivRemByConstInfo::DivU32(value, imm_u64 as u32))
100                }
101            } else {
102                None
103            }
104        }
105
106        (false, I64) => {
107            // unsigned 64, no range constraint.
108            if is_rem {
109                Some(DivRemByConstInfo::RemU64(value, imm_u64))
110            } else {
111                Some(DivRemByConstInfo::DivU64(value, imm_u64))
112            }
113        }
114
115        (true, I32) => {
116            if imm_u64 <= 0x7fff_ffff || imm_u64 >= 0xffff_ffff_8000_0000 {
117                if is_rem {
118                    Some(DivRemByConstInfo::RemS32(value, imm_u64 as i32))
119                } else {
120                    Some(DivRemByConstInfo::DivS32(value, imm_u64 as i32))
121                }
122            } else {
123                None
124            }
125        }
126
127        (true, I64) => {
128            // signed 64, no range constraint.
129            if is_rem {
130                Some(DivRemByConstInfo::RemS64(value, imm_u64 as i64))
131            } else {
132                Some(DivRemByConstInfo::DivS64(value, imm_u64 as i64))
133            }
134        }
135
136        _ => None,
137    }
138}
139
140/// Examine `inst` to see if it is a div or rem by a constant, and if so return the operands,
141/// signedness, operation size and div-vs-rem-ness in a handy bundle.
142fn get_div_info(inst: Inst, dfg: &DataFlowGraph) -> Option<DivRemByConstInfo> {
143    if let InstructionData::BinaryImm64 { opcode, arg, imm } = dfg.insts[inst] {
144        let (is_signed, is_rem) = match opcode {
145            Opcode::UdivImm => (false, false),
146            Opcode::UremImm => (false, true),
147            Opcode::SdivImm => (true, false),
148            Opcode::SremImm => (true, true),
149            _ => return None,
150        };
151        return package_up_divrem_info(arg, dfg.value_type(arg), imm.into(), is_signed, is_rem);
152    }
153
154    None
155}
156
157/// Actually do the transformation given a bundle containing the relevant information.
158/// `divrem_info` describes a div or rem by a constant, that `pos` currently points at, and `inst`
159/// is the associated instruction.  `inst` is replaced by a sequence of other operations that
160/// calculate the same result. Note that there are various `divrem_info` cases where we cannot do
161/// any transformation, in which case `inst` is left unchanged.
162fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCursor, inst: Inst) {
163    let is_rem = match *divrem_info {
164        DivRemByConstInfo::DivU32(_, _)
165        | DivRemByConstInfo::DivU64(_, _)
166        | DivRemByConstInfo::DivS32(_, _)
167        | DivRemByConstInfo::DivS64(_, _) => false,
168        DivRemByConstInfo::RemU32(_, _)
169        | DivRemByConstInfo::RemU64(_, _)
170        | DivRemByConstInfo::RemS32(_, _)
171        | DivRemByConstInfo::RemS64(_, _) => true,
172    };
173
174    match *divrem_info {
175        // -------------------- U32 --------------------
176
177        // U32 div, rem by zero: ignore
178        DivRemByConstInfo::DivU32(_n1, 0) | DivRemByConstInfo::RemU32(_n1, 0) => {}
179
180        // U32 div by 1: identity
181        // U32 rem by 1: zero
182        DivRemByConstInfo::DivU32(n1, 1) | DivRemByConstInfo::RemU32(n1, 1) => {
183            if is_rem {
184                pos.func.dfg.replace(inst).iconst(I32, 0);
185            } else {
186                replace_single_result_with_alias(&mut pos.func.dfg, inst, n1);
187            }
188        }
189
190        // U32 div, rem by a power-of-2
191        DivRemByConstInfo::DivU32(n1, d) | DivRemByConstInfo::RemU32(n1, d)
192            if d.is_power_of_two() =>
193        {
194            debug_assert!(d >= 2);
195            // compute k where d == 2^k
196            let k = d.trailing_zeros();
197            debug_assert!(k >= 1 && k <= 31);
198            if is_rem {
199                let mask = (1u64 << k) - 1;
200                pos.func.dfg.replace(inst).band_imm(n1, mask as i64);
201            } else {
202                pos.func.dfg.replace(inst).ushr_imm(n1, k as i64);
203            }
204        }
205
206        // U32 div, rem by non-power-of-2
207        DivRemByConstInfo::DivU32(n1, d) | DivRemByConstInfo::RemU32(n1, d) => {
208            debug_assert!(d >= 3);
209            let MU32 {
210                mul_by,
211                do_add,
212                shift_by,
213            } = magic_u32(d);
214            let qf; // final quotient
215            let q0 = pos.ins().iconst(I32, mul_by as i64);
216            let q1 = pos.ins().umulhi(n1, q0);
217            if do_add {
218                debug_assert!(shift_by >= 1 && shift_by <= 32);
219                let t1 = pos.ins().isub(n1, q1);
220                let t2 = pos.ins().ushr_imm(t1, 1);
221                let t3 = pos.ins().iadd(t2, q1);
222                // I never found any case where shift_by == 1 here.
223                // So there's no attempt to fold out a zero shift.
224                debug_assert_ne!(shift_by, 1);
225                qf = pos.ins().ushr_imm(t3, (shift_by - 1) as i64);
226            } else {
227                debug_assert!(shift_by >= 0 && shift_by <= 31);
228                // Whereas there are known cases here for shift_by == 0.
229                if shift_by > 0 {
230                    qf = pos.ins().ushr_imm(q1, shift_by as i64);
231                } else {
232                    qf = q1;
233                }
234            }
235            // Now qf holds the final quotient. If necessary calculate the
236            // remainder instead.
237            if is_rem {
238                let tt = pos.ins().imul_imm(qf, d as i64);
239                pos.func.dfg.replace(inst).isub(n1, tt);
240            } else {
241                replace_single_result_with_alias(&mut pos.func.dfg, inst, qf);
242            }
243        }
244
245        // -------------------- U64 --------------------
246
247        // U64 div, rem by zero: ignore
248        DivRemByConstInfo::DivU64(_n1, 0) | DivRemByConstInfo::RemU64(_n1, 0) => {}
249
250        // U64 div by 1: identity
251        // U64 rem by 1: zero
252        DivRemByConstInfo::DivU64(n1, 1) | DivRemByConstInfo::RemU64(n1, 1) => {
253            if is_rem {
254                pos.func.dfg.replace(inst).iconst(I64, 0);
255            } else {
256                replace_single_result_with_alias(&mut pos.func.dfg, inst, n1);
257            }
258        }
259
260        // U64 div, rem by a power-of-2
261        DivRemByConstInfo::DivU64(n1, d) | DivRemByConstInfo::RemU64(n1, d)
262            if d.is_power_of_two() =>
263        {
264            debug_assert!(d >= 2);
265            // compute k where d == 2^k
266            let k = d.trailing_zeros();
267            debug_assert!(k >= 1 && k <= 63);
268            if is_rem {
269                let mask = (1u64 << k) - 1;
270                pos.func.dfg.replace(inst).band_imm(n1, mask as i64);
271            } else {
272                pos.func.dfg.replace(inst).ushr_imm(n1, k as i64);
273            }
274        }
275
276        // U64 div, rem by non-power-of-2
277        DivRemByConstInfo::DivU64(n1, d) | DivRemByConstInfo::RemU64(n1, d) => {
278            debug_assert!(d >= 3);
279            let MU64 {
280                mul_by,
281                do_add,
282                shift_by,
283            } = magic_u64(d);
284            let qf; // final quotient
285            let q0 = pos.ins().iconst(I64, mul_by as i64);
286            let q1 = pos.ins().umulhi(n1, q0);
287            if do_add {
288                debug_assert!(shift_by >= 1 && shift_by <= 64);
289                let t1 = pos.ins().isub(n1, q1);
290                let t2 = pos.ins().ushr_imm(t1, 1);
291                let t3 = pos.ins().iadd(t2, q1);
292                // I never found any case where shift_by == 1 here.
293                // So there's no attempt to fold out a zero shift.
294                debug_assert_ne!(shift_by, 1);
295                qf = pos.ins().ushr_imm(t3, (shift_by - 1) as i64);
296            } else {
297                debug_assert!(shift_by >= 0 && shift_by <= 63);
298                // Whereas there are known cases here for shift_by == 0.
299                if shift_by > 0 {
300                    qf = pos.ins().ushr_imm(q1, shift_by as i64);
301                } else {
302                    qf = q1;
303                }
304            }
305            // Now qf holds the final quotient. If necessary calculate the
306            // remainder instead.
307            if is_rem {
308                let tt = pos.ins().imul_imm(qf, d as i64);
309                pos.func.dfg.replace(inst).isub(n1, tt);
310            } else {
311                replace_single_result_with_alias(&mut pos.func.dfg, inst, qf);
312            }
313        }
314
315        // -------------------- S32 --------------------
316
317        // S32 div, rem by zero or -1: ignore
318        DivRemByConstInfo::DivS32(_n1, -1)
319        | DivRemByConstInfo::RemS32(_n1, -1)
320        | DivRemByConstInfo::DivS32(_n1, 0)
321        | DivRemByConstInfo::RemS32(_n1, 0) => {}
322
323        // S32 div by 1: identity
324        // S32 rem by 1: zero
325        DivRemByConstInfo::DivS32(n1, 1) | DivRemByConstInfo::RemS32(n1, 1) => {
326            if is_rem {
327                pos.func.dfg.replace(inst).iconst(I32, 0);
328            } else {
329                replace_single_result_with_alias(&mut pos.func.dfg, inst, n1);
330            }
331        }
332
333        DivRemByConstInfo::DivS32(n1, d) | DivRemByConstInfo::RemS32(n1, d) => {
334            if let Some((is_negative, k)) = i32_is_power_of_two(d) {
335                // k can be 31 only in the case that d is -2^31.
336                debug_assert!(k >= 1 && k <= 31);
337                let t1 = if k - 1 == 0 {
338                    n1
339                } else {
340                    pos.ins().sshr_imm(n1, (k - 1) as i64)
341                };
342                let t2 = pos.ins().ushr_imm(t1, (32 - k) as i64);
343                let t3 = pos.ins().iadd(n1, t2);
344                if is_rem {
345                    // S32 rem by a power-of-2
346                    let t4 = pos.ins().band_imm(t3, i32::wrapping_neg(1 << k) as i64);
347                    // Curiously, we don't care here what the sign of d is.
348                    pos.func.dfg.replace(inst).isub(n1, t4);
349                } else {
350                    // S32 div by a power-of-2
351                    let t4 = pos.ins().sshr_imm(t3, k as i64);
352                    if is_negative {
353                        pos.func.dfg.replace(inst).irsub_imm(t4, 0);
354                    } else {
355                        replace_single_result_with_alias(&mut pos.func.dfg, inst, t4);
356                    }
357                }
358            } else {
359                // S32 div, rem by a non-power-of-2
360                debug_assert!(d < -2 || d > 2);
361                let MS32 { mul_by, shift_by } = magic_s32(d);
362                let q0 = pos.ins().iconst(I32, mul_by as i64);
363                let q1 = pos.ins().smulhi(n1, q0);
364                let q2 = if d > 0 && mul_by < 0 {
365                    pos.ins().iadd(q1, n1)
366                } else if d < 0 && mul_by > 0 {
367                    pos.ins().isub(q1, n1)
368                } else {
369                    q1
370                };
371                debug_assert!(shift_by >= 0 && shift_by <= 31);
372                let q3 = if shift_by == 0 {
373                    q2
374                } else {
375                    pos.ins().sshr_imm(q2, shift_by as i64)
376                };
377                let t1 = pos.ins().ushr_imm(q3, 31);
378                let qf = pos.ins().iadd(q3, t1);
379                // Now qf holds the final quotient. If necessary calculate
380                // the remainder instead.
381                if is_rem {
382                    let tt = pos.ins().imul_imm(qf, d as i64);
383                    pos.func.dfg.replace(inst).isub(n1, tt);
384                } else {
385                    replace_single_result_with_alias(&mut pos.func.dfg, inst, qf);
386                }
387            }
388        }
389
390        // -------------------- S64 --------------------
391
392        // S64 div, rem by zero or -1: ignore
393        DivRemByConstInfo::DivS64(_n1, -1)
394        | DivRemByConstInfo::RemS64(_n1, -1)
395        | DivRemByConstInfo::DivS64(_n1, 0)
396        | DivRemByConstInfo::RemS64(_n1, 0) => {}
397
398        // S64 div by 1: identity
399        // S64 rem by 1: zero
400        DivRemByConstInfo::DivS64(n1, 1) | DivRemByConstInfo::RemS64(n1, 1) => {
401            if is_rem {
402                pos.func.dfg.replace(inst).iconst(I64, 0);
403            } else {
404                replace_single_result_with_alias(&mut pos.func.dfg, inst, n1);
405            }
406        }
407
408        DivRemByConstInfo::DivS64(n1, d) | DivRemByConstInfo::RemS64(n1, d) => {
409            if let Some((is_negative, k)) = i64_is_power_of_two(d) {
410                // k can be 63 only in the case that d is -2^63.
411                debug_assert!(k >= 1 && k <= 63);
412                let t1 = if k - 1 == 0 {
413                    n1
414                } else {
415                    pos.ins().sshr_imm(n1, (k - 1) as i64)
416                };
417                let t2 = pos.ins().ushr_imm(t1, (64 - k) as i64);
418                let t3 = pos.ins().iadd(n1, t2);
419                if is_rem {
420                    // S64 rem by a power-of-2
421                    let t4 = pos.ins().band_imm(t3, i64::wrapping_neg(1 << k));
422                    // Curiously, we don't care here what the sign of d is.
423                    pos.func.dfg.replace(inst).isub(n1, t4);
424                } else {
425                    // S64 div by a power-of-2
426                    let t4 = pos.ins().sshr_imm(t3, k as i64);
427                    if is_negative {
428                        pos.func.dfg.replace(inst).irsub_imm(t4, 0);
429                    } else {
430                        replace_single_result_with_alias(&mut pos.func.dfg, inst, t4);
431                    }
432                }
433            } else {
434                // S64 div, rem by a non-power-of-2
435                debug_assert!(d < -2 || d > 2);
436                let MS64 { mul_by, shift_by } = magic_s64(d);
437                let q0 = pos.ins().iconst(I64, mul_by);
438                let q1 = pos.ins().smulhi(n1, q0);
439                let q2 = if d > 0 && mul_by < 0 {
440                    pos.ins().iadd(q1, n1)
441                } else if d < 0 && mul_by > 0 {
442                    pos.ins().isub(q1, n1)
443                } else {
444                    q1
445                };
446                debug_assert!(shift_by >= 0 && shift_by <= 63);
447                let q3 = if shift_by == 0 {
448                    q2
449                } else {
450                    pos.ins().sshr_imm(q2, shift_by as i64)
451                };
452                let t1 = pos.ins().ushr_imm(q3, 63);
453                let qf = pos.ins().iadd(q3, t1);
454                // Now qf holds the final quotient. If necessary calculate
455                // the remainder instead.
456                if is_rem {
457                    let tt = pos.ins().imul_imm(qf, d);
458                    pos.func.dfg.replace(inst).isub(n1, tt);
459                } else {
460                    replace_single_result_with_alias(&mut pos.func.dfg, inst, qf);
461                }
462            }
463        }
464    }
465}
466
467mod simplify {
468    use super::*;
469    use crate::ir::{
470        dfg::ValueDef,
471        immediates,
472        instructions::Opcode,
473        types::{I16, I32, I8},
474    };
475    use std::marker::PhantomData;
476
477    pub struct PeepholeOptimizer<'a, 'b> {
478        phantom: PhantomData<(&'a (), &'b ())>,
479    }
480
481    pub fn peephole_optimizer<'a, 'b>(_: &dyn TargetIsa) -> PeepholeOptimizer<'a, 'b> {
482        PeepholeOptimizer {
483            phantom: PhantomData,
484        }
485    }
486
487    pub fn apply_all<'a, 'b>(
488        _optimizer: &mut PeepholeOptimizer<'a, 'b>,
489        pos: &mut FuncCursor<'a>,
490        inst: Inst,
491        native_word_width: u32,
492    ) {
493        simplify(pos, inst, native_word_width);
494        branch_opt(pos, inst);
495    }
496
497    #[inline]
498    fn resolve_imm64_value(dfg: &DataFlowGraph, value: Value) -> Option<immediates::Imm64> {
499        if let ValueDef::Result(candidate_inst, _) = dfg.value_def(value) {
500            if let InstructionData::UnaryImm {
501                opcode: Opcode::Iconst,
502                imm,
503            } = dfg.insts[candidate_inst]
504            {
505                return Some(imm);
506            }
507        }
508        None
509    }
510
511    /// Try to transform [(x << N) >> N] into a (un)signed-extending move.
512    /// Returns true if the final instruction has been converted to such a move.
513    fn try_fold_extended_move(
514        pos: &mut FuncCursor,
515        inst: Inst,
516        opcode: Opcode,
517        arg: Value,
518        imm: immediates::Imm64,
519    ) -> bool {
520        if let ValueDef::Result(arg_inst, _) = pos.func.dfg.value_def(arg) {
521            if let InstructionData::BinaryImm64 {
522                opcode: Opcode::IshlImm,
523                arg: prev_arg,
524                imm: prev_imm,
525            } = &pos.func.dfg.insts[arg_inst]
526            {
527                if imm != *prev_imm {
528                    return false;
529                }
530
531                let dest_ty = pos.func.dfg.ctrl_typevar(inst);
532                if dest_ty != pos.func.dfg.ctrl_typevar(arg_inst) || !dest_ty.is_int() {
533                    return false;
534                }
535
536                let imm_bits: i64 = imm.into();
537                let ireduce_ty = match (dest_ty.lane_bits() as i64).wrapping_sub(imm_bits) {
538                    8 => I8,
539                    16 => I16,
540                    32 => I32,
541                    _ => return false,
542                };
543                let ireduce_ty = ireduce_ty.by(dest_ty.lane_count()).unwrap();
544
545                // This becomes a no-op, since ireduce_ty has a smaller lane width than
546                // the argument type (also the destination type).
547                let arg = *prev_arg;
548                let narrower_arg = pos.ins().ireduce(ireduce_ty, arg);
549
550                if opcode == Opcode::UshrImm {
551                    pos.func.dfg.replace(inst).uextend(dest_ty, narrower_arg);
552                } else {
553                    pos.func.dfg.replace(inst).sextend(dest_ty, narrower_arg);
554                }
555                return true;
556            }
557        }
558        false
559    }
560
561    /// Apply basic simplifications.
562    ///
563    /// This folds constants with arithmetic to form `_imm` instructions, and other minor
564    /// simplifications.
565    ///
566    /// Doesn't apply some simplifications if the native word width (in bytes) is smaller than the
567    /// controlling type's width of the instruction. This would result in an illegal instruction that
568    /// would likely be expanded back into an instruction on smaller types with the same initial
569    /// opcode, creating unnecessary churn.
570    fn simplify(pos: &mut FuncCursor, inst: Inst, native_word_width: u32) {
571        match pos.func.dfg.insts[inst] {
572            InstructionData::Binary { opcode, args } => {
573                if let Some(mut imm) = resolve_imm64_value(&pos.func.dfg, args[1]) {
574                    let new_opcode = match opcode {
575                        Opcode::Iadd => Opcode::IaddImm,
576                        Opcode::Imul => Opcode::ImulImm,
577                        Opcode::Sdiv => Opcode::SdivImm,
578                        Opcode::Udiv => Opcode::UdivImm,
579                        Opcode::Srem => Opcode::SremImm,
580                        Opcode::Urem => Opcode::UremImm,
581                        Opcode::Band => Opcode::BandImm,
582                        Opcode::Bor => Opcode::BorImm,
583                        Opcode::Bxor => Opcode::BxorImm,
584                        Opcode::Rotl => Opcode::RotlImm,
585                        Opcode::Rotr => Opcode::RotrImm,
586                        Opcode::Ishl => Opcode::IshlImm,
587                        Opcode::Ushr => Opcode::UshrImm,
588                        Opcode::Sshr => Opcode::SshrImm,
589                        Opcode::Isub => {
590                            imm = imm.wrapping_neg();
591                            Opcode::IaddImm
592                        }
593                        _ => return,
594                    };
595                    let ty = pos.func.dfg.ctrl_typevar(inst);
596                    if ty.bytes() <= native_word_width {
597                        pos.func
598                            .dfg
599                            .replace(inst)
600                            .BinaryImm64(new_opcode, ty, imm, args[0]);
601
602                        // Repeat for BinaryImm simplification.
603                        simplify(pos, inst, native_word_width);
604                    }
605                } else if let Some(imm) = resolve_imm64_value(&pos.func.dfg, args[0]) {
606                    let new_opcode = match opcode {
607                        Opcode::Iadd => Opcode::IaddImm,
608                        Opcode::Imul => Opcode::ImulImm,
609                        Opcode::Band => Opcode::BandImm,
610                        Opcode::Bor => Opcode::BorImm,
611                        Opcode::Bxor => Opcode::BxorImm,
612                        Opcode::Isub => Opcode::IrsubImm,
613                        _ => return,
614                    };
615                    let ty = pos.func.dfg.ctrl_typevar(inst);
616                    if ty.bytes() <= native_word_width {
617                        pos.func
618                            .dfg
619                            .replace(inst)
620                            .BinaryImm64(new_opcode, ty, imm, args[1]);
621                    }
622                }
623            }
624
625            InstructionData::BinaryImm64 { opcode, arg, imm } => {
626                let ty = pos.func.dfg.ctrl_typevar(inst);
627
628                let mut arg = arg;
629                let mut imm = imm;
630                match opcode {
631                    Opcode::IaddImm
632                    | Opcode::ImulImm
633                    | Opcode::BorImm
634                    | Opcode::BandImm
635                    | Opcode::BxorImm => {
636                        // Fold binary_op(C2, binary_op(C1, x)) into binary_op(binary_op(C1, C2), x)
637                        if let ValueDef::Result(arg_inst, _) = pos.func.dfg.value_def(arg) {
638                            if let InstructionData::BinaryImm64 {
639                                opcode: prev_opcode,
640                                arg: prev_arg,
641                                imm: prev_imm,
642                            } = &pos.func.dfg.insts[arg_inst]
643                            {
644                                if opcode == *prev_opcode
645                                    && ty == pos.func.dfg.ctrl_typevar(arg_inst)
646                                {
647                                    let lhs: i64 = imm.into();
648                                    let rhs: i64 = (*prev_imm).into();
649                                    let new_imm = match opcode {
650                                        Opcode::BorImm => lhs | rhs,
651                                        Opcode::BandImm => lhs & rhs,
652                                        Opcode::BxorImm => lhs ^ rhs,
653                                        Opcode::IaddImm => lhs.wrapping_add(rhs),
654                                        Opcode::ImulImm => lhs.wrapping_mul(rhs),
655                                        _ => panic!("can't happen"),
656                                    };
657                                    let new_imm = immediates::Imm64::from(new_imm);
658                                    let new_arg = *prev_arg;
659                                    pos.func
660                                        .dfg
661                                        .replace(inst)
662                                        .BinaryImm64(opcode, ty, new_imm, new_arg);
663                                    imm = new_imm;
664                                    arg = new_arg;
665                                }
666                            }
667                        }
668                    }
669
670                    Opcode::UshrImm | Opcode::SshrImm => {
671                        if pos.func.dfg.ctrl_typevar(inst).bytes() <= native_word_width
672                            && try_fold_extended_move(pos, inst, opcode, arg, imm)
673                        {
674                            return;
675                        }
676                    }
677
678                    _ => {}
679                };
680
681                // Replace operations that are no-ops.
682                match (opcode, imm.into(), ty) {
683                    (Opcode::IaddImm, 0, _)
684                    | (Opcode::ImulImm, 1, _)
685                    | (Opcode::SdivImm, 1, _)
686                    | (Opcode::UdivImm, 1, _)
687                    | (Opcode::BorImm, 0, _)
688                    | (Opcode::BandImm, -1, _)
689                    | (Opcode::BxorImm, 0, _)
690                    | (Opcode::RotlImm, 0, _)
691                    | (Opcode::RotrImm, 0, _)
692                    | (Opcode::IshlImm, 0, _)
693                    | (Opcode::UshrImm, 0, _)
694                    | (Opcode::SshrImm, 0, _) => {
695                        // Alias the result value with the original argument.
696                        replace_single_result_with_alias(&mut pos.func.dfg, inst, arg);
697                    }
698                    (Opcode::ImulImm, 0, ty) | (Opcode::BandImm, 0, ty) if ty != I128 => {
699                        // Replace by zero.
700                        pos.func.dfg.replace(inst).iconst(ty, 0);
701                    }
702                    (Opcode::BorImm, -1, ty) if ty != I128 => {
703                        // Replace by minus one.
704                        pos.func.dfg.replace(inst).iconst(ty, -1);
705                    }
706                    _ => {}
707                }
708            }
709
710            InstructionData::IntCompare { opcode, cond, args } => {
711                debug_assert_eq!(opcode, Opcode::Icmp);
712                if let Some(imm) = resolve_imm64_value(&pos.func.dfg, args[1]) {
713                    if pos.func.dfg.ctrl_typevar(inst).bytes() <= native_word_width {
714                        pos.func.dfg.replace(inst).icmp_imm(cond, args[0], imm);
715                    }
716                }
717            }
718
719            _ => {}
720        }
721    }
722
723    /// Fold comparisons into branch operations when possible.
724    ///
725    /// This matches against operations which compare against zero, then use the
726    /// result in a conditional branch.
727    fn branch_opt(pos: &mut FuncCursor, inst: Inst) {
728        let (cmp_arg, new_then, new_else) = if let InstructionData::Brif {
729            arg: first_arg,
730            blocks: [block_then, block_else],
731            ..
732        } = pos.func.dfg.insts[inst]
733        {
734            let icmp_inst =
735                if let ValueDef::Result(icmp_inst, _) = pos.func.dfg.value_def(first_arg) {
736                    icmp_inst
737                } else {
738                    return;
739                };
740
741            if let InstructionData::IntCompareImm {
742                opcode: Opcode::IcmpImm,
743                arg: cmp_arg,
744                cond: cmp_cond,
745                imm: cmp_imm,
746            } = pos.func.dfg.insts[icmp_inst]
747            {
748                let cmp_imm: i64 = cmp_imm.into();
749                if cmp_imm != 0 {
750                    return;
751                }
752
753                let (new_then, new_else) = match cmp_cond {
754                    IntCC::Equal => (block_else, block_then),
755                    IntCC::NotEqual => (block_then, block_else),
756                    _ => return,
757                };
758
759                (cmp_arg, new_then, new_else)
760            } else {
761                return;
762            }
763        } else {
764            return;
765        };
766
767        if let InstructionData::Brif { arg, blocks, .. } = &mut pos.func.dfg.insts[inst] {
768            *arg = cmp_arg;
769            blocks[0] = new_then;
770            blocks[1] = new_else;
771        } else {
772            unreachable!();
773        }
774    }
775}
776
777/// The main pre-opt pass.
778pub fn do_preopt(func: &mut Function, isa: &dyn TargetIsa) {
779    let _tt = timing::preopt();
780
781    let mut pos = FuncCursor::new(func);
782    let native_word_width = isa.pointer_bytes() as u32;
783    let mut optimizer = simplify::peephole_optimizer(isa);
784
785    while let Some(_) = pos.next_block() {
786        while let Some(inst) = pos.next_inst() {
787            simplify::apply_all(&mut optimizer, &mut pos, inst, native_word_width);
788
789            // Try to transform divide-by-constant into simpler operations.
790            if let Some(divrem_info) = get_div_info(inst, &pos.func.dfg) {
791                do_divrem_transformation(&divrem_info, &mut pos, inst);
792                continue;
793            }
794        }
795    }
796}