Skip to main content

rucc_codegen/
widths.rs

1//! The integer widths the machine has, for the ones a program wrote that it does not.
2//!
3//! Design: `spec/08-ir.md` section 8.2 and `spec/10-backend.md` section 10.2.
4//!
5//! The IR has an integer of any width, because C does: `_BitInt(40)` is forty bits of value and
6//! `unsigned long long b:40` is a bit-field whose arithmetic happens at forty bits, and an IR that
7//! rounded either of those up to sixty four would have thrown away the thing that makes them
8//! different from a `long long`. A machine has four integer widths and forty is not one of them.
9//! This is where the gap is closed.
10//!
11//! Every value of a width the machine has no register for is put into the narrowest one it does,
12//! which is the width rounded up to a byte and then to a power of two, and that is the same width
13//! the type's own layout already has: a `_BitInt(40)` object is eight bytes, so nothing here
14//! changes how wide a load or a store is against the object it reads.
15//!
16//! # What the spare bits hold
17//!
18//! Nothing, and that is the decision the rest of this file follows from. A forty bit value in a
19//! sixty four bit register has twenty four bits above it, and this pass does not say what is in
20//! them. The alternative is to keep the value extended and fix up every instruction that produces
21//! one, and it costs more: an add, a subtract, a multiply, a shift left and the three bitwise
22//! operations all give the right low forty bits whatever is above them, so an invariant would pay
23//! for a mask after each of those to buy a mask before the few that need one.
24//!
25//! What needs one is every instruction that reads a bit the narrow value does not have. A divide,
26//! a remainder, a shift right and a comparison each look at the whole register, so each gets its
27//! operands put into shape first, with the sign spread for the signed ones and the spare bits
28//! cleared for the unsigned ones, which is the same distinction the opcode already carries. A
29//! widening reads the value it widens, so it becomes the shaping itself when the two widths land
30//! in the same register. A store writes the spare bits into the object's padding, and they are
31//! cleared first so that the same program run twice writes the same bytes, which C leaves
32//! unspecified and a compiler should not.
33//!
34//! A shift count is shaped as well, which reads like an oddity and is not. The count has the type
35//! of the value being shifted, so a shift by a forty bit count is a count with twenty four spare
36//! bits in it, and the machine reads the low five or six bits of whatever register it is handed. A
37//! count that is a constant is already in range and is left alone, which is what every shift a C
38//! program writes at these widths turns out to be.
39//!
40//! # What it does not do
41//!
42//! A function whose signature has one of these widths in it, a call that passes or returns one,
43//! and anything else that touches one is left exactly as it was, and the selector then refuses the
44//! function by name the way it does today. The reason is the boundary rather than the arithmetic:
45//! the psABI says a `_BitInt(40)` argument arrives extended, and which extension it is depends on
46//! whether the type was signed, which is a fact the IR deliberately does not carry because the
47//! signedness of an integer lives on the operation there and not on the type. That belongs in the
48//! ABI lowering, where the C type is still in hand. `tamnd/rucc#425` is the issue for it.
49
50use rucc_base::Idx;
51use rucc_ir::{Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value};
52
53/// The width a value of this type is kept in, and [`None`] when the machine has one already.
54///
55/// One bit is a width the rules name, since a comparison produces it and it is what a `bool`
56/// lives in, so it is not one of these. Above sixty four bits there is no register to round up
57/// into, and a hundred and twenty eight bit integer is refused by name in [`crate::coverage`]
58/// rather than being pretended about here.
59#[must_use]
60fn container(ty: Type) -> Option<u32> {
61    if !ty.is_int() || !ty.is_scalar() {
62        return None;
63    }
64    let bits = ty.bits();
65    if bits == 1 || bits > 64 {
66        return None;
67    }
68    let held = bits.next_power_of_two().max(8);
69    (held != bits).then_some(held)
70}
71
72/// The opcodes this pass knows how to put into a machine width.
73///
74/// An instruction that touches one of these widths and is not one of these is why the pass leaves
75/// the whole function alone, so this list is the pass's own statement of what it has thought
76/// about. Adding to it is adding an arm to [`rewrite`] as well.
77#[must_use]
78fn understood(opcode: Opcode) -> bool {
79    matches!(
80        opcode,
81        Opcode::IConst
82            | Opcode::Add
83            | Opcode::Sub
84            | Opcode::Mul
85            | Opcode::SDiv
86            | Opcode::UDiv
87            | Opcode::SRem
88            | Opcode::URem
89            | Opcode::And
90            | Opcode::Or
91            | Opcode::Xor
92            | Opcode::Shl
93            | Opcode::LShr
94            | Opcode::AShr
95            | Opcode::ICmp
96            | Opcode::Trunc
97            | Opcode::SExt
98            | Opcode::ZExt
99            | Opcode::Load
100            | Opcode::Store
101            | Opcode::Jump
102            | Opcode::BrIf
103    )
104}
105
106/// Puts every integer of a width the machine has no register for into the width that holds it.
107///
108/// Gives back whether it changed anything, which is what a test asks and what tells a caller that
109/// a function it is about to hand to the selector is not the one the middle end produced.
110///
111/// The function is left exactly as it was when there is nothing at such a width, and also when
112/// something at such a width is reached by an instruction this does not understand, which is the
113/// second half of why the answer is a boolean. Leaving it alone is what makes the selector's
114/// refusal the thing a user sees, rather than a rewrite that guessed.
115pub fn integers(func: &mut Func) -> bool {
116    let narrow: Vec<Option<u32>> = func
117        .values()
118        .map(|value| container(func[value].ty).map(|_| func[value].ty.bits()))
119        .collect();
120    if narrow.iter().all(Option::is_none) {
121        return false;
122    }
123    if !every_width_is_one_the_signature_has(func) {
124        return false;
125    }
126
127    let insts: Vec<Inst> =
128        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
129    if !insts.iter().all(|&inst| touches_nothing_it_does_not_understand(func, &narrow, inst)) {
130        return false;
131    }
132
133    let values: Vec<Value> = func.values().collect();
134    for value in values {
135        let ty = func[value].ty;
136        if let Some(held) = container(ty) {
137            func.retype(value, Type::int(held));
138        }
139    }
140    // The constants first and all of them, because a shift count is a constant and the shift asks
141    // what its value is. The instruction order is the order the blocks are laid out in, which is
142    // not an order every definition comes before its uses in, so asking that question during the
143    // walk below would be asking it of an immediate that may or may not have been rewritten yet.
144    for &inst in &insts {
145        if func[inst].opcode == Opcode::IConst {
146            constant(func, &narrow, inst);
147        }
148    }
149    for inst in insts {
150        rewrite(func, &narrow, inst);
151    }
152    true
153}
154
155/// Whether nothing at one of these widths crosses the function's own boundary.
156///
157/// A parameter and a return value are the two places a width is agreed with something this
158/// compilation is not looking at, so a width the ABI has not been taught is a width this pass
159/// leaves for the ABI to be taught about. The entry block's parameters are asked as well as the
160/// signature's, because they are the same list said twice and this pass would rather notice the
161/// day they stop being.
162fn every_width_is_one_the_signature_has(func: &Func) -> bool {
163    let signature = func.signature();
164    let crossing = signature.params.iter().chain(signature.returns.iter());
165    if crossing.map(|param| param.ty).any(|ty| container(ty).is_some()) {
166        return false;
167    }
168    let Some(entry) = func.entry() else { return true };
169    func[entry].params.iter().all(|&value| container(func[value].ty).is_none())
170}
171
172/// Whether every value at one of these widths that this instruction touches is one it can handle.
173fn touches_nothing_it_does_not_understand(func: &Func, narrow: &[Option<u32>], inst: Inst) -> bool {
174    let data = &func[inst];
175    let touched = results(func, inst).any(|value| at(narrow, value).is_some())
176        || func[data.args].iter().any(|&value| at(narrow, value).is_some());
177    !touched || understood(data.opcode)
178}
179
180/// The narrow width a value had before it was widened, and [`None`] for one that was never narrow.
181///
182/// A value this pass created has no entry, which is the right answer for it: it was built at a
183/// width the machine has.
184#[must_use]
185fn at(narrow: &[Option<u32>], value: Value) -> Option<u32> {
186    narrow.get(value.index()).copied().flatten()
187}
188
189/// The values an instruction produces.
190fn results(func: &Func, inst: Inst) -> impl Iterator<Item = Value> + use<'_> {
191    let first = func[inst].first_result.map_or(0, Idx::index);
192    let count = usize::from(func[inst].results);
193    (first..first + count).map(Idx::from_usize)
194}
195
196/// One instruction, now that every value it names is at a width the machine has.
197fn rewrite(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
198    match func[inst].opcode {
199        // The low bits are the answer whatever is above them, so there is nothing to shape. The
200        // flags go, because `nsw` was a promise about the narrow width and says nothing about the
201        // wide one: an add of two values with rubbish in the spare bits can carry out of the
202        // register while the forty bit add it stands for does not.
203        Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::And | Opcode::Or | Opcode::Xor => {
204            forget_flags(func, narrow, inst);
205        }
206        Opcode::SDiv | Opcode::SRem => shape_both(func, narrow, inst, true),
207        Opcode::UDiv | Opcode::URem => shape_both(func, narrow, inst, false),
208        Opcode::Shl => shape_count(func, narrow, inst),
209        Opcode::LShr => {
210            shape_operand(func, narrow, inst, 0, false);
211            shape_count(func, narrow, inst);
212        }
213        Opcode::AShr => {
214            shape_operand(func, narrow, inst, 0, true);
215            shape_count(func, narrow, inst);
216        }
217        Opcode::ICmp => compare(func, narrow, inst),
218        Opcode::Trunc => truncate(func, narrow, inst),
219        Opcode::SExt => extend(func, narrow, inst, true),
220        Opcode::ZExt => extend(func, narrow, inst, false),
221        // The value written goes into the object's padding as well as into the object, and it is
222        // cleared so that the padding is the same on every run rather than being whatever was in
223        // the register. C says those bits hold nothing in particular; a compiler that writes a
224        // different nothing each time is a compiler whose output cannot be compared with itself.
225        Opcode::Store => shape_operand(func, narrow, inst, 0, false),
226        _ => {}
227    }
228}
229
230/// A constant, at the width it is now held in.
231///
232/// The immediate is stored in exactly the width of its type, so the same bits under a wider type
233/// are the same non-negative number and a negative one loses its sign extension. Reading it back
234/// signed at the narrow width and writing it at the wide one is what keeps `-1` a `-1`, and it is
235/// also what makes the spare bits of a constant say what the value says rather than say nothing,
236/// which is the one place this pass shapes something it did not have to.
237fn constant(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
238    let Some(ty) = produced(func, inst) else { return };
239    let Some(was) = produced_narrow(func, narrow, inst) else { return };
240    let Extra::Imm(imm) = func[inst].extra else { return };
241    let value = func[imm].signed(Type::int(was));
242    let imm = func.add_imm(Imm::int(value, ty));
243    func[inst].extra = Extra::Imm(imm);
244}
245
246/// Drops the arithmetic flags from an instruction whose result was narrower than its register.
247fn forget_flags(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
248    if produced_narrow(func, narrow, inst).is_none() {
249        return;
250    }
251    func[inst].flags = func[inst].flags.without(Flags::NSW.union(Flags::NUW).union(Flags::EXACT));
252}
253
254/// Both operands put into shape, for the instructions that read every bit of both.
255fn shape_both(func: &mut Func, narrow: &[Option<u32>], inst: Inst, signed: bool) {
256    shape_operand(func, narrow, inst, 0, signed);
257    shape_operand(func, narrow, inst, 1, signed);
258    if produced_narrow(func, narrow, inst).is_some() {
259        func[inst].flags = func[inst].flags.without(Flags::EXACT);
260    }
261}
262
263/// The count of a shift put into shape, which the machine reads the low bits of.
264fn shape_count(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
265    shape_operand(func, narrow, inst, 1, false);
266    if produced_narrow(func, narrow, inst).is_some() {
267        func[inst].flags =
268            func[inst].flags.without(Flags::NSW.union(Flags::NUW).union(Flags::EXACT));
269    }
270}
271
272/// A comparison, whose two operands are shaped the way its predicate reads them.
273///
274/// An equality reads both the same way, so either shape answers it and the cheaper one is used.
275fn compare(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
276    let Extra::IntPred(pred) = func[inst].extra else { return };
277    let signed = matches!(pred, IntPred::Slt | IntPred::Sle | IntPred::Sgt | IntPred::Sge);
278    shape_operand(func, narrow, inst, 0, signed);
279    shape_operand(func, narrow, inst, 1, signed);
280}
281
282/// Keeping the low bits, where the two widths may or may not have landed in the same register.
283///
284/// A truncation to a width the machine has always lands in a narrower register than it started
285/// in, so it stays a truncation. A truncation to one of these widths may not: forty bits down to
286/// thirty three is the same register twice, and what is left of it is the clearing of the bits
287/// the narrower value does not have, which is a mask.
288fn truncate(func: &mut Func, narrow: &[Option<u32>], inst: Inst) {
289    let Some(to) = produced_narrow(func, narrow, inst) else { return };
290    let args = func[inst].args;
291    let Some(&arg) = func[args].first() else { return };
292    let ty = func[arg].ty;
293    if produced(func, inst) != Some(ty) {
294        return;
295    }
296    let mask = ahead_const(func, inst, Imm::int(low_bits(to), ty), ty);
297    becomes(func, inst, Opcode::And, &[arg, mask]);
298}
299
300/// Widening, which reads every bit of what it widens.
301///
302/// The value it reads is put into shape first, and when the two widths landed in the same
303/// register that shaping is the whole of the answer: a thirty three bit value widened to forty one
304/// bits, both of them held in sixty four, is that value with its spare bits made into the sign or
305/// into zeroes and nothing else. When they landed in different registers the machine's own
306/// widening still has to happen, so the shaping goes in front of it.
307fn extend(func: &mut Func, narrow: &[Option<u32>], inst: Inst, signed: bool) {
308    let args = func[inst].args;
309    let Some(&arg) = func[args].first() else { return };
310    let Some(from) = at(narrow, arg) else { return };
311    let ty = func[arg].ty;
312    let Some(wide) = produced(func, inst) else { return };
313    if wide != ty {
314        let shaped = shaped(func, inst, arg, from, signed);
315        let opcode = if signed { Opcode::SExt } else { Opcode::ZExt };
316        becomes(func, inst, opcode, &[shaped]);
317        return;
318    }
319    if signed {
320        let spare = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - from), ty), ty);
321        let up = ahead(func, inst, Opcode::Shl, &[arg, spare], ty);
322        becomes(func, inst, Opcode::AShr, &[up, spare]);
323        return;
324    }
325    let mask = ahead_const(func, inst, Imm::int(low_bits(from), ty), ty);
326    becomes(func, inst, Opcode::And, &[arg, mask]);
327}
328
329/// Puts one operand of an instruction into shape, in place.
330fn shape_operand(func: &mut Func, narrow: &[Option<u32>], inst: Inst, index: usize, signed: bool) {
331    let list = func[inst].args;
332    let mut args: Vec<Value> = func[list].to_vec();
333    let Some(&arg) = args.get(index) else { return };
334    let Some(width) = at(narrow, arg) else { return };
335    let shaped = shaped(func, inst, arg, width, signed);
336    if shaped == arg {
337        return;
338    }
339    args[index] = shaped;
340    let list = func.push_values(&args);
341    func[inst].args = list;
342}
343
344/// A value whose spare bits say what the narrow value says, put in front of `inst`.
345///
346/// The sign spread over them for a signed reading, which is a shift up and an arithmetic shift
347/// back down, and zeroes for an unsigned one, which is a mask. A constant already in range is
348/// itself, which is what keeps a shift by a written number one instruction.
349fn shaped(func: &mut Func, inst: Inst, value: Value, width: u32, signed: bool) -> Value {
350    let ty = func[value].ty;
351    if already(func, value, width, signed) {
352        return value;
353    }
354    if signed {
355        let spare = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - width), ty), ty);
356        let up = ahead(func, inst, Opcode::Shl, &[value, spare], ty);
357        return ahead(func, inst, Opcode::AShr, &[up, spare], ty);
358    }
359    let mask = ahead_const(func, inst, Imm::int(low_bits(width), ty), ty);
360    ahead(func, inst, Opcode::And, &[value, mask], ty)
361}
362
363/// Whether a value already says what the narrow value says in every bit of its register.
364///
365/// Two shapes are recognised and both are ones this pass or the front end has just written, so
366/// neither needs an analysis to answer. A constant is in range or it is not, and a shift count is
367/// a constant in every C program that has reached this so far. A mask that keeps no more bits than
368/// the width has is a value whose spare bits are already zero, which is what a widening into the
369/// same register became a few instructions ago, and it is why a shifted bit-field is one `and`
370/// rather than two.
371fn already(func: &Func, value: Value, width: u32, signed: bool) -> bool {
372    let Def::Result { inst, .. } = func[value].def else { return false };
373    let ty = func[value].ty;
374    match func[inst].opcode {
375        Opcode::IConst => {
376            let Extra::Imm(imm) = func[inst].extra else { return false };
377            let held = func[imm].signed(ty);
378            if signed {
379                let spare = 128 - width;
380                return (held << spare) >> spare == held;
381            }
382            held >= 0 && held == held & low_bits(width)
383        }
384        // A mask says nothing about the sign bit of a narrower value, so it answers the unsigned
385        // question only.
386        Opcode::And if !signed => {
387            let args = func[inst].args;
388            func[args].iter().any(|&arg| keeps_no_more_than(func, arg, width))
389        }
390        _ => false,
391    }
392}
393
394/// Whether a value is a constant mask that keeps no bit above the low `width` of them.
395fn keeps_no_more_than(func: &Func, value: Value, width: u32) -> bool {
396    let Def::Result { inst, .. } = func[value].def else { return false };
397    if func[inst].opcode != Opcode::IConst {
398        return false;
399    }
400    let Extra::Imm(imm) = func[inst].extra else { return false };
401    let held = func[imm].signed(func[value].ty);
402    held >= 0 && held & !low_bits(width) == 0
403}
404
405/// The low `width` bits set, as an immediate's value.
406#[must_use]
407fn low_bits(width: u32) -> i128 {
408    (1i128 << width) - 1
409}
410
411/// The type of the one value an instruction produces, and [`None`] when it produces none.
412fn produced(func: &Func, inst: Inst) -> Option<Type> {
413    func[inst].first_result.map(|value| func[value].ty)
414}
415
416/// The narrow width the one value an instruction produces used to have.
417fn produced_narrow(func: &Func, narrow: &[Option<u32>], inst: Inst) -> Option<u32> {
418    at(narrow, func[inst].first_result?)
419}
420
421/// Puts an instruction over these operands in front of another one, and gives back its value.
422fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
423    let args = func.push_values(args);
424    written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
425}
426
427/// The same for a constant, which carries an immediate rather than operands.
428fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
429    let extra = Extra::Imm(func.add_imm(imm));
430    written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
431}
432
433/// Creates the instruction, puts it where those two asked, and reads its value back out.
434fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
435    let span = func.span(inst);
436    let made = func.create_inst(data, &[ty], span);
437    func.insert_before(made, inst);
438    func[made].first_result.expect("an instruction created with one result has one")
439}
440
441/// Turns an instruction into a different one over different operands, in place.
442///
443/// The value the rest of the function reads is the value it already read, so nothing has to be
444/// substituted anywhere, and its type is the one this pass has already given it.
445fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
446    let args = func.push_values(args);
447    let data = &mut func[inst];
448    data.opcode = opcode;
449    data.args = args;
450    data.extra = Extra::None;
451    data.flags = data.flags.intersection(Flags::legal_on(opcode));
452}
453
454#[cfg(test)]
455mod tests {
456    use rucc_base::Interner;
457    use rucc_ir::{Builder, Flags, Func, IntPred, Module, Opcode, Signature, Type};
458    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
459
460    use super::{container, integers};
461
462    fn target() -> TargetInfo {
463        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
464    }
465
466    fn printed(func: &Func, names: &mut Interner) -> String {
467        let module = Module::new(names.intern("w.c"), &target());
468        rucc_ir::print_func(&module, func, names)
469    }
470
471    /// A function of no arguments returning an `int`, with a block to build in.
472    fn shell(names: &mut Interner) -> (Func, rucc_ir::Block) {
473        let int = Type::int(32);
474        let mut func = Func::new(names.intern("f"), Signature::new().with_returns(&[int]));
475        let entry = func.create_block();
476        (func, entry)
477    }
478
479    #[test]
480    fn a_width_is_held_in_the_narrowest_register_that_fits_it() {
481        assert_eq!(container(Type::int(40)), Some(64));
482        assert_eq!(container(Type::int(33)), Some(64));
483        assert_eq!(container(Type::int(17)), Some(32));
484        assert_eq!(container(Type::int(9)), Some(16));
485        assert_eq!(container(Type::int(3)), Some(8));
486        // The widths the machine has, which are left alone.
487        for bits in [1, 8, 16, 32, 64] {
488            assert_eq!(container(Type::int(bits)), None, "{bits} is a width the machine has");
489        }
490        // Above sixty four there is nothing to round up into, and a vector is not a scalar.
491        assert_eq!(container(Type::int(65)), None);
492        assert_eq!(container(Type::int(128)), None);
493        assert_eq!(container(Type::vector(Type::int(40), 2)), None);
494        assert_eq!(container(Type::PTR), None);
495    }
496
497    /// The shape `x.b << 32` has for a forty bit bit-field, which is the program in
498    /// `gcc.c-torture/execute/pr32244-1.c` with the load taken out.
499    #[test]
500    fn a_shift_at_a_width_the_machine_lacks_keeps_the_bits_the_width_has() {
501        let mut names = Interner::new();
502        let (mut func, entry) = shell(&mut names);
503        let narrow = Type::int(40);
504        let mut build = Builder::new(&mut func, entry);
505        let value = build.iconst(narrow, 0x100);
506        let count = build.iconst(narrow, 32);
507        let shifted = build.binary(Opcode::Shl, value, count, Flags::NONE);
508        let wide = build.unary(Opcode::ZExt, shifted, Type::int(64));
509        let answer = build.unary(Opcode::Trunc, wide, Type::int(32));
510        build.ret(&[answer]);
511
512        assert!(integers(&mut func), "there is a width to widen");
513        let text = printed(&func, &mut names);
514        assert!(!text.contains("i40"), "no forty bit value is left: {text}");
515        // The widening became the mask, because both widths are held in the same register and
516        // clearing the spare bits is the whole of what the widening meant.
517        assert_eq!(text.matches(" = and ").count(), 1, "the widening became a mask: {text}");
518        assert!(!text.contains("zext"), "and is no longer a widening: {text}");
519    }
520
521    /// A value the constant check has no answer for, so that shaping it is a real instruction.
522    ///
523    /// A truncation down to one of these widths is one, and it is also the shortest way to make
524    /// one: the pass turns it into a mask, since both widths land in the same register.
525    fn seed(build: &mut Builder<'_>, narrow: Type) -> rucc_ir::Value {
526        let wide = build.iconst(Type::int(64), 5);
527        build.unary(Opcode::Trunc, wide, narrow)
528    }
529
530    /// An arithmetic shift right reads the sign of the narrow value, which is not the sign of the
531    /// register it is in.
532    #[test]
533    fn a_signed_shift_right_spreads_the_sign_the_narrow_value_has() {
534        let mut names = Interner::new();
535        let (mut func, entry) = shell(&mut names);
536        let narrow = Type::int(40);
537        let mut build = Builder::new(&mut func, entry);
538        let value = seed(&mut build, narrow);
539        let count = build.iconst(narrow, 3);
540        let shifted = build.binary(Opcode::AShr, value, count, Flags::NONE);
541        let answer = build.unary(Opcode::Trunc, shifted, Type::int(32));
542        build.ret(&[answer]);
543
544        assert!(integers(&mut func), "there is a width to widen");
545        let text = printed(&func, &mut names);
546        assert!(!text.contains("i40"), "no forty bit value is left: {text}");
547        // A shift up by twenty four and back down, which is what putting the sign of a forty bit
548        // value into the whole of a sixty four bit register is. The count itself is a constant
549        // and is left as it was, since three is three at either width.
550        assert!(text.contains("iconst.i64 24"), "the spare bits are counted: {text}");
551        assert_eq!(text.matches(" = shl ").count(), 1, "shifted up once: {text}");
552        assert_eq!(text.matches(" = ashr ").count(), 2, "and back down, then by three: {text}");
553    }
554
555    /// An unsigned comparison reads the whole register, so its operands have their spare bits
556    /// cleared, and a signed one has the sign put into them instead.
557    #[test]
558    fn a_comparison_shapes_its_operands_the_way_its_predicate_reads_them() {
559        // The seed is a mask already, which answers the unsigned question and not the signed one,
560        // so only the signed predicate pays for anything. The other side is the constant seven,
561        // which is seven at every width and either sign.
562        for (pred, shifts) in [(IntPred::Ult, 0), (IntPred::Eq, 0), (IntPred::Slt, 1)] {
563            let mut names = Interner::new();
564            let (mut func, entry) = shell(&mut names);
565            let narrow = Type::int(33);
566            let mut build = Builder::new(&mut func, entry);
567            let left = seed(&mut build, narrow);
568            let right = build.iconst(narrow, 7);
569            let same = build.icmp(pred, left, right);
570            let answer = build.unary(Opcode::ZExt, same, Type::int(32));
571            build.ret(&[answer]);
572
573            assert!(integers(&mut func), "there is a width to widen");
574            let text = printed(&func, &mut names);
575            assert!(!text.contains("i33"), "no thirty three bit value is left: {text}");
576            assert_eq!(text.matches(" = and ").count(), 1, "{pred:?} masks once: {text}");
577            assert_eq!(text.matches(" = shl ").count(), shifts, "{pred:?} shifts up: {text}");
578        }
579    }
580
581    #[test]
582    fn a_width_that_crosses_the_boundary_is_left_for_the_abi() {
583        let mut names = Interner::new();
584        let narrow = Type::int(40);
585        let mut func = Func::new(
586            names.intern("f"),
587            Signature::new().with_params(&[narrow]).with_returns(&[narrow]),
588        );
589        let entry = func.create_block();
590        let x = func.append_param(entry, narrow);
591        let mut build = Builder::new(&mut func, entry);
592        let one = build.iconst(narrow, 1);
593        let sum = build.binary(Opcode::Add, x, one, Flags::NONE);
594        build.ret(&[sum]);
595
596        assert!(!integers(&mut func), "a parameter at that width is not this pass's to move");
597        let text = printed(&func, &mut names);
598        assert!(text.contains("i40"), "the function is exactly as it was: {text}");
599    }
600
601    #[test]
602    fn a_width_reaching_an_opcode_this_does_not_understand_is_left_alone() {
603        let mut names = Interner::new();
604        let (mut func, entry) = shell(&mut names);
605        let narrow = Type::int(40);
606        let mut build = Builder::new(&mut func, entry);
607        let value = build.iconst(narrow, 3);
608        // A population count is not on the list, so the function goes to the selector as it is and
609        // the selector refuses it by name.
610        let counted = build.unary(Opcode::Ctpop, value, narrow);
611        let answer = build.unary(Opcode::Trunc, counted, Type::int(32));
612        build.ret(&[answer]);
613
614        assert!(!integers(&mut func), "an opcode this has not thought about stops it");
615        let text = printed(&func, &mut names);
616        assert!(text.contains("i40"), "the function is exactly as it was: {text}");
617    }
618
619    #[test]
620    fn a_function_with_nothing_at_such_a_width_is_not_touched() {
621        let mut names = Interner::new();
622        let (mut func, entry) = shell(&mut names);
623        let mut build = Builder::new(&mut func, entry);
624        let value = build.iconst(Type::int(32), 3);
625        build.ret(&[value]);
626
627        assert!(!integers(&mut func), "there is nothing to widen");
628    }
629}