Rust · 27903 bytes Raw Blame History
1 //! Strength reduction.
2 //!
3 //! Replaces expensive integer operations with cheaper equivalents:
4 //!
5 //! * `imul x, 2^k` → `shl x, k`
6 //! * `imul x, 0` → `const(0)`
7 //! * `imul x, 1` → identity (rewrite uses)
8 //! * `imul x, -1` → `ineg x`
9 //! * `idiv x, 1` → identity
10 //! * `idiv x, -1` → `ineg x`
11 //! * `iadd x, 0` → identity
12 //! * `isub x, 0` → identity
13 //! * `isub 0, x` → `ineg x`
14 //! * `bit_and x, 0` → `const(0)`
15 //! * `bit_and x, -1` → identity
16 //! * `bit_or x, 0` → identity
17 //! * `bit_or x, -1` → `const(-1)`
18 //! * `bit_xor x, 0` → identity
19 //! * `shl/lshr/ashr x, 0` → identity
20 //!
21 //! ## What we deliberately don't do here
22 //!
23 //! * **Signed division by `2^k`**: a single `ashr` is wrong because
24 //! integer division rounds *toward zero* while arithmetic shift
25 //! rounds *toward -infinity* for negatives. The correct lowering
26 //! needs a sign-bias adjustment (`(x + ((x >> 63) & ((1<<k)-1))) >> k`)
27 //! which is three instructions, not one — still profitable, but it
28 //! grows the IR. We'll handle it in a dedicated "div by constant"
29 //! lowering once we have a benchmark to point at.
30 //!
31 //! * **Signed modulo by `2^k`**: same problem — signed-mod semantics
32 //! differ from `bit_and` for negative dividends. Skip.
33 //!
34 //! * **Floating-point identities**: `x + 0.0`, `x * 1.0`, etc. are
35 //! *not* IEEE-equivalent (signed zeros, signaling NaNs). They're
36 //! only safe under `-Ofast` / fast-math, which we don't yet have.
37 //!
38 //! ## Implementation
39 //!
40 //! The pass walks every block and rewrites instructions in place where
41 //! the result type is identical to the rewrite. For "identity"
42 //! rewrites that pass a value through unchanged, we use
43 //! `util::substitute_uses` to retarget downstream consumers at the
44 //! original operand and let DCE clean up the now-dead instruction.
45
46 use super::pass::Pass;
47 use super::util::{for_each_operand_mut, for_each_terminator_operand_mut};
48 use crate::ir::inst::*;
49 use crate::ir::types::{IntWidth, IrType};
50 use std::collections::HashMap;
51
52 /// Apply `rewrites` to every operand slot in the function in a
53 /// single walk. Audit B-2: replaces the previous per-Identity
54 /// `substitute_uses` calls (which each walked the function in
55 /// O(function_size)) with a single O(function_size) walk for any
56 /// number of rewrites.
57 ///
58 /// Audit B-9: see `cse::substitute_uses_batch` for the closure
59 /// `Copy`-by-capture contract — same constraint applies here.
60 fn substitute_uses_batch(func: &mut Function, rewrites: &HashMap<ValueId, ValueId>) {
61 let r = |v: &mut ValueId| {
62 if let Some(&new) = rewrites.get(v) {
63 *v = new;
64 }
65 };
66 for block in &mut func.blocks {
67 for inst in &mut block.insts {
68 for_each_operand_mut(&mut inst.kind, r);
69 }
70 if let Some(term) = &mut block.terminator {
71 for_each_terminator_operand_mut(term, r);
72 }
73 }
74 }
75
76 /// Look up the constant integer value behind a `ValueId`, if any.
77 fn const_int(consts: &HashMap<ValueId, (i64, IntWidth)>, id: ValueId) -> Option<(i64, IntWidth)> {
78 consts.get(&id).copied()
79 }
80
81 /// Collect integer constants in canonical form (sign-extended at
82 /// their declared width). Audit m4-1: this mirrors the N-9 / M4-4
83 /// normalization in `const_fold` and `const_prop`. The previous
84 /// version stored raw bit patterns, so `rewrite_for` had to re-sext
85 /// at use-site — and did so using the *result* type's width, not the
86 /// operand's, producing conservative no-ops on width-drifted inputs.
87 fn collect_int_consts(func: &Function) -> HashMap<ValueId, (i64, IntWidth)> {
88 let mut consts = HashMap::new();
89 for block in &func.blocks {
90 for inst in &block.insts {
91 if let InstKind::ConstInt(v, w) = inst.kind {
92 if let Ok(v) = i64::try_from(v) {
93 consts.insert(inst.id, (sext(v, w.bits()), w));
94 }
95 }
96 }
97 }
98 consts
99 }
100
101 /// Sign-extend a stored i64 to its declared bit width. Used so that
102 /// "is this -1?" checks survive narrow types.
103 fn sext(v: i64, bits: u32) -> i64 {
104 if bits >= 64 {
105 v
106 } else {
107 let shift = 64 - bits;
108 (v << shift) >> shift
109 }
110 }
111
112 /// Power-of-two test that returns the exponent. Returns `Some(k)` only
113 /// when `v == 1 << k` and `k > 0`. (`v == 1` is handled separately as
114 /// "multiply by 1 → identity".)
115 fn pow2_exponent(v: i64) -> Option<u32> {
116 if v <= 0 {
117 return None;
118 }
119 let u = v as u64;
120 if u.is_power_of_two() && u != 1 {
121 Some(u.trailing_zeros())
122 } else {
123 None
124 }
125 }
126
127 /// One rewrite directive describing how a single instruction should be
128 /// replaced. Computed during the read-only walk and applied afterwards
129 /// so we don't fight the borrow checker.
130 enum Rewrite {
131 /// Replace this instruction's `kind` with a new one.
132 KindOnly(InstKind),
133 /// Replace this instruction's `kind` AND retarget every use of the
134 /// old result `ValueId` to `pass_through`. Used for identity ops.
135 Identity { pass_through: ValueId },
136 /// Rewrite as `shl var, const(k)`. The apply phase synthesizes a
137 /// fresh `ConstInt(k)` instruction and inserts it directly before
138 /// the rewritten instruction.
139 ShlByConst { var: ValueId, k: u32 },
140 }
141
142 /// Try to derive a strength-reduction rewrite for one instruction.
143 /// Returns `None` if no rewrite applies.
144 fn rewrite_for(inst: &Inst, consts: &HashMap<ValueId, (i64, IntWidth)>) -> Option<Rewrite> {
145 let int_w = if let IrType::Int(w) = inst.ty {
146 w
147 } else {
148 return None;
149 };
150 // Audit m4-1: after the M4-4 normalization in `collect_int_consts`,
151 // every `kv` returned from `const_int` is already sign-extended
152 // at its source width. The earlier code re-sext'd at `int_w` (the
153 // *result* width), which was redundant at best and actively
154 // harmful when an operand's source width differed from the result
155 // width (the re-sext would truncate a wider value to a narrower
156 // signed range, making e.g. `imul x, 2000000000` at result-type
157 // i8 look like `imul x, 0` and spuriously rewrite to const(0)).
158 // Now we just compare against the canonical i64-stored value.
159
160 match &inst.kind {
161 // ---- imul -------------------------------------------------------
162 InstKind::IMul(a, b) => {
163 let var = if let Some((kv, _)) = const_int(consts, *b) {
164 Some((*a, kv))
165 } else if let Some((kv, _)) = const_int(consts, *a) {
166 Some((*b, kv))
167 } else {
168 None
169 };
170 if let Some((var_id, kv)) = var {
171 if kv == 0 {
172 return Some(Rewrite::KindOnly(InstKind::ConstInt(0, int_w)));
173 }
174 if kv == 1 {
175 return Some(Rewrite::Identity {
176 pass_through: var_id,
177 });
178 }
179 if kv == -1 {
180 return Some(Rewrite::KindOnly(InstKind::INeg(var_id)));
181 }
182 if let Some(k) = pow2_exponent(kv) {
183 return Some(Rewrite::ShlByConst { var: var_id, k });
184 }
185 }
186 None
187 }
188 // ---- iadd -------------------------------------------------------
189 InstKind::IAdd(a, b) => {
190 if let Some((0, _)) = const_int(consts, *b) {
191 return Some(Rewrite::Identity { pass_through: *a });
192 }
193 if let Some((0, _)) = const_int(consts, *a) {
194 return Some(Rewrite::Identity { pass_through: *b });
195 }
196 None
197 }
198 // ---- isub -------------------------------------------------------
199 InstKind::ISub(a, b) => {
200 if let Some((0, _)) = const_int(consts, *b) {
201 return Some(Rewrite::Identity { pass_through: *a });
202 }
203 if let Some((0, _)) = const_int(consts, *a) {
204 return Some(Rewrite::KindOnly(InstKind::INeg(*b)));
205 }
206 None
207 }
208 // ---- idiv -------------------------------------------------------
209 InstKind::IDiv(a, b) => {
210 if let Some((kv, _)) = const_int(consts, *b) {
211 if kv == 1 {
212 return Some(Rewrite::Identity { pass_through: *a });
213 }
214 if kv == -1 {
215 return Some(Rewrite::KindOnly(InstKind::INeg(*a)));
216 }
217 // Power-of-two signed division skipped (see module doc).
218 }
219 None
220 }
221 // ---- bit_and ---------------------------------------------------
222 InstKind::BitAnd(a, b) => {
223 if let Some((kv, _)) = const_int(consts, *b) {
224 if kv == 0 {
225 return Some(Rewrite::KindOnly(InstKind::ConstInt(0, int_w)));
226 }
227 if kv == -1 {
228 return Some(Rewrite::Identity { pass_through: *a });
229 }
230 }
231 if let Some((kv, _)) = const_int(consts, *a) {
232 if kv == 0 {
233 return Some(Rewrite::KindOnly(InstKind::ConstInt(0, int_w)));
234 }
235 if kv == -1 {
236 return Some(Rewrite::Identity { pass_through: *b });
237 }
238 }
239 None
240 }
241 // ---- bit_or ----------------------------------------------------
242 InstKind::BitOr(a, b) => {
243 if let Some((kv, _)) = const_int(consts, *b) {
244 if kv == 0 {
245 return Some(Rewrite::Identity { pass_through: *a });
246 }
247 if kv == -1 {
248 return Some(Rewrite::KindOnly(InstKind::ConstInt(-1, int_w)));
249 }
250 }
251 if let Some((kv, _)) = const_int(consts, *a) {
252 if kv == 0 {
253 return Some(Rewrite::Identity { pass_through: *b });
254 }
255 if kv == -1 {
256 return Some(Rewrite::KindOnly(InstKind::ConstInt(-1, int_w)));
257 }
258 }
259 None
260 }
261 // ---- bit_xor ---------------------------------------------------
262 InstKind::BitXor(a, b) => {
263 if let Some((0, _)) = const_int(consts, *b) {
264 return Some(Rewrite::Identity { pass_through: *a });
265 }
266 if let Some((0, _)) = const_int(consts, *a) {
267 return Some(Rewrite::Identity { pass_through: *b });
268 }
269 None
270 }
271 // ---- shifts ----------------------------------------------------
272 InstKind::Shl(a, b) | InstKind::LShr(a, b) | InstKind::AShr(a, b) => {
273 if let Some((kv, _)) = const_int(consts, *b) {
274 if kv == 0 {
275 return Some(Rewrite::Identity { pass_through: *a });
276 }
277 }
278 None
279 }
280 _ => None,
281 }
282 }
283
284 pub struct StrengthReduce;
285
286 impl Pass for StrengthReduce {
287 fn name(&self) -> &'static str {
288 "strength-reduce"
289 }
290
291 fn run(&self, module: &mut Module) -> bool {
292 let mut changed = false;
293 for func in &mut module.functions {
294 // Snapshot constants once per function. Strength reduction
295 // never produces a *new* constant operand whose appearance
296 // would unlock further reductions on the same pass; if it
297 // did, the manager would re-run us anyway.
298 let consts = collect_int_consts(func);
299
300 // First pass: collect (block_idx, inst_idx, rewrite) tuples.
301 let mut rewrites: Vec<(usize, usize, Rewrite, ValueId, IrType)> = Vec::new();
302 for (bi, block) in func.blocks.iter().enumerate() {
303 for (ii, inst) in block.insts.iter().enumerate() {
304 if let Some(rw) = rewrite_for(inst, &consts) {
305 rewrites.push((bi, ii, rw, inst.id, inst.ty.clone()));
306 }
307 }
308 }
309 if rewrites.is_empty() {
310 continue;
311 }
312
313 // Second pass: apply rewrites. We process them in reverse
314 // block-then-instruction order so that inserting a new
315 // const instruction at index `ii` doesn't shift the
316 // indices of any earlier rewrite still pending.
317 rewrites.sort_by_key(|entry| std::cmp::Reverse((entry.0, entry.1)));
318
319 // Audit B-2: collect all Identity rewrites into a single
320 // rename map and apply them in one function walk at the
321 // end, instead of calling `substitute_uses` once per
322 // Identity rewrite (which would re-walk the function for
323 // each rewrite). The CSE pass got the same treatment in
324 // the Min-1 fix; this finally closes the parallel.
325 let mut identity_rewrites: HashMap<ValueId, ValueId> = HashMap::new();
326
327 for (bi, ii, rw, old_id, ty) in rewrites {
328 match rw {
329 Rewrite::ShlByConst { var, k } => {
330 let int_w = if let IrType::Int(w) = ty {
331 w
332 } else {
333 unreachable!()
334 };
335 let kid = func.next_value_id();
336 let span = func.blocks[bi].insts[ii].span;
337 func.blocks[bi].insts.insert(
338 ii,
339 Inst {
340 id: kid,
341 kind: InstKind::ConstInt(k as i128, int_w),
342 ty: IrType::Int(int_w),
343 span,
344 },
345 );
346 // The original instruction shifted to ii+1.
347 func.blocks[bi].insts[ii + 1].kind = InstKind::Shl(var, kid);
348 }
349 Rewrite::KindOnly(new_kind) => {
350 func.blocks[bi].insts[ii].kind = new_kind;
351 }
352 Rewrite::Identity { pass_through } => {
353 // Defer the substitution. Record the
354 // (old_id → pass_through) pair and apply all
355 // such pairs in one walk after this loop.
356 // The orphan instruction is left alone so
357 // dominance still holds; DCE removes it on
358 // the next sweep.
359 //
360 // Audit M-A1 / C-A: the original code wrote a
361 // placeholder `Const(0)` here, then `continue`d
362 // for non-int/bool types — silently swallowing
363 // `changed = true` after mutating the function.
364 // The structural fix removed the placeholder.
365 identity_rewrites.insert(old_id, pass_through);
366 let _ = ty; // intentionally unused
367 }
368 }
369 changed = true;
370 }
371
372 // Apply all Identity rewrites in a single function walk.
373 // Chase pointer chains so each entry maps directly to its
374 // terminal target — strength_reduce can produce chains
375 // like `((x*1)*1)+0` where each Identity points to the
376 // result of an earlier Identity.
377 //
378 // Audit N-7: termination is guaranteed today by the SSA
379 // invariant that every Identity rewrite points to a
380 // value with a strictly smaller `ValueId` (operands are
381 // defined before their uses). But that invariant isn't
382 // enforced anywhere — a future pass could in principle
383 // produce a cycle. We defend with a `HashSet<ValueId>`
384 // visited guard that breaks the chase on the second
385 // visit of any node, so a bug-introduced cycle becomes
386 // "this rewrite is skipped" rather than "compiler hangs
387 // in an infinite loop."
388 if !identity_rewrites.is_empty() {
389 let keys: Vec<ValueId> = identity_rewrites.keys().copied().collect();
390 for k in keys {
391 let mut cur = identity_rewrites[&k];
392 let mut visited: std::collections::HashSet<ValueId> =
393 std::collections::HashSet::new();
394 visited.insert(k);
395 while let Some(&next) = identity_rewrites.get(&cur) {
396 if !visited.insert(next) {
397 break;
398 }
399 cur = next;
400 }
401 identity_rewrites.insert(k, cur);
402 }
403 substitute_uses_batch(func, &identity_rewrites);
404 }
405 }
406 changed
407 }
408 }
409
410 #[cfg(test)]
411 mod tests {
412 use super::*;
413 use crate::ir::types::IrType;
414 use crate::lexer::{Position, Span};
415
416 fn dummy_span() -> Span {
417 let p = Position { line: 1, col: 1 };
418 Span {
419 start: p,
420 end: p,
421 file_id: 0,
422 }
423 }
424
425 fn push(f: &mut Function, kind: InstKind, ty: IrType) -> ValueId {
426 let id = f.next_value_id();
427 let entry = f.entry;
428 f.block_mut(entry).insts.push(Inst {
429 id,
430 kind,
431 ty,
432 span: dummy_span(),
433 });
434 id
435 }
436
437 fn make_fn() -> (Module, Function) {
438 let m = Module::new("t".into());
439 let f = Function::new("f".into(), vec![], IrType::Void);
440 (m, f)
441 }
442
443 #[test]
444 fn imul_by_pow2_becomes_shl() {
445 let (mut m, mut f) = make_fn();
446 let x = push(
447 &mut f,
448 InstKind::ConstInt(7, IntWidth::I32),
449 IrType::Int(IntWidth::I32),
450 );
451 let k = push(
452 &mut f,
453 InstKind::ConstInt(8, IntWidth::I32),
454 IrType::Int(IntWidth::I32),
455 );
456 let y = push(&mut f, InstKind::IMul(x, k), IrType::Int(IntWidth::I32));
457 let entry = f.entry;
458 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
459 m.add_function(f);
460
461 assert!(StrengthReduce.run(&mut m));
462 // The Shl should reference x and a freshly inserted const(3).
463 let block = &m.functions[0].blocks[0];
464 let shl = block
465 .insts
466 .iter()
467 .find_map(|i| match &i.kind {
468 InstKind::Shl(var, kid) => Some((*var, *kid)),
469 _ => None,
470 })
471 .expect("expected Shl");
472 assert_eq!(shl.0, x);
473 // The shift count should be a const(3).
474 let kconst = block
475 .insts
476 .iter()
477 .find(|i| i.id == shl.1)
478 .expect("shift const");
479 assert!(matches!(kconst.kind, InstKind::ConstInt(3, IntWidth::I32)));
480 }
481
482 #[test]
483 fn imul_by_zero_becomes_const_zero() {
484 let (mut m, mut f) = make_fn();
485 let x = push(
486 &mut f,
487 InstKind::ConstInt(7, IntWidth::I32),
488 IrType::Int(IntWidth::I32),
489 );
490 let z = push(
491 &mut f,
492 InstKind::ConstInt(0, IntWidth::I32),
493 IrType::Int(IntWidth::I32),
494 );
495 let y = push(&mut f, InstKind::IMul(x, z), IrType::Int(IntWidth::I32));
496 let entry = f.entry;
497 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
498 m.add_function(f);
499
500 assert!(StrengthReduce.run(&mut m));
501 // y now defines const(0)
502 let y_inst = m.functions[0].blocks[0]
503 .insts
504 .iter()
505 .find(|i| i.id == y)
506 .unwrap();
507 assert!(matches!(y_inst.kind, InstKind::ConstInt(0, IntWidth::I32)));
508 }
509
510 #[test]
511 fn imul_by_one_becomes_identity() {
512 let (mut m, mut f) = make_fn();
513 let x = push(
514 &mut f,
515 InstKind::ConstInt(7, IntWidth::I32),
516 IrType::Int(IntWidth::I32),
517 );
518 let one = push(
519 &mut f,
520 InstKind::ConstInt(1, IntWidth::I32),
521 IrType::Int(IntWidth::I32),
522 );
523 let y = push(&mut f, InstKind::IMul(x, one), IrType::Int(IntWidth::I32));
524 let entry = f.entry;
525 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
526 m.add_function(f);
527
528 assert!(StrengthReduce.run(&mut m));
529 // Return now uses x directly.
530 match &m.functions[0].blocks[0].terminator {
531 Some(Terminator::Return(Some(v))) => assert_eq!(*v, x),
532 other => panic!("expected Return(x), got {:?}", other),
533 }
534 }
535
536 #[test]
537 fn imul_by_neg_one_becomes_neg() {
538 let (mut m, mut f) = make_fn();
539 let x = push(
540 &mut f,
541 InstKind::ConstInt(7, IntWidth::I32),
542 IrType::Int(IntWidth::I32),
543 );
544 let neg = push(
545 &mut f,
546 InstKind::ConstInt(-1, IntWidth::I32),
547 IrType::Int(IntWidth::I32),
548 );
549 let y = push(&mut f, InstKind::IMul(x, neg), IrType::Int(IntWidth::I32));
550 let entry = f.entry;
551 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
552 m.add_function(f);
553
554 assert!(StrengthReduce.run(&mut m));
555 let y_inst = m.functions[0].blocks[0]
556 .insts
557 .iter()
558 .find(|i| i.id == y)
559 .unwrap();
560 match y_inst.kind {
561 InstKind::INeg(v) => assert_eq!(v, x),
562 ref other => panic!("expected INeg, got {:?}", other),
563 }
564 }
565
566 #[test]
567 fn iadd_zero_becomes_identity() {
568 let (mut m, mut f) = make_fn();
569 let x = push(
570 &mut f,
571 InstKind::ConstInt(42, IntWidth::I32),
572 IrType::Int(IntWidth::I32),
573 );
574 let z = push(
575 &mut f,
576 InstKind::ConstInt(0, IntWidth::I32),
577 IrType::Int(IntWidth::I32),
578 );
579 let y = push(&mut f, InstKind::IAdd(x, z), IrType::Int(IntWidth::I32));
580 let entry = f.entry;
581 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
582 m.add_function(f);
583
584 assert!(StrengthReduce.run(&mut m));
585 match &m.functions[0].blocks[0].terminator {
586 Some(Terminator::Return(Some(v))) => assert_eq!(*v, x),
587 _ => panic!(),
588 }
589 }
590
591 #[test]
592 fn isub_from_zero_becomes_neg() {
593 let (mut m, mut f) = make_fn();
594 let z = push(
595 &mut f,
596 InstKind::ConstInt(0, IntWidth::I32),
597 IrType::Int(IntWidth::I32),
598 );
599 let x = push(
600 &mut f,
601 InstKind::ConstInt(42, IntWidth::I32),
602 IrType::Int(IntWidth::I32),
603 );
604 let y = push(&mut f, InstKind::ISub(z, x), IrType::Int(IntWidth::I32));
605 let entry = f.entry;
606 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
607 m.add_function(f);
608
609 assert!(StrengthReduce.run(&mut m));
610 let y_inst = m.functions[0].blocks[0]
611 .insts
612 .iter()
613 .find(|i| i.id == y)
614 .unwrap();
615 match y_inst.kind {
616 InstKind::INeg(v) => assert_eq!(v, x),
617 _ => panic!(),
618 }
619 }
620
621 #[test]
622 fn idiv_by_one_becomes_identity() {
623 let (mut m, mut f) = make_fn();
624 let x = push(
625 &mut f,
626 InstKind::ConstInt(42, IntWidth::I32),
627 IrType::Int(IntWidth::I32),
628 );
629 let one = push(
630 &mut f,
631 InstKind::ConstInt(1, IntWidth::I32),
632 IrType::Int(IntWidth::I32),
633 );
634 let y = push(&mut f, InstKind::IDiv(x, one), IrType::Int(IntWidth::I32));
635 let entry = f.entry;
636 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
637 m.add_function(f);
638
639 assert!(StrengthReduce.run(&mut m));
640 match &m.functions[0].blocks[0].terminator {
641 Some(Terminator::Return(Some(v))) => assert_eq!(*v, x),
642 _ => panic!(),
643 }
644 }
645
646 #[test]
647 fn band_with_neg_one_becomes_identity() {
648 let (mut m, mut f) = make_fn();
649 let x = push(
650 &mut f,
651 InstKind::ConstInt(42, IntWidth::I32),
652 IrType::Int(IntWidth::I32),
653 );
654 let mask = push(
655 &mut f,
656 InstKind::ConstInt(-1, IntWidth::I32),
657 IrType::Int(IntWidth::I32),
658 );
659 let y = push(
660 &mut f,
661 InstKind::BitAnd(x, mask),
662 IrType::Int(IntWidth::I32),
663 );
664 let entry = f.entry;
665 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
666 m.add_function(f);
667
668 assert!(StrengthReduce.run(&mut m));
669 match &m.functions[0].blocks[0].terminator {
670 Some(Terminator::Return(Some(v))) => assert_eq!(*v, x),
671 _ => panic!(),
672 }
673 }
674
675 #[test]
676 fn band_with_zero_becomes_const_zero() {
677 let (mut m, mut f) = make_fn();
678 let x = push(
679 &mut f,
680 InstKind::ConstInt(42, IntWidth::I32),
681 IrType::Int(IntWidth::I32),
682 );
683 let z = push(
684 &mut f,
685 InstKind::ConstInt(0, IntWidth::I32),
686 IrType::Int(IntWidth::I32),
687 );
688 let y = push(&mut f, InstKind::BitAnd(x, z), IrType::Int(IntWidth::I32));
689 let entry = f.entry;
690 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
691 m.add_function(f);
692
693 assert!(StrengthReduce.run(&mut m));
694 let y_inst = m.functions[0].blocks[0]
695 .insts
696 .iter()
697 .find(|i| i.id == y)
698 .unwrap();
699 assert!(matches!(y_inst.kind, InstKind::ConstInt(0, IntWidth::I32)));
700 }
701
702 #[test]
703 fn shift_by_zero_becomes_identity() {
704 let (mut m, mut f) = make_fn();
705 let x = push(
706 &mut f,
707 InstKind::ConstInt(42, IntWidth::I32),
708 IrType::Int(IntWidth::I32),
709 );
710 let z = push(
711 &mut f,
712 InstKind::ConstInt(0, IntWidth::I32),
713 IrType::Int(IntWidth::I32),
714 );
715 let y = push(&mut f, InstKind::Shl(x, z), IrType::Int(IntWidth::I32));
716 let entry = f.entry;
717 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
718 m.add_function(f);
719
720 assert!(StrengthReduce.run(&mut m));
721 match &m.functions[0].blocks[0].terminator {
722 Some(Terminator::Return(Some(v))) => assert_eq!(*v, x),
723 _ => panic!(),
724 }
725 }
726
727 #[test]
728 fn no_rewrite_when_no_constant_operand() {
729 // imul of two non-const values: should not change.
730 let mut m = Module::new("t".into());
731 let params = vec![
732 Param {
733 name: "a".into(),
734 ty: IrType::Int(IntWidth::I32),
735 id: ValueId(0),
736 fortran_noalias: false,
737 },
738 Param {
739 name: "b".into(),
740 ty: IrType::Int(IntWidth::I32),
741 id: ValueId(1),
742 fortran_noalias: false,
743 },
744 ];
745 let mut f = Function::new("f".into(), params, IrType::Int(IntWidth::I32));
746 let y = push(
747 &mut f,
748 InstKind::IMul(ValueId(0), ValueId(1)),
749 IrType::Int(IntWidth::I32),
750 );
751 let entry = f.entry;
752 f.block_mut(entry).terminator = Some(Terminator::Return(Some(y)));
753 m.add_function(f);
754
755 assert!(!StrengthReduce.run(&mut m));
756 }
757 }
758