Skip to main content

rucc_opt/
narrow.rs

1//! Width narrowing: arithmetic redone at the width the program actually uses.
2//!
3//! The lowering rule set is written at an opcode and a width together, so `add.i8` and `add.i32`
4//! are two rules and the machine can be asked to add two bytes as easily as two words. C never
5//! asks it to. The integer promotions say the operands of an arithmetic operator go to `int`
6//! first, so `char a, b; a + b` is an `int` addition of two sign extended bytes, and the front end
7//! is right to write it that way because that is what the language says the expression means.
8//!
9//! That leaves the promoted form as the only form, and on x86-64 it is often the wrong one. A
10//! byte compare against a byte is a `cmpb`, and two `movsbl` are not needed to reach it. A byte
11//! add whose result is stored back into a `char` throws away every bit the promotion computed.
12//! The promoted shape exists because C says so and not because the machine wants it. This is
13//! issue 375.
14//!
15//! # The three shapes
16//!
17//! A truncation of arithmetic. The low bits of a sum, a difference, a product, a bitwise
18//! operation or a shift by a constant depend only on the low bits of what went into it, so
19//! `trunc.i8 (add.i32 (sext a) (sext b))` is `add.i8 a b` and the two extensions are left with
20//! nothing reading them. That is the arithmetic half, and it is what `char c = a + b;` is.
21//!
22//! A comparison of extensions. Sign extension is an order isomorphism onto its image under both
23//! readings of the bits, so a comparison of two of them at any predicate is the same comparison of
24//! what they extended. That is what `char a, b; a < b` is. Zero extension is an isomorphism under
25//! the unsigned reading and is not one under the signed reading, since it takes a negative byte to
26//! a positive word, so the equalities and the unsigned predicates come over as they are. A signed
27//! predicate comes over as its unsigned counterpart, because what a zero extension produces has
28//! its top bits clear and the two readings agree on a value like that. That is what `unsigned char
29//! a, b; a < b` is, and the promotions make it the shape most C at these widths has.
30//!
31//! Both are written so that one side may be a constant instead, because `if (c == 'x')` is the
32//! common case and the constant is representable at the narrow width whenever the comparison is
33//! not already decided.
34//!
35//! A bitwise operation on widened bits. That narrows all the way to one bit, which the other two
36//! shapes stop short of on purpose. `and`, `or` and `xor` work a bit at a time, so over two values
37//! a zero extension from one bit produced, which are zero or one and nothing else, the wide result
38//! is zero or one as well and the whole of it is its own bottom bit. That bit is the operation done
39//! on the two bits themselves. Two things ask for it. A comparison against zero at the `ne`
40//! predicate wants it as a truth, which is what `_Bool r = p & q;` is, the comparison rather than a
41//! truncation being the standard speaking: a conversion to `_Bool` gives zero or one according to
42//! whether the value compares equal to zero. An extension wants it back as a number of its own
43//! width, which is what `(long long)(p & q)` is, and since the bits are zero or one a sign
44//! extension of them and a zero extension of them are the same value. This is the shape that gives
45//! the one bit rewrite rules something to match, which is `tamnd/rucc#518`. Here too one side may
46//! be a constant, and here a constant is a bit when it is zero or one.
47//!
48//! # Why it always pays
49//!
50//! No shape is applied unless every leaf it reaches narrows for nothing. A leaf is what an
51//! extension extended, which is already the narrow value, or a constant, which is written down
52//! again. So the rewrite replaces a wide operation, its extensions and the truncation with one
53//! narrow operation and never leaves a widening behind to pay for a narrowing. Everything in
54//! between is required to have exactly one reader, which is the operation above it, so the whole
55//! subtree it replaces is dead the moment it is replaced.
56//!
57//! That is the whole profitability argument, and it is deliberately a structural one rather than
58//! a cost model. A pass whose payoff has to be estimated is a pass whose payoff can be wrong.
59//!
60//! # What it does not narrow
61//!
62//! Not a divide or a remainder. `char a = -128, b = -1; char c = a / b;` is well defined in C: the
63//! division happens at `int`, gives 128, and the conversion back to `char` is what makes it minus
64//! 128 again. The same division at one byte is the overflow case that raises on this machine, so
65//! narrowing it turns a program that works into a program that dies. It needs a range that says
66//! the operands miss that one pair, and ranges are the analysis this pass does not have.
67//!
68//! Not a shift by a value. `char c; c <<= n;` shifts at `int`, so a count of twenty is a defined
69//! shift whose low eight bits are zero, and the same count at one byte is poison. A shift by a
70//! constant below the narrow width has neither problem and is narrowed.
71//!
72//! Not a signed operation's overflow flags. A sum that could not overflow at four bytes can
73//! overflow at one, so `nsw` and `nuw` do not come along. Dropping them is a refinement in the
74//! safe direction: it makes the operation more defined rather than less.
75//!
76//! # What is left for the analysis
77//!
78//! The width here is the one the truncation names. A real demanded bits analysis would let it
79//! shrink further, so that `(x & 0xff) + 1` narrows on the strength of the mask rather than on the
80//! strength of a truncation that is not written, and so that a value read at three widths is
81//! narrowed to the widest of them rather than to none. That is the first box of issue 375 and it
82//! wants the analysis manager, which wants the dominator tree, which is the next thing to build.
83
84use rucc_ir::{
85    Block, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value, ValueList,
86};
87
88use crate::uses::count;
89use crate::{Analyses, Analysis, Fuel, Pass, Preserved, Stats};
90
91/// Recorded once for each subtree redone at the narrow width.
92const NARROWED: &str = "arithmetic redone at the width the program truncates it to";
93
94/// Recorded for a subtree that would have been redone if there had been fuel for it.
95const NO_FUEL: &str = "arithmetic left wide, the pass ran out of fuel";
96
97/// How deep the walk from a truncation goes before it gives up.
98///
99/// A chain of arithmetic is as long as the expression somebody wrote, and generated C writes long
100/// ones, so a walk with no limit is a stack overflow waiting for the right input file. Six is
101/// deeper than hand written C reaches and shallow enough that the recursion cannot cost anything,
102/// and an expression deeper than this narrows from whatever truncation is nearer to its leaves.
103const DEPTH: u32 = 6;
104
105/// The pass. It holds nothing, because the width it narrows to is the one the truncation names.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub struct Narrow;
108
109impl Pass for Narrow {
110    fn name(&self) -> &'static str {
111        "narrow"
112    }
113
114    fn describe(&self) -> &'static str {
115        "arithmetic the program truncates is redone at the width it truncates to"
116    }
117
118    fn preserves(&self) -> Preserved {
119        // The arithmetic is redone at another width in the block it was already in. Widths are
120        // not something the graph, the trees or the forest have an opinion about. Liveness is
121        // another matter: the narrow arithmetic is new values, and the wide values it was
122        // written from are read in one fewer place or in none.
123        Preserved::ALL.without(Analysis::Liveness)
124    }
125
126    fn run(&self, func: &mut Func, _an: &mut Analyses, fuel: &mut Fuel) -> Stats {
127        let mut stats = Stats::new();
128        let mut uses = count(func);
129        for block in func.blocks().collect::<Vec<Block>>() {
130            for inst in func.insts(block).collect::<Vec<Inst>>() {
131                let Some(redo) = truncated_arithmetic(func, inst, &uses)
132                    .or_else(|| extended_comparison(func, inst))
133                    .or_else(|| widened_bits(func, inst, &uses))
134                else {
135                    continue;
136                };
137                if !fuel.take() {
138                    // Out of fuel, which stops the transforming rather than the looking, the
139                    // same way the other three passes treat it. The walk is the same walk at
140                    // every fuel setting, which is what makes bisecting over it monotonic.
141                    stats.missed(NO_FUEL);
142                    continue;
143                }
144                apply(func, inst, &redo, &mut uses);
145                stats.optimized(NARROWED);
146            }
147        }
148        stats
149    }
150}
151
152/// An instruction rewritten at the narrow width, with its operands narrowed too.
153struct Redo {
154    /// What the instruction becomes, which is the wide operation at the narrow width.
155    opcode: Opcode,
156    /// The predicate, for a comparison, and nothing for arithmetic.
157    extra: Extra,
158    /// The width everything under this is redone at.
159    ty: Type,
160    /// The left operand, or the only one when the instruction written takes one.
161    lhs: Plan,
162    /// The right operand, and nothing when the instruction written takes one.
163    rhs: Option<Plan>,
164}
165
166/// What an operand becomes at the narrow width.
167enum Plan {
168    /// A value that already has it, which is what an extension was extending.
169    Already(Value),
170    /// A constant, written down again at the narrow width.
171    Constant(i128),
172    /// An operation redone, which is the recursive case and the reason this is a tree.
173    Nested(Box<Redo>),
174}
175
176/// Whether this is a truncation of arithmetic that can be redone narrow, and what it becomes.
177///
178/// The truncation is the root because it is the only place the narrow width is written down. Its
179/// operand has to be read by nothing else, since a second reader would keep the wide operation
180/// alive and the rewrite would be a second instruction rather than a replacement.
181fn truncated_arithmetic(func: &Func, inst: Inst, uses: &[u32]) -> Option<Redo> {
182    let data = &func[inst];
183    if data.opcode != Opcode::Trunc {
184        return None;
185    }
186    let ty = func[data.results().next()?].ty;
187    if !narrowable(ty) {
188        return None;
189    }
190    redo(func, *func[data.args].first()?, ty, uses, DEPTH)
191}
192
193/// Whether a width is one this pass will redo an operation at.
194///
195/// An integer scalar of a byte or more. The lower bound is the interesting half. One bit is an
196/// integer type in the IR and a comparison against a zero extended truth is a comparison the
197/// argument narrows all the way down to it, and `spec/12-instruction-selection.md` says a one bit
198/// value is a truth rather than a width: `tamnd/rucc#352` is the list of what a target lowers at
199/// that width and it is `and`, `or`, `xor`, a constant and the widening out of one. Narrowing an
200/// `icmp` into it would be asking every target for something no target has, so the floor is the
201/// narrowest width a machine holds a number in.
202///
203/// That list is also why `widened_bits` is allowed below the floor and asks this nothing. What it
204/// writes is one of the three operations the list has, at the one width they are on it for.
205const fn narrowable(ty: Type) -> bool {
206    ty.is_int() && ty.is_scalar() && ty.bits() >= 8
207}
208
209/// Whether this value is arithmetic that can be redone at that width, and what it becomes.
210fn redo(func: &Func, value: Value, ty: Type, uses: &[u32], depth: u32) -> Option<Redo> {
211    if depth == 0 || uses[value.index()] != 1 {
212        return None;
213    }
214    let Def::Result { inst, .. } = func[value].def else { return None };
215    let data = &func[inst];
216    if !low_bits_only(data.opcode) {
217        return None;
218    }
219    let args = &func[data.args];
220    let (&left, &right) = (args.first()?, args.get(1)?);
221    let lhs = plan(func, left, ty, uses, depth)?;
222    // A shift is the one operation whose right operand is not a number of the same kind as its
223    // left one, and it is the one that is unsafe to narrow when that operand is not a constant.
224    let rhs = match data.opcode {
225        Opcode::Shl => Plan::Constant(count_below(func, right, ty)?),
226        _ => plan(func, right, ty, uses, depth)?,
227    };
228    Some(Redo { opcode: data.opcode, extra: Extra::None, ty, lhs, rhs: Some(rhs) })
229}
230
231/// What an operand becomes at that width, or `None` when it would cost something to get there.
232fn plan(func: &Func, value: Value, ty: Type, uses: &[u32], depth: u32) -> Option<Plan> {
233    if let Some(narrow) = extended(func, value, ty) {
234        return Some(Plan::Already(narrow));
235    }
236    if let Some((imm, wide)) = constant(func, value) {
237        return Some(Plan::Constant(imm.signed(wide)));
238    }
239    redo(func, value, ty, uses, depth - 1).map(|redo| Plan::Nested(Box::new(redo)))
240}
241
242/// Whether an operation's low bits depend only on the low bits of what went into it.
243///
244/// True of the four that carry left to right and of the three that work a bit at a time. Not true
245/// of a divide, a remainder or a shift right, all of which read bits above the ones they produce.
246const fn low_bits_only(opcode: Opcode) -> bool {
247    matches!(
248        opcode,
249        Opcode::Add
250            | Opcode::Sub
251            | Opcode::Mul
252            | Opcode::And
253            | Opcode::Or
254            | Opcode::Xor
255            | Opcode::Shl
256    )
257}
258
259/// Whether this is a comparison of two things extended from the same narrower width.
260///
261/// Sign extension keeps the order of what it extends under both readings of the bits, so every
262/// predicate survives it and the comparison narrows as it stands.
263///
264/// Zero extension keeps the unsigned order and not the signed one, since it takes a negative byte
265/// to a positive word. That does not stop a signed comparison of two of them narrowing: what a
266/// zero extension produces is a value with its top bits clear, the two readings of the bits agree
267/// on a value like that, and so the signed comparison is asking an unsigned question. It narrows
268/// to the unsigned predicate rather than to the one that was written. This is the shape the
269/// integer promotions give `unsigned char a, b; a < b`, which is a signed comparison of two zero
270/// extensions and is most of what C produces at these widths, so refusing it would leave the rule
271/// set's narrow half with nothing to match. The swap is asked for on an extension that widens,
272/// because one to the width it already has is the identity and the predicate written on it is the
273/// one that holds.
274///
275/// The two sides have to be the same extension as well as from the same width. `(signed char) a <
276/// b` where `b` is an `unsigned char` is a sign extension against a zero extension, and comparing
277/// what they extended is comparing a byte against a byte at one predicate where the wide
278/// comparison had a signed byte against an unsigned one. Both readings of the narrow comparison
279/// are wrong, and the wide comparison is right, which is the whole reason C promotes.
280fn extended_comparison(func: &Func, inst: Inst) -> Option<Redo> {
281    let data = &func[inst];
282    if data.opcode != Opcode::ICmp {
283        return None;
284    }
285    let Extra::IntPred(pred) = data.extra else { return None };
286    let args = &func[data.args];
287    let (&left, &right) = (args.first()?, args.get(1)?);
288    let (kind, ty, narrow) = widening(func, left)?;
289    if !narrowable(ty) {
290        return None;
291    }
292    let widens = ty.bits() < func[left].ty.bits();
293    let pred = if kind == Opcode::ZExt && widens { pred.unsigned() } else { pred };
294    let rhs = match widening(func, right) {
295        Some((same, from, other)) if same == kind && from == ty => Plan::Already(other),
296        _ => Plan::Constant(survives(func, right, kind, ty)?),
297    };
298    let extra = Extra::IntPred(pred);
299    Some(Redo { opcode: Opcode::ICmp, extra, ty, lhs: Plan::Already(narrow), rhs: Some(rhs) })
300}
301
302/// Whether this is something asking about a bitwise operation on widened bits, and what it
303/// becomes.
304///
305/// A value a zero extension from one bit produced is zero or one and nothing else, and `and`, `or`
306/// and `xor` of two such values are again zero or one, because each works a bit at a time and
307/// every bit above the bottom of both operands is clear. So the whole wide result is its own
308/// bottom bit, and that bit is the operation done on the two bits themselves.
309///
310/// One side may be a constant instead, the way it may in the other two shapes, and here it has to
311/// be zero or one, since that is what being a bit is.
312///
313/// This is the shape that gives the one bit rewrite rules a producer. Nothing in the front end
314/// emits an `and.i1`, so `tamnd/rucc#518` is thirteen rules that no program could reach, and the
315/// reason is that C has no way of writing one: every bitwise operator promotes its operands to
316/// `int` first. That makes it the one narrowing whose payoff is not in the instruction it saves.
317fn widened_bits(func: &Func, inst: Inst, uses: &[u32]) -> Option<Redo> {
318    let (wide, back) = asked(func, inst, uses)?;
319    let data = &func[wide];
320    if !bit_at_a_time(data.opcode) {
321        return None;
322    }
323    // The operation has to be wider than a bit, because this shape is a narrowing and an
324    // operation already at one bit has nowhere to go. Saying so is what stops the second of the
325    // two questions rewriting for ever: an extension stays an extension after the rewrite, so
326    // without this it would ask again about the one bit operation it was just given and write
327    // another one just like it every time the pass ran.
328    if func[data.results().next()?].ty.bits() <= 1 {
329        return None;
330    }
331    let args = &func[data.args];
332    let (&left, &right) = (args.first()?, args.get(1)?);
333    // An operand the operation reads twice is read twice by it and by nothing else, which is the
334    // same fact about the subtree as an operand it reads once being read by nothing else. `_Bool
335    // r = p & p;` is that shape, and it is one of the thirteen rules waiting for a producer.
336    let readers = if left == right { 2 } else { 1 };
337    let lhs = side(func, left, uses, readers)?;
338    let rhs = side(func, right, uses, readers)?;
339    // Two constants is arithmetic on two numbers, which the folder owns and answers outright.
340    // This shape is here to reach past a widening, and with nothing widened on either side there
341    // is nothing to reach past. `0u % 2u` is the case: the folder turns the remainder into an
342    // `and` against one before it turns the `and` into a number, and for that one moment the
343    // operation is two bits sitting next to each other with nothing behind them.
344    if matches!((&lhs, &rhs), (Plan::Constant(_), Plan::Constant(_))) {
345        return None;
346    }
347    let extra = Extra::None;
348    let bit = Redo { opcode: data.opcode, extra, ty: Type::int(1), lhs, rhs: Some(rhs) };
349    let Some(ty) = back else { return Some(bit) };
350    let lhs = Plan::Nested(Box::new(bit));
351    Some(Redo { opcode: Opcode::ZExt, extra, ty, lhs, rhs: None })
352}
353
354/// The wide operation an instruction is asking about, and the width the answer is wanted at.
355///
356/// Two instructions ask. A comparison against zero at the `ne` predicate wants the answer as a
357/// truth, so the width it is wanted at is the one bit the operation is redone at and there is
358/// nothing to say. `_Bool r = p & q;` is that: C computes the `and` at `int` because the
359/// promotions say so, and the conversion of the result back to `_Bool` is a comparison against
360/// zero rather than a truncation, because the standard says a conversion to `_Bool` gives zero or
361/// one according to whether the value compares equal to zero.
362///
363/// Only the `ne` predicate. Asking whether the wide result is zero is the negation of this, and a
364/// negation is a second instruction where every other shape here writes one.
365///
366/// An extension wants the answer back at its own width, which is the shape `(long long)(p & q)`
367/// and every other use of the result as a number wider than the `int` the promotions computed it
368/// at. The bits are zero or one either way, so a sign extension of them is the same value as a
369/// zero extension of them and both come out as a zero extension from the one bit. That is the pass
370/// writing an opcode other than the one it read, which it otherwise refuses to do, and it is
371/// allowed here because the operation being rewritten is the extension rather than the bitwise
372/// operation, and what an extension does is decided by what it extends.
373fn asked(func: &Func, inst: Inst, uses: &[u32]) -> Option<(Inst, Option<Type>)> {
374    let data = &func[inst];
375    let args = &func[data.args];
376    match data.opcode {
377        Opcode::ICmp if data.extra == Extra::IntPred(IntPred::Ne) => {
378            let (&left, &right) = (args.first()?, args.get(1)?);
379            let (zero, wide) = constant(func, right)?;
380            (zero.signed(wide) == 0).then_some((read_by(func, left, uses, 1)?, None))
381        }
382        Opcode::ZExt | Opcode::SExt => {
383            let ty = func[data.results().next()?].ty;
384            Some((read_by(func, *args.first()?, uses, 1)?, Some(ty)))
385        }
386        _ => None,
387    }
388}
389
390/// What one operand of that operation is at one bit, or nothing when it is not a bit.
391///
392/// A constant is a bit when it is zero or one, and a constant with anything set above the bottom
393/// bit is refused for the reason the whole rewrite rests on: the wide result would then be able to
394/// come out nonzero with its bottom bit clear, and the nonzero question would be asking about bits
395/// the narrow operation does not have. How many readers the constant has is not asked, because a
396/// constant is written down again rather than kept alive.
397fn side(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Plan> {
398    if let Some((imm, wide)) = constant(func, value) {
399        let k = imm.signed(wide);
400        return (k == 0 || k == 1).then_some(Plan::Constant(k));
401    }
402    Some(Plan::Already(widened_bit(func, value, uses, readers)?))
403}
404
405/// The instruction that computed this value, when the readers it has are the ones expected.
406///
407/// A reader beyond those keeps the wide subtree alive, and then the rewrite is an instruction
408/// added rather than a subtree replaced, which is the one thing the profitability argument here
409/// does not allow.
410fn read_by(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Inst> {
411    if uses[value.index()] != readers {
412        return None;
413    }
414    let Def::Result { inst, .. } = func[value].def else { return None };
415    Some(inst)
416}
417
418/// Whether an operation works a bit at a time, so that its result at one bit is its result over
419/// the bottom bit of what went in.
420///
421/// The three that do. An `add` of two widened bits is nonzero exactly when their `or` is and a
422/// `mul` of two exactly when their `and` is, and neither is here, because both would be this pass
423/// writing an opcode other than the one it read and that is a different claim from the one above.
424const fn bit_at_a_time(opcode: Opcode) -> bool {
425    matches!(opcode, Opcode::And | Opcode::Or | Opcode::Xor)
426}
427
428/// The one bit value this operand is the zero extension of, when that is what it is.
429///
430/// A zero extension and not a sign extension. A sign extension from one bit gives zero or minus
431/// one, so the operation over two of them is again zero or minus one, and the answer to the
432/// nonzero question is still the bottom bit, so the rewrite would hold. Nothing produces one: a
433/// one bit value in this IR is what a comparison answers and the front end widens it with a zero
434/// extension every time, which is what the language says, since a `_Bool` converted to `int` is
435/// zero or one.
436fn widened_bit(func: &Func, value: Value, uses: &[u32], readers: u32) -> Option<Value> {
437    let inst = read_by(func, value, uses, readers)?;
438    let data = &func[inst];
439    if data.opcode != Opcode::ZExt {
440        return None;
441    }
442    let narrow = *func[data.args].first()?;
443    (func[narrow].ty == Type::int(1)).then_some(narrow)
444}
445
446/// The extension this value is, as the kind, the width it came from and the value it extended.
447fn widening(func: &Func, value: Value) -> Option<(Opcode, Type, Value)> {
448    let Def::Result { inst, .. } = func[value].def else { return None };
449    let data = &func[inst];
450    if data.opcode != Opcode::SExt && data.opcode != Opcode::ZExt {
451        return None;
452    }
453    let narrow = *func[data.args].first()?;
454    Some((data.opcode, func[narrow].ty, narrow))
455}
456
457/// What this value was before it was extended to that width, when that is what it is.
458///
459/// Which extension it was is not asked, because this is the arithmetic side and the arithmetic
460/// reads the low bits only. Those are the bits the extension copied, whichever one it was.
461fn extended(func: &Func, value: Value, ty: Type) -> Option<Value> {
462    let (_, from, narrow) = widening(func, value)?;
463    (from == ty).then_some(narrow)
464}
465
466/// The constant this value is, with the type it has.
467fn constant(func: &Func, value: Value) -> Option<(Imm, Type)> {
468    let Def::Result { inst, .. } = func[value].def else { return None };
469    let data = &func[inst];
470    let Extra::Imm(at) = data.extra else { return None };
471    if data.opcode != Opcode::IConst {
472        return None;
473    }
474    let ty = func[value].ty;
475    ty.is_int().then(|| (func[at], ty))
476}
477
478/// A shift count that is a constant below the narrow width, which is the only one that narrows.
479///
480/// A count at or above the width is poison at the narrow width and is a defined shift to zero at
481/// the wide one, so the guard is what keeps the rewrite from inventing undefined behaviour. A
482/// count that is not a constant cannot be guarded, since its value is what decides.
483fn count_below(func: &Func, value: Value, ty: Type) -> Option<i128> {
484    let (imm, wide) = constant(func, value)?;
485    let by = imm.signed(wide);
486    (by >= 0 && by < i128::from(ty.bits())).then_some(by)
487}
488
489/// A constant that is the extension of a constant at the narrow width, as that narrow constant.
490///
491/// Both extensions are injective, so a comparison against a constant in the image of one is the
492/// same comparison against what it is the image of. A constant outside the image is a comparison
493/// that is already decided, which is a thing for folding to say rather than for this to guess at.
494fn survives(func: &Func, value: Value, kind: Opcode, ty: Type) -> Option<i128> {
495    let (imm, wide) = constant(func, value)?;
496    let k = imm.signed(wide);
497    let back = Imm::int(k, ty).signed(ty);
498    let same = if kind == Opcode::SExt { back } else { Imm::int(k, ty).unsigned() as i128 };
499    (same == k).then_some(k)
500}
501
502/// Rewrites the instruction into what the plan says it is.
503///
504/// In place, because the result already has the narrow type and every use of it is already
505/// correct, which is the same reason folding and the peephole rewrite in place. What is left
506/// behind is the wide subtree, now read by nothing, which is what dead code elimination is for.
507fn apply(func: &mut Func, inst: Inst, redo: &Redo, uses: &mut Vec<u32>) {
508    let operands = operands(func, inst, redo, uses);
509    for value in func[func[inst].args].iter().copied() {
510        uses[value.index()] -= 1;
511    }
512    let args = listed(func, operands, uses);
513    let data = &mut func[inst];
514    data.opcode = redo.opcode;
515    // No flags. An operation that could not overflow at the wide width can overflow at the narrow
516    // one, so `nsw` and `nuw` do not survive the narrowing, and dropping them makes the operation
517    // more defined rather than less.
518    data.flags = Flags::NONE;
519    data.args = args;
520    data.extra = redo.extra;
521}
522
523/// The value an operand's plan comes to, writing whatever it needs in front of the instruction.
524fn build(func: &mut Func, before: Inst, ty: Type, plan: &Plan, uses: &mut Vec<u32>) -> Value {
525    match plan {
526        Plan::Already(value) => *value,
527        Plan::Constant(value) => {
528            let at = func.add_imm(Imm::int(*value, ty.lane()));
529            let data = InstData { extra: Extra::Imm(at), ..InstData::new(Opcode::IConst) };
530            written(func, before, data, ty, uses)
531        }
532        Plan::Nested(redo) => {
533            let operands = operands(func, before, redo, uses);
534            let args = listed(func, operands, uses);
535            let data = InstData { args, extra: redo.extra, ..InstData::new(redo.opcode) };
536            written(func, before, data, redo.ty, uses)
537        }
538    }
539}
540
541/// The values the plan's operands come to, written in front of the instruction if they are new.
542fn operands(
543    func: &mut Func,
544    before: Inst,
545    redo: &Redo,
546    uses: &mut Vec<u32>,
547) -> (Value, Option<Value>) {
548    let lhs = build(func, before, redo.ty, &redo.lhs, uses);
549    let rhs = redo.rhs.as_ref().map(|plan| build(func, before, redo.ty, plan, uses));
550    (lhs, rhs)
551}
552
553/// Hands back the operand list to put on an instruction, counting each one as read.
554fn listed(func: &mut Func, (lhs, rhs): (Value, Option<Value>), uses: &mut [u32]) -> ValueList {
555    uses[lhs.index()] += 1;
556    let Some(rhs) = rhs else { return func.push_values(&[lhs]) };
557    uses[rhs.index()] += 1;
558    func.push_values(&[lhs, rhs])
559}
560
561/// Puts an instruction in front of another one and gives back the value it produces.
562fn written(func: &mut Func, before: Inst, data: InstData, ty: Type, uses: &mut Vec<u32>) -> Value {
563    let span = func.span(before);
564    let inst = func.create_inst(data, &[ty], span);
565    func.insert_before(inst, before);
566    uses.resize(func.counts().values, 0);
567    func[inst].first_result.expect("one result was asked for")
568}
569
570#[cfg(test)]
571mod tests {
572    use rucc_base::Interner;
573    use rucc_ir::{Block, Builder, Flags, Func, Inst, IntPred, Opcode, Signature, Type, Value};
574
575    use crate::narrow::Narrow;
576    use crate::{Fuel, Pass};
577
578    /// A function with one block, ready to have instructions appended to it.
579    fn blank() -> (Func, Block) {
580        let mut names = Interner::new();
581        let name = names.intern("f");
582        let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(32)]));
583        let block = func.create_block();
584        (func, block)
585    }
586
587    /// The opcode and the operand types of the instruction that produced a value.
588    fn shape(func: &Func, value: Value) -> (Opcode, Vec<Type>) {
589        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
590        let data = &func[inst];
591        (data.opcode, func[data.args].iter().map(|&arg| func[arg].ty).collect())
592    }
593
594    /// The first operand of the instruction that produced a value.
595    fn under(func: &Func, value: Value) -> Value {
596        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
597        *func[func[inst].args].first().expect("an operand")
598    }
599
600    /// The predicate of the comparison this value is the answer to.
601    fn predicate(func: &Func, value: Value) -> IntPred {
602        let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("a result") };
603        let rucc_ir::Extra::IntPred(pred) = func[inst].extra else { panic!("a comparison") };
604        pred
605    }
606
607    /// How many instructions are in a block.
608    fn left(func: &Func, block: Block) -> usize {
609        func.insts(block).count()
610    }
611
612    /// The last instruction of a block, which is the one every test here returns from.
613    fn last(func: &Func, block: Block) -> Inst {
614        func.insts(block).last().expect("a block with something in it")
615    }
616
617    #[test]
618    fn a_truncated_sum_of_two_extensions_is_the_sum_at_the_narrow_width() {
619        let (mut func, block) = blank();
620        let a = func.append_param(block, Type::int(8));
621        let b = func.append_param(block, Type::int(8));
622        let mut build = Builder::new(&mut func, block);
623        let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
624        let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
625        let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
626        let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
627        build.ret(&[narrow]);
628        assert!(
629            Narrow
630                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
631                .changed()
632        );
633        assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
634        // Nothing new was written. The two extensions and the wide add are still there, read by
635        // nothing, which is what dead code elimination takes out after this.
636        assert_eq!(left(&func, block), 5);
637    }
638
639    #[test]
640    fn a_constant_operand_is_written_down_again_at_the_narrow_width() {
641        let (mut func, block) = blank();
642        let a = func.append_param(block, Type::int(8));
643        let mut build = Builder::new(&mut func, block);
644        let wide = build.unary(Opcode::SExt, a, Type::int(32));
645        let one = build.iconst(Type::int(32), 1);
646        let sum = build.binary(Opcode::Add, wide, one, Flags::NONE);
647        let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
648        build.ret(&[narrow]);
649        assert!(
650            Narrow
651                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
652                .changed()
653        );
654        assert_eq!(shape(&func, narrow), (Opcode::Add, vec![Type::int(8), Type::int(8)]));
655    }
656
657    #[test]
658    fn a_chain_of_arithmetic_narrows_the_whole_way_down() {
659        let (mut func, block) = blank();
660        let a = func.append_param(block, Type::int(8));
661        let b = func.append_param(block, Type::int(8));
662        let c = func.append_param(block, Type::int(8));
663        let mut build = Builder::new(&mut func, block);
664        let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
665        let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
666        let wide_c = build.unary(Opcode::SExt, c, Type::int(32));
667        let inner = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
668        let outer = build.binary(Opcode::Mul, inner, wide_c, Flags::NONE);
669        let narrow = build.unary(Opcode::Trunc, outer, Type::int(8));
670        build.ret(&[narrow]);
671        assert!(
672            Narrow
673                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
674                .changed()
675        );
676        // The outer operation is the truncation rewritten, and the inner one is a new instruction
677        // written in front of it, which is the recursive case and the reason a plan is a tree.
678        assert_eq!(shape(&func, narrow), (Opcode::Mul, vec![Type::int(8), Type::int(8)]));
679        assert_eq!(left(&func, block), 8);
680    }
681
682    #[test]
683    fn an_operation_something_else_reads_stays_wide() {
684        let (mut func, block) = blank();
685        let a = func.append_param(block, Type::int(8));
686        let b = func.append_param(block, Type::int(8));
687        let mut build = Builder::new(&mut func, block);
688        let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
689        let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
690        let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NONE);
691        let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
692        let kept = build.unary(Opcode::SExt, narrow, Type::int(32));
693        build.ret(&[sum, kept]);
694        assert!(
695            !Narrow
696                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
697                .changed()
698        );
699        // The wide sum is read by the return as well as by the truncation, so narrowing would add
700        // an instruction rather than replace one.
701        assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
702    }
703
704    #[test]
705    fn a_divide_stays_wide_because_the_narrow_one_can_raise() {
706        let (mut func, block) = blank();
707        let a = func.append_param(block, Type::int(8));
708        let b = func.append_param(block, Type::int(8));
709        let mut build = Builder::new(&mut func, block);
710        let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
711        let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
712        let quotient = build.binary(Opcode::SDiv, wide_a, wide_b, Flags::NONE);
713        let narrow = build.unary(Opcode::Trunc, quotient, Type::int(8));
714        build.ret(&[narrow]);
715        assert!(
716            !Narrow
717                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
718                .changed()
719        );
720        // The most negative byte over minus one is a hundred and twenty eight at four bytes and
721        // is the overflow that raises at one, so this is the rewrite that would turn a working
722        // program into one that dies.
723        assert_eq!(shape(&func, narrow), (Opcode::Trunc, vec![Type::int(32)]));
724    }
725
726    #[test]
727    fn a_shift_by_a_constant_below_the_width_narrows_and_one_at_it_does_not() {
728        for (by, narrows) in [(3, true), (20, false)] {
729            let (mut func, block) = blank();
730            let a = func.append_param(block, Type::int(8));
731            let mut build = Builder::new(&mut func, block);
732            let wide = build.unary(Opcode::SExt, a, Type::int(32));
733            let count = build.iconst(Type::int(32), by);
734            let shifted = build.binary(Opcode::Shl, wide, count, Flags::NONE);
735            let narrow = build.unary(Opcode::Trunc, shifted, Type::int(8));
736            build.ret(&[narrow]);
737            assert_eq!(
738                Narrow
739                    .run(
740                        &mut func,
741                        &mut crate::machine::fixtures::analyses(),
742                        &mut Fuel::unlimited()
743                    )
744                    .changed(),
745                narrows,
746                "shift by {by}"
747            );
748            // A count of twenty is a defined shift to zero at four bytes and is poison at one, so
749            // narrowing it would be inventing undefined behaviour rather than removing a widening.
750            let want = if narrows { Opcode::Shl } else { Opcode::Trunc };
751            assert_eq!(shape(&func, narrow).0, want, "shift by {by}");
752        }
753    }
754
755    #[test]
756    fn a_shift_by_a_value_stays_wide() {
757        let (mut func, block) = blank();
758        let a = func.append_param(block, Type::int(8));
759        let n = func.append_param(block, Type::int(8));
760        let mut build = Builder::new(&mut func, block);
761        let wide = build.unary(Opcode::SExt, a, Type::int(32));
762        let by = build.unary(Opcode::SExt, n, Type::int(32));
763        let shifted = build.binary(Opcode::Shl, wide, by, Flags::NONE);
764        let narrow = build.unary(Opcode::Trunc, shifted, Type::int(8));
765        build.ret(&[narrow]);
766        assert!(
767            !Narrow
768                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
769                .changed()
770        );
771        assert_eq!(shape(&func, narrow).0, Opcode::Trunc);
772    }
773
774    #[test]
775    fn a_comparison_of_two_sign_extensions_is_the_comparison_of_what_they_extended() {
776        for pred in IntPred::all() {
777            let (mut func, block) = blank();
778            let a = func.append_param(block, Type::int(8));
779            let b = func.append_param(block, Type::int(8));
780            let mut build = Builder::new(&mut func, block);
781            let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
782            let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
783            let answer = build.icmp(pred, wide_a, wide_b);
784            build.ret(&[answer]);
785            assert!(
786                Narrow
787                    .run(
788                        &mut func,
789                        &mut crate::machine::fixtures::analyses(),
790                        &mut Fuel::unlimited()
791                    )
792                    .changed(),
793                "{pred}"
794            );
795            // Every predicate, because sign extension keeps the order of what it extends under
796            // the signed reading and under the unsigned one.
797            assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)], "{pred}");
798        }
799    }
800
801    #[test]
802    fn a_comparison_of_two_zero_extensions_narrows_at_every_predicate() {
803        for pred in IntPred::all() {
804            let (mut func, block) = blank();
805            let a = func.append_param(block, Type::int(8));
806            let b = func.append_param(block, Type::int(8));
807            let mut build = Builder::new(&mut func, block);
808            let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
809            let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
810            let answer = build.icmp(pred, wide_a, wide_b);
811            build.ret(&[answer]);
812            assert!(
813                Narrow
814                    .run(
815                        &mut func,
816                        &mut crate::machine::fixtures::analyses(),
817                        &mut Fuel::unlimited()
818                    )
819                    .changed(),
820                "{pred}"
821            );
822            assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)], "{pred}");
823        }
824    }
825
826    #[test]
827    fn a_signed_comparison_of_two_zero_extensions_narrows_to_the_unsigned_one() {
828        // `unsigned char a, b; a < b`, which the promotions write as a signed comparison of two
829        // zero extensions. Both sides have their top bits clear, where the two readings of the
830        // bits agree, so the question the wide comparison asks is the unsigned one and that is
831        // the predicate the narrow comparison is written with.
832        for pred in IntPred::all() {
833            let (mut func, block) = blank();
834            let a = func.append_param(block, Type::int(8));
835            let b = func.append_param(block, Type::int(8));
836            let mut build = Builder::new(&mut func, block);
837            let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
838            let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
839            let answer = build.icmp(pred, wide_a, wide_b);
840            build.ret(&[answer]);
841            Narrow.run(
842                &mut func,
843                &mut crate::machine::fixtures::analyses(),
844                &mut Fuel::unlimited(),
845            );
846            assert_eq!(predicate(&func, answer), pred.unsigned(), "{pred}");
847        }
848    }
849
850    #[test]
851    fn a_signed_comparison_of_two_sign_extensions_keeps_the_predicate_it_was_written_with() {
852        for pred in IntPred::all() {
853            let (mut func, block) = blank();
854            let a = func.append_param(block, Type::int(8));
855            let b = func.append_param(block, Type::int(8));
856            let mut build = Builder::new(&mut func, block);
857            let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
858            let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
859            let answer = build.icmp(pred, wide_a, wide_b);
860            build.ret(&[answer]);
861            Narrow.run(
862                &mut func,
863                &mut crate::machine::fixtures::analyses(),
864                &mut Fuel::unlimited(),
865            );
866            assert_eq!(predicate(&func, answer), pred, "{pred}");
867        }
868    }
869
870    #[test]
871    fn a_signed_comparison_of_a_zero_extension_against_a_constant_narrows_to_the_unsigned_one() {
872        // `unsigned char a; a < 200`. Two hundred is the zero extension of a byte even though it
873        // is not the sign extension of one, so the constant comes along and the comparison that
874        // is left is the unsigned one against that byte.
875        let (mut func, block) = blank();
876        let a = func.append_param(block, Type::int(8));
877        let mut build = Builder::new(&mut func, block);
878        let wide = build.unary(Opcode::ZExt, a, Type::int(32));
879        let k = build.iconst(Type::int(32), 200);
880        let answer = build.icmp(IntPred::Slt, wide, k);
881        build.ret(&[answer]);
882        assert!(
883            Narrow
884                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
885                .changed()
886        );
887        assert_eq!(shape(&func, answer).1, vec![Type::int(8), Type::int(8)]);
888        assert_eq!(predicate(&func, answer), IntPred::Ult);
889    }
890
891    #[test]
892    fn a_signed_comparison_of_a_zero_extension_against_a_negative_constant_is_left_alone() {
893        // Minus one is no byte's zero extension, so the comparison is already decided and saying
894        // which way is folding's job. Narrowing it would compare a byte against minus one, which
895        // is a different question under either reading.
896        let (mut func, block) = blank();
897        let a = func.append_param(block, Type::int(8));
898        let mut build = Builder::new(&mut func, block);
899        let wide = build.unary(Opcode::ZExt, a, Type::int(32));
900        let k = build.iconst(Type::int(32), -1);
901        let answer = build.icmp(IntPred::Sgt, wide, k);
902        build.ret(&[answer]);
903        assert!(
904            !Narrow
905                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
906                .changed()
907        );
908    }
909
910    #[test]
911    fn a_comparison_against_a_constant_narrows_when_the_constant_is_one_of_the_narrow_ones() {
912        for (k, narrows) in [(120, true), (-1, true), (200, false)] {
913            let (mut func, block) = blank();
914            let a = func.append_param(block, Type::int(8));
915            let mut build = Builder::new(&mut func, block);
916            let wide = build.unary(Opcode::SExt, a, Type::int(32));
917            let k = build.iconst(Type::int(32), k);
918            let answer = build.icmp(IntPred::Eq, wide, k);
919            build.ret(&[answer]);
920            // Two hundred is not the sign extension of any byte, so the comparison is already
921            // decided and saying so is folding's job rather than this pass's.
922            assert_eq!(
923                Narrow
924                    .run(
925                        &mut func,
926                        &mut crate::machine::fixtures::analyses(),
927                        &mut Fuel::unlimited()
928                    )
929                    .changed(),
930                narrows
931            );
932        }
933    }
934
935    #[test]
936    fn one_extension_against_the_other_kind_is_not_a_comparison_at_the_narrow_width() {
937        // `(signed char) a < b` with `b` an `unsigned char`, which is `tamnd/rucc#375`'s one
938        // wrong answer over the torture suite: sixteen is less than a hundred and ninety five at
939        // four bytes and is not less than minus sixty one at one, and neither is the byte
940        // comparison the other reading would give.
941        for pred in IntPred::all() {
942            let (mut func, block) = blank();
943            let a = func.append_param(block, Type::int(8));
944            let b = func.append_param(block, Type::int(8));
945            let mut build = Builder::new(&mut func, block);
946            let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
947            let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
948            let answer = build.icmp(pred, wide_a, wide_b);
949            build.ret(&[answer]);
950            assert!(
951                !Narrow
952                    .run(
953                        &mut func,
954                        &mut crate::machine::fixtures::analyses(),
955                        &mut Fuel::unlimited()
956                    )
957                    .changed(),
958                "{pred}"
959            );
960        }
961    }
962
963    #[test]
964    fn a_truth_is_not_a_width_to_narrow_to() {
965        // `!c != 0`, which is a comparison of a widened truth against a zero that survives the
966        // widening, so the argument narrows it the whole way to one bit. The answer would be
967        // right and no target lowers a one bit comparison, which is `tamnd/rucc#352`.
968        let (mut func, block) = blank();
969        let a = func.append_param(block, Type::int(1));
970        let mut build = Builder::new(&mut func, block);
971        let wide = build.unary(Opcode::ZExt, a, Type::int(32));
972        let zero = build.iconst(Type::int(32), 0);
973        let answer = build.icmp(IntPred::Ne, wide, zero);
974        build.ret(&[answer]);
975        assert!(
976            !Narrow
977                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
978                .changed()
979        );
980        assert_eq!(shape(&func, answer).1, vec![Type::int(32), Type::int(32)]);
981    }
982
983    #[test]
984    fn extensions_from_different_widths_are_not_a_comparison_at_either_of_them() {
985        let (mut func, block) = blank();
986        let a = func.append_param(block, Type::int(8));
987        let b = func.append_param(block, Type::int(16));
988        let mut build = Builder::new(&mut func, block);
989        let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
990        let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
991        let answer = build.icmp(IntPred::Slt, wide_a, wide_b);
992        build.ret(&[answer]);
993        assert!(
994            !Narrow
995                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
996                .changed()
997        );
998    }
999
1000    #[test]
1001    fn the_overflow_flags_do_not_come_along() {
1002        let (mut func, block) = blank();
1003        let a = func.append_param(block, Type::int(8));
1004        let b = func.append_param(block, Type::int(8));
1005        let mut build = Builder::new(&mut func, block);
1006        let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1007        let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1008        let sum = build.binary(Opcode::Add, wide_a, wide_b, Flags::NSW);
1009        let narrow = build.unary(Opcode::Trunc, sum, Type::int(8));
1010        build.ret(&[narrow]);
1011        assert!(
1012            Narrow
1013                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1014                .changed()
1015        );
1016        // A sum of two bytes that cannot overflow four bytes can overflow one, so a promise made
1017        // about the wide operation is not a promise about the narrow one.
1018        let rucc_ir::Def::Result { inst, .. } = func[narrow].def else { panic!("a result") };
1019        assert_eq!(func[inst].flags, Flags::NONE);
1020    }
1021
1022    /// `_Bool p, q; _Bool r = p & q;` and the same at the other two operators.
1023    ///
1024    /// The promotions widen both bits to an `int`, the operator runs there, and the conversion of
1025    /// the answer back to `_Bool` is the comparison against zero. All of that is the operator on
1026    /// the two bits.
1027    #[test]
1028    fn a_bitwise_operation_on_two_widened_bits_is_done_at_one_bit() {
1029        for opcode in [Opcode::And, Opcode::Or, Opcode::Xor] {
1030            let (mut func, block) = blank();
1031            let p = func.append_param(block, Type::int(1));
1032            let q = func.append_param(block, Type::int(1));
1033            let mut build = Builder::new(&mut func, block);
1034            let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1035            let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1036            let both = build.binary(opcode, wide_p, wide_q, Flags::NONE);
1037            let zero = build.iconst(Type::int(32), 0);
1038            let answer = build.icmp(IntPred::Ne, both, zero);
1039            build.ret(&[answer]);
1040            assert!(
1041                Narrow
1042                    .run(
1043                        &mut func,
1044                        &mut crate::machine::fixtures::analyses(),
1045                        &mut Fuel::unlimited()
1046                    )
1047                    .changed(),
1048                "{opcode:?}"
1049            );
1050            assert_eq!(shape(&func, answer), (opcode, vec![Type::int(1), Type::int(1)]));
1051        }
1052    }
1053
1054    /// Asking whether it came out zero is the negation of asking whether it came out nonzero, and
1055    /// a negation is an instruction this pass has nowhere to put.
1056    #[test]
1057    fn asking_whether_a_bitwise_operation_on_widened_bits_is_zero_is_left_alone() {
1058        let (mut func, block) = blank();
1059        let p = func.append_param(block, Type::int(1));
1060        let q = func.append_param(block, Type::int(1));
1061        let mut build = Builder::new(&mut func, block);
1062        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1063        let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1064        let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1065        let zero = build.iconst(Type::int(32), 0);
1066        let answer = build.icmp(IntPred::Eq, both, zero);
1067        build.ret(&[answer]);
1068        assert!(
1069            !Narrow
1070                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1071                .changed()
1072        );
1073        assert_eq!(shape(&func, answer).1, vec![Type::int(32), Type::int(32)]);
1074    }
1075
1076    /// A sum of two widened bits is nonzero exactly when their `or` is, and that is a different
1077    /// claim from the one this makes, so it is not made here.
1078    #[test]
1079    fn a_sum_of_two_widened_bits_is_left_alone() {
1080        let (mut func, block) = blank();
1081        let p = func.append_param(block, Type::int(1));
1082        let q = func.append_param(block, Type::int(1));
1083        let mut build = Builder::new(&mut func, block);
1084        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1085        let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1086        let both = build.binary(Opcode::Add, wide_p, wide_q, Flags::NONE);
1087        let zero = build.iconst(Type::int(32), 0);
1088        let answer = build.icmp(IntPred::Ne, both, zero);
1089        build.ret(&[answer]);
1090        assert!(
1091            !Narrow
1092                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1093                .changed()
1094        );
1095    }
1096
1097    /// Two widened bytes, which are not zero or one, so the bottom bit of the `and` is not the
1098    /// answer to whether the whole of it is nonzero.
1099    #[test]
1100    fn a_bitwise_operation_on_something_wider_than_a_bit_is_not_this_shape() {
1101        let (mut func, block) = blank();
1102        let a = func.append_param(block, Type::int(8));
1103        let b = func.append_param(block, Type::int(8));
1104        let mut build = Builder::new(&mut func, block);
1105        let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
1106        let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1107        let both = build.binary(Opcode::And, wide_a, wide_b, Flags::NONE);
1108        let zero = build.iconst(Type::int(32), 0);
1109        let answer = build.icmp(IntPred::Ne, both, zero);
1110        build.ret(&[answer]);
1111        Narrow.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited());
1112        // The truncated arithmetic shape does narrow the `and` to a byte, which is a different
1113        // rewrite and is why this asserts on the comparison rather than on nothing having moved.
1114        assert_eq!(shape(&func, answer).0, Opcode::ICmp);
1115    }
1116
1117    /// `_Bool r = p & 1;` and the rest of the one bit table, which is the point of the shape.
1118    ///
1119    /// The constant comes over as the same constant at one bit, and then tier one has the rule
1120    /// that finishes it. Four of the thirteen are here, one per answer the table gives.
1121    #[test]
1122    fn a_bitwise_operation_on_a_widened_bit_and_a_bit_constant_is_done_at_one_bit() {
1123        for (opcode, k) in
1124            [(Opcode::And, 0), (Opcode::And, 1), (Opcode::Or, 0), (Opcode::Or, 1), (Opcode::Xor, 0)]
1125        {
1126            let (mut func, block) = blank();
1127            let p = func.append_param(block, Type::int(1));
1128            let mut build = Builder::new(&mut func, block);
1129            let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1130            let bit = build.iconst(Type::int(32), k);
1131            let both = build.binary(opcode, wide_p, bit, Flags::NONE);
1132            let zero = build.iconst(Type::int(32), 0);
1133            let answer = build.icmp(IntPred::Ne, both, zero);
1134            build.ret(&[answer]);
1135            assert!(
1136                Narrow
1137                    .run(
1138                        &mut func,
1139                        &mut crate::machine::fixtures::analyses(),
1140                        &mut Fuel::unlimited()
1141                    )
1142                    .changed(),
1143                "{opcode:?} {k}"
1144            );
1145            assert_eq!(shape(&func, answer), (opcode, vec![Type::int(1), Type::int(1)]));
1146        }
1147    }
1148
1149    /// A constant with a bit set above the bottom one, which is where the argument stops holding:
1150    /// the wide result can be nonzero with its bottom bit clear.
1151    #[test]
1152    fn a_bitwise_operation_against_a_constant_wider_than_a_bit_is_left_alone() {
1153        let (mut func, block) = blank();
1154        let p = func.append_param(block, Type::int(1));
1155        let mut build = Builder::new(&mut func, block);
1156        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1157        let two = build.iconst(Type::int(32), 2);
1158        let both = build.binary(Opcode::Or, wide_p, two, Flags::NONE);
1159        let zero = build.iconst(Type::int(32), 0);
1160        let answer = build.icmp(IntPred::Ne, both, zero);
1161        build.ret(&[answer]);
1162        assert!(
1163            !Narrow
1164                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1165                .changed()
1166        );
1167    }
1168
1169    /// `_Bool r = p & p;`, where one widening is read twice by the operation above it and by
1170    /// nothing else, which is the same fact about the subtree as one reader is.
1171    #[test]
1172    fn a_widened_bit_the_operation_reads_twice_is_still_only_read_by_it() {
1173        let (mut func, block) = blank();
1174        let p = func.append_param(block, Type::int(1));
1175        let mut build = Builder::new(&mut func, block);
1176        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1177        let both = build.binary(Opcode::And, wide_p, wide_p, Flags::NONE);
1178        let zero = build.iconst(Type::int(32), 0);
1179        let answer = build.icmp(IntPred::Ne, both, zero);
1180        build.ret(&[answer]);
1181        assert!(
1182            Narrow
1183                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1184                .changed()
1185        );
1186        assert_eq!(shape(&func, answer), (Opcode::And, vec![Type::int(1), Type::int(1)]));
1187    }
1188
1189    /// A widened bit something else reads as well, which keeps the widening alive, so the
1190    /// rewrite would be an instruction added rather than a subtree replaced.
1191    #[test]
1192    fn a_widened_bit_that_something_else_reads_is_left_alone() {
1193        let (mut func, block) = blank();
1194        let p = func.append_param(block, Type::int(1));
1195        let q = func.append_param(block, Type::int(1));
1196        let mut build = Builder::new(&mut func, block);
1197        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1198        let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1199        let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1200        let zero = build.iconst(Type::int(32), 0);
1201        let answer = build.icmp(IntPred::Ne, both, zero);
1202        build.ret(&[answer, wide_p]);
1203        assert!(
1204            !Narrow
1205                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1206                .changed()
1207        );
1208    }
1209
1210    /// Against something other than zero, which asks a question the bottom bit does not answer.
1211    #[test]
1212    fn a_bitwise_operation_on_widened_bits_compared_against_one_is_left_alone() {
1213        let (mut func, block) = blank();
1214        let p = func.append_param(block, Type::int(1));
1215        let q = func.append_param(block, Type::int(1));
1216        let mut build = Builder::new(&mut func, block);
1217        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1218        let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1219        let both = build.binary(Opcode::Or, wide_p, wide_q, Flags::NONE);
1220        let one = build.iconst(Type::int(32), 1);
1221        let answer = build.icmp(IntPred::Ne, both, one);
1222        build.ret(&[answer]);
1223        assert!(
1224            !Narrow
1225                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1226                .changed()
1227        );
1228    }
1229
1230    /// `(long long)(p & q)`, which asks for the result as a wider number rather than as a truth.
1231    ///
1232    /// The bits are zero or one, so the extension of what the operation came to is the extension
1233    /// of the one bit it came to, and a sign extension there is the same value as a zero one.
1234    #[test]
1235    fn a_bitwise_operation_on_widened_bits_taken_wider_is_done_at_one_bit() {
1236        for kind in [Opcode::ZExt, Opcode::SExt] {
1237            let (mut func, block) = blank();
1238            let p = func.append_param(block, Type::int(1));
1239            let q = func.append_param(block, Type::int(1));
1240            let mut build = Builder::new(&mut func, block);
1241            let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1242            let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1243            let both = build.binary(Opcode::And, wide_p, wide_q, Flags::NONE);
1244            let wider = build.unary(kind, both, Type::int(64));
1245            build.ret(&[wider]);
1246            assert!(
1247                Narrow
1248                    .run(
1249                        &mut func,
1250                        &mut crate::machine::fixtures::analyses(),
1251                        &mut Fuel::unlimited()
1252                    )
1253                    .changed(),
1254                "{kind:?}"
1255            );
1256            assert_eq!(shape(&func, wider), (Opcode::ZExt, vec![Type::int(1)]), "{kind:?}");
1257            let bit = under(&func, wider);
1258            let want = (Opcode::And, vec![Type::int(1), Type::int(1)]);
1259            assert_eq!(shape(&func, bit), want, "{kind:?}");
1260        }
1261    }
1262
1263    /// A bitwise operation on two bit constants, which is a number the folder knows and not a
1264    /// widening this has any way of reaching past.
1265    #[test]
1266    fn a_bitwise_operation_on_two_bit_constants_is_left_to_the_folder() {
1267        let (mut func, block) = blank();
1268        let mut build = Builder::new(&mut func, block);
1269        let zero = build.iconst(Type::int(32), 0);
1270        let one = build.iconst(Type::int(32), 1);
1271        let both = build.binary(Opcode::And, zero, one, Flags::NONE);
1272        let wider = build.unary(Opcode::ZExt, both, Type::int(64));
1273        build.ret(&[wider]);
1274        assert!(
1275            !Narrow
1276                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1277                .changed()
1278        );
1279    }
1280
1281    /// The second question rewrites an extension into an extension, so the pass has to be asked
1282    /// twice before it has said it stops. What it is handed the second time is an operation
1283    /// already at one bit, which is not a narrowing and is left where it is.
1284    #[test]
1285    fn a_bitwise_operation_already_at_one_bit_is_not_done_again() {
1286        let (mut func, block) = blank();
1287        let p = func.append_param(block, Type::int(1));
1288        let q = func.append_param(block, Type::int(1));
1289        let mut build = Builder::new(&mut func, block);
1290        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1291        let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1292        let both = build.binary(Opcode::Xor, wide_p, wide_q, Flags::NONE);
1293        let wider = build.unary(Opcode::SExt, both, Type::int(64));
1294        build.ret(&[wider]);
1295        let mut an = crate::machine::fixtures::analyses();
1296        assert!(Narrow.run(&mut func, &mut an, &mut Fuel::unlimited()).changed());
1297        assert!(!Narrow.run(&mut func, &mut an, &mut Fuel::unlimited()).changed());
1298    }
1299
1300    /// `(long long)(p & 1)`, where the constant comes over at one bit the same as it does under a
1301    /// comparison, and then tier one has the rule that finishes it.
1302    #[test]
1303    fn a_bit_constant_comes_over_under_an_extension_too() {
1304        let (mut func, block) = blank();
1305        let p = func.append_param(block, Type::int(1));
1306        let mut build = Builder::new(&mut func, block);
1307        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1308        let one = build.iconst(Type::int(32), 1);
1309        let both = build.binary(Opcode::And, wide_p, one, Flags::NONE);
1310        let wider = build.unary(Opcode::SExt, both, Type::int(64));
1311        build.ret(&[wider]);
1312        assert!(
1313            Narrow
1314                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1315                .changed()
1316        );
1317        assert_eq!(shape(&func, wider), (Opcode::ZExt, vec![Type::int(1)]));
1318        assert_eq!(shape(&func, under(&func, wider)), (Opcode::And, vec![Type::int(1); 2]));
1319    }
1320
1321    /// An extension of a bitwise operation on things wider than a bit, which is the ordinary
1322    /// promoted shape and has nothing to do with this.
1323    #[test]
1324    fn an_extension_of_a_bitwise_operation_on_bytes_is_left_alone() {
1325        let (mut func, block) = blank();
1326        let a = func.append_param(block, Type::int(8));
1327        let b = func.append_param(block, Type::int(8));
1328        let mut build = Builder::new(&mut func, block);
1329        let wide_a = build.unary(Opcode::ZExt, a, Type::int(32));
1330        let wide_b = build.unary(Opcode::ZExt, b, Type::int(32));
1331        let both = build.binary(Opcode::And, wide_a, wide_b, Flags::NONE);
1332        let wider = build.unary(Opcode::SExt, both, Type::int(64));
1333        build.ret(&[wider]);
1334        assert!(
1335            !Narrow
1336                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1337                .changed()
1338        );
1339    }
1340
1341    /// An extension of a sum of two widened bits, which is zero, one or two, so the whole of it is
1342    /// not its own bottom bit and the operation the pass would write is not the one it read.
1343    #[test]
1344    fn an_extension_of_a_sum_of_two_widened_bits_is_left_alone() {
1345        let (mut func, block) = blank();
1346        let p = func.append_param(block, Type::int(1));
1347        let q = func.append_param(block, Type::int(1));
1348        let mut build = Builder::new(&mut func, block);
1349        let wide_p = build.unary(Opcode::ZExt, p, Type::int(32));
1350        let wide_q = build.unary(Opcode::ZExt, q, Type::int(32));
1351        let both = build.binary(Opcode::Add, wide_p, wide_q, Flags::NONE);
1352        let wider = build.unary(Opcode::SExt, both, Type::int(64));
1353        build.ret(&[wider]);
1354        assert!(
1355            !Narrow
1356                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1357                .changed()
1358        );
1359    }
1360
1361    #[test]
1362    fn fuel_stops_the_narrowing_and_not_the_looking() {
1363        let (mut func, block) = blank();
1364        let a = func.append_param(block, Type::int(8));
1365        let b = func.append_param(block, Type::int(8));
1366        let mut build = Builder::new(&mut func, block);
1367        let wide_a = build.unary(Opcode::SExt, a, Type::int(32));
1368        let wide_b = build.unary(Opcode::SExt, b, Type::int(32));
1369        let first = build.icmp(IntPred::Slt, wide_a, wide_b);
1370        let second = build.icmp(IntPred::Sgt, wide_a, wide_b);
1371        build.ret(&[first, second]);
1372        let mut fuel = Fuel::of(1);
1373        assert!(
1374            Narrow.run(&mut func, &mut crate::machine::fixtures::analyses(), &mut fuel).changed()
1375        );
1376        assert_eq!(shape(&func, first).1, vec![Type::int(8), Type::int(8)]);
1377        assert_eq!(shape(&func, second).1, vec![Type::int(32), Type::int(32)]);
1378    }
1379
1380    #[test]
1381    fn a_block_that_narrows_nothing_is_left_exactly_as_it_was() {
1382        let (mut func, block) = blank();
1383        let a = func.append_param(block, Type::int(32));
1384        let mut build = Builder::new(&mut func, block);
1385        let sum = build.binary(Opcode::Add, a, a, Flags::NONE);
1386        build.ret(&[sum]);
1387        assert!(
1388            !Narrow
1389                .run(&mut func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
1390                .changed()
1391        );
1392        assert_eq!(left(&func, block), 2);
1393        assert_eq!(func[last(&func, block)].opcode, Opcode::Return);
1394    }
1395}