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