Skip to main content

rucc_codegen/
expand.rs

1//! The IR rewrites the machine needs before a rule can be asked anything.
2//!
3//! Design: `spec/10-backend.md` section 10.2, which is where the ordering comes from.
4//!
5//! Everything else in this crate turns an instruction into instructions. There are two things a
6//! rule cannot do, and each of them is a pass here.
7//!
8//! The first is a new shape of control flow. A rule replaces a term with a term and the
9//! replacement has nowhere to put a block, so a construct that becomes blocks has to be rewritten
10//! before selection rather than during it. There is one such construct today and it is `switch`.
11//! Every other terminator leaves a block with one successor or two, which is what the block layout
12//! writes jumps for, and a `switch` leaves it with as many as the program had cases.
13//!
14//! The second is arithmetic on what a rule matched. A rule may name a constant and pass it along,
15//! and it may not add to one or read it as something else, because the pattern language is a
16//! pattern language and giving it a way to compute would make a rule set a program the solver has
17//! to reason about rather than a table it can check a line of at a time. So an instruction whose
18//! lowering needs a value worked out from another one is rewritten here into instructions whose
19//! lowerings do not, and every one of the four is a float: a float constant, a negation, and the
20//! two conversions between a float and an unsigned integer.
21//!
22//! # Why the chain a `switch` becomes is the backend's and not the front end's
23//!
24//! What a `switch` should become is a target decision and not a language one. A chain of compares
25//! is right for three cases and wrong for two hundred, where the answer is a jump table, and wrong
26//! again for twenty spread over a million, where it is a binary search on the value. A front end
27//! that picked one would be picking for every target at once, and the IR would no longer hold what
28//! the program said. So the `switch` survives as far as here, and here is where it is given up.
29//!
30//! What is written today is the chain, which `spec/10-backend.md` calls the version every compiler
31//! starts with. It is correct for any number of cases and it is slow for a large one. A jump table
32//! wants a read only section to put the table in and a relocation to reach it, and neither exists
33//! yet, so the chain is also the only one that could be written today.
34
35use rucc_ir::{
36    BlockCall, Builder, Extra, Flags, Func, Imm, Inst, InstData, IntPred, Opcode, Type, Value,
37};
38
39/// Rewrites every `switch` in the function into branches, and leaves everything else alone.
40///
41/// The function is changed in place, which is what makes this the last thing that reads the IR as
42/// the front end built it. `--emit=ir` prints before this runs, and nothing after this asks what
43/// the program said, only what the machine has to do.
44pub fn switches(func: &mut Func) {
45    let found: Vec<Inst> = func
46        .blocks()
47        .filter_map(|block| func.terminator(block))
48        .filter(|&inst| func[inst].opcode == Opcode::Switch)
49        .collect();
50    for inst in found {
51        chain(func, inst);
52    }
53}
54
55/// One `switch`, as a compare and a branch for each case in the order they were written.
56///
57/// The block the `switch` was in gets the first compare, and each case after the first gets a
58/// block of its own that the one before it falls to when its compare failed. The last of them
59/// falls to the default, so the default is not a block anything is created for and the chain costs
60/// one block per case less one.
61///
62/// The order is the order the cases are in, which is the order the program wrote them and not a
63/// sorted one. Sorting would be the first half of a binary search and the second half is not here,
64/// so it would cost a reader the ability to look at the assembly and see their own `switch`, and
65/// buy nothing.
66fn chain(func: &mut Func, inst: Inst) {
67    let block = func.block_of(inst).expect("a terminator is in a block");
68    let span = func.span(inst);
69    let Extra::Switch(info) = func[inst].extra else { return };
70    let info = func[info];
71    let value = func[func[inst].args][0];
72    // The lane, because a `switch` on a vector is not a thing C can write and the immediates are
73    // an integer's either way.
74    let ty = func[value].ty.lane();
75    let calls: Vec<BlockCall> = func[info.targets].to_vec();
76    let cases: Vec<Imm> = func[info.cases].to_vec();
77    let Some((default, arms)) = calls.split_first() else { return };
78
79    // Before anything is written, because the builder appends and the `switch` is where the
80    // appending has to happen.
81    func.remove_inst(inst);
82
83    // A `switch` with nothing but a default is a jump, which is worth writing down rather than
84    // refusing: it is what a `switch` whose only label is `default` is, and it is also what one
85    // whose cases were all folded away by a later pass would be.
86    let Some((first, rest)) = arms.split_first() else {
87        let args: Vec<Value> = func[default.args].to_vec();
88        Builder::new(func, block).at(span).jump(default.block, &args);
89        return;
90    };
91
92    let mut at = block;
93    for (index, arm) in std::iter::once(first).chain(rest).enumerate() {
94        let last = index + 1 == arms.len();
95        let next = if last { default.block } else { func.create_block() };
96        let onward: Vec<Value> = if last { func[default.args].to_vec() } else { Vec::new() };
97        let taken: Vec<Value> = func[arm.args].to_vec();
98        let case = cases[index].signed(ty);
99
100        let mut build = Builder::new(func, at).at(span);
101        let want = build.iconst(ty, case);
102        let same = build.icmp(IntPred::Eq, value, want);
103        build.br_if(same, arm.block, &taken, next, &onward);
104        at = next;
105    }
106}
107
108/// Rewrites the float instructions no rule can be written for, and leaves the rest alone.
109///
110/// Each of them needs a value worked out from one the pattern matched, which is the one thing the
111/// rule language deliberately cannot do. A float constant is an integer constant read as a float,
112/// and reading it is arithmetic on the immediate. A negation is an exclusive or with a mask that
113/// depends on the format. A conversion between a float and an integer is that conversion at a
114/// width the machine has, which is a width neither the pattern nor the replacement can work out.
115///
116/// What is left after this is a function whose float instructions are each one machine
117/// instruction, so what a rule is asked stays a table. The one thing that is not rewritten is a
118/// conversion between a float and an unsigned sixty four bit integer, which is refused by name:
119/// there is no signed width that holds those values, so it is not the signed conversion anywhere,
120/// and what it is instead is a compare and a branch that this would have to write blocks for. No
121/// program in the corpus has asked for one yet.
122pub fn floats(func: &mut Func) {
123    let found: Vec<Inst> =
124        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
125    for inst in found {
126        match func[inst].opcode {
127            Opcode::FConst => constant(func, inst),
128            Opcode::FNeg => negate(func, inst),
129            Opcode::SIToFP | Opcode::UIToFP => widen_then_convert(func, inst),
130            Opcode::FPToSI | Opcode::FPToUI => convert_then_narrow(func, inst),
131            _ => {}
132        }
133    }
134}
135
136/// A float constant, as the integer that spells it and a reading of those bits as the float.
137///
138/// This is the whole of what a `movsd` from a literal would be if there were a section to put the
139/// literal in, and there is not one yet. Two instructions in a register beats a constant pool that
140/// nothing else needs, and it is exactly what the bits of the immediate already say, since the IR
141/// holds a float constant as its bit pattern rather than as a number.
142fn constant(func: &mut Func, inst: Inst) {
143    let ty = produced(func, inst);
144    let Extra::Imm(imm) = func[inst].extra else { return };
145    if !ty.is_float() || !ty.is_scalar() {
146        return;
147    }
148    let int = Type::int(ty.bits());
149    let bits = func[imm].bits();
150    // The cast is the bits as they are stored, and `Imm::int` keeps the width, so a constant whose
151    // top bit is set stays the negative integer that spells it rather than becoming a wider one.
152    let spelled = ahead_const(func, inst, Imm::int(bits as i128, int), int);
153    becomes(func, inst, Opcode::Bitcast, &[spelled]);
154}
155
156/// A negation, as an exclusive or with the sign bit.
157///
158/// C says negation flips the sign and says nothing else about it, which is not what subtracting
159/// from zero does to a zero or to a not a number, so this is the operation the IR already calls
160/// out as not being `0 - x`. Flipping the bit is the whole of it, and it is right for every value
161/// a float can hold, the payload of a not a number included, because no other bit is touched.
162///
163/// The bit is flipped in a general purpose register rather than in the one the float is in. The
164/// other way is one instruction rather than three and it wants the mask in memory aligned to the
165/// register, which is the same section a constant pool would need.
166fn negate(func: &mut Func, inst: Inst) {
167    let ty = produced(func, inst);
168    let Some(&arg) = func[func[inst].args].first() else { return };
169    if !ty.is_float() || !ty.is_scalar() {
170        return;
171    }
172    let int = Type::int(ty.bits());
173    let bits = ahead(func, inst, Opcode::Bitcast, &[arg], int);
174    let mask = ahead_const(func, inst, Imm::int(1i128 << (ty.bits() - 1), int), int);
175    let flipped = ahead(func, inst, Opcode::Xor, &[bits, mask], int);
176    becomes(func, inst, Opcode::Bitcast, &[flipped]);
177}
178
179/// An integer becoming a float, as a widening and the signed conversion at a width there is one at.
180///
181/// The widening is with the sign for a signed integer and with zeroes for an unsigned one, and
182/// after it the value is the same number in a signed integer the machine converts from, so the
183/// conversion is the same value and the same rounding. That is the whole of why the machine needs
184/// no unsigned conversion and none at a width narrower than an `int`.
185fn widen_then_convert(func: &mut Func, inst: Inst) {
186    let signed = func[inst].opcode == Opcode::SIToFP;
187    let Some(&arg) = func[func[inst].args].first() else { return };
188    let from = func[arg].ty;
189    if !from.is_int() || !from.is_scalar() {
190        return;
191    }
192    let Some(width) = holder(from.bits(), signed) else { return };
193    if width == from.bits() {
194        return;
195    }
196    let widen = if signed { Opcode::SExt } else { Opcode::ZExt };
197    let wide = ahead(func, inst, widen, &[arg], Type::int(width));
198    becomes(func, inst, Opcode::SIToFP, &[wide]);
199}
200
201/// A float becoming an integer, as the signed conversion at such a width and a narrowing.
202///
203/// The same argument the other way round. A float the program says fits in the integer it asked
204/// for fits in the signed one that holds every value of it, so converting there and keeping the
205/// low bits is that value however it is read, and a float that does not fit is undefined in C and
206/// unspecified in the model at either width.
207fn convert_then_narrow(func: &mut Func, inst: Inst) {
208    let signed = func[inst].opcode == Opcode::FPToSI;
209    let ty = produced(func, inst);
210    let Some(&arg) = func[func[inst].args].first() else { return };
211    if !ty.is_int() || !ty.is_scalar() {
212        return;
213    }
214    let Some(width) = holder(ty.bits(), signed) else { return };
215    if width == ty.bits() {
216        return;
217    }
218    let wide = ahead(func, inst, Opcode::FPToSI, &[arg], Type::int(width));
219    becomes(func, inst, Opcode::Trunc, &[wide]);
220}
221
222/// The width the machine converts at that holds every value of an integer of this one.
223///
224/// The machine converts between a float and a signed integer at thirty two bits and at sixty four
225/// and at no other width, so a conversion anywhere else is one of those two with a widening in
226/// front of it or a narrowing behind it. Which of the two it is, is the narrower one the values
227/// fit in, and an unsigned integer of `bits` bits needs one more bit than that to be signed in.
228///
229/// `None` is a width no signed integer here holds, which is only an unsigned sixty four bit one.
230fn holder(bits: u32, signed: bool) -> Option<u32> {
231    match if signed { bits } else { bits + 1 } {
232        ..=32 => Some(32),
233        33..=64 => Some(64),
234        _ => None,
235    }
236}
237
238/// The type of the one value an instruction produces.
239///
240/// Every opcode this pass touches produces exactly one, so an instruction that produces none is
241/// one the caller has already gone wrong about and the void type says so without panicking.
242fn produced(func: &Func, inst: Inst) -> Type {
243    func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
244}
245
246/// Puts an instruction over these operands in front of another one, and gives back its value.
247fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
248    let args = func.push_values(args);
249    written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
250}
251
252/// The same for a constant, which carries an immediate rather than operands.
253fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
254    let extra = Extra::Imm(func.add_imm(imm));
255    written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
256}
257
258/// Creates the instruction, puts it where those two asked, and reads its value back out.
259fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
260    let span = func.span(inst);
261    let made = func.create_inst(data, &[ty], span);
262    func.insert_before(made, inst);
263    func[made].first_result.expect("an instruction created with one result has one")
264}
265
266/// Turns an instruction into a different one over different operands, in place.
267///
268/// The last instruction of a rewrite is the original rather than a new one, so the value the rest
269/// of the function reads is the value it already read and nothing has to be substituted anywhere.
270/// The type of that value does not change either, because every rewrite here ends at the type it
271/// started at.
272fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
273    let args = func.push_values(args);
274    let data = &mut func[inst];
275    data.opcode = opcode;
276    data.args = args;
277    data.extra = Extra::None;
278    // What the program said about rounding and about not a numbers is still true of the
279    // instructions it became, and what is no longer meaningful is dropped rather than carried.
280    data.flags = data.flags.intersection(Flags::legal_on(opcode));
281}
282
283/// The blocks a chain of `n` cases needs beyond the ones the program already had.
284///
285/// Here so that a test can say the number rather than count it, and so that whoever writes the
286/// jump table has one place to compare against.
287#[must_use]
288pub fn blocks_for(cases: usize) -> usize {
289    cases.saturating_sub(1)
290}
291
292#[cfg(test)]
293mod tests {
294    use rucc_base::Interner;
295    use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
296    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
297
298    use super::{blocks_for, floats, switches};
299
300    fn target() -> TargetInfo {
301        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
302    }
303
304    /// `int sw(int x) { switch (x) { case 1: return 10; case 2: return 20; default: return 30; } }`
305    /// as the walk builds it, which is the program in issue 275.
306    fn built(cases: &[i128]) -> (Interner, Func) {
307        let mut names = Interner::new();
308        let int = Type::int(32);
309        let mut func = Func::new(
310            names.intern("sw"),
311            Signature::new().with_params(&[int]).with_returns(&[int]),
312        );
313        let entry = func.create_block();
314        let x = func.append_param(entry, int);
315
316        let default = func.create_block();
317        let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
318        let table: Vec<(i128, rucc_ir::Block)> =
319            cases.iter().copied().zip(arms.iter().copied()).collect();
320        Builder::new(&mut func, entry).switch(x, default, &table);
321
322        for (index, &arm) in arms.iter().enumerate() {
323            let mut build = Builder::new(&mut func, arm);
324            let what = i128::try_from(index).expect("a small number of cases");
325            let v = build.iconst(int, (what + 1) * 10);
326            build.ret(&[v]);
327        }
328        let mut build = Builder::new(&mut func, default);
329        let v = build.iconst(int, 30);
330        build.ret(&[v]);
331        (names, func)
332    }
333
334    fn count(func: &Func) -> usize {
335        func.blocks().count()
336    }
337
338    fn printed(func: &Func, names: &mut Interner) -> String {
339        let module = Module::new(names.intern("sw.c"), &target());
340        rucc_ir::print_func(&module, func, names)
341    }
342
343    #[test]
344    fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
345        let (mut names, mut func) = built(&[1, 2]);
346        let before = count(&func);
347        switches(&mut func);
348        assert_eq!(count(&func), before + blocks_for(2));
349
350        let text = printed(&func, &mut names);
351        assert!(!text.contains("switch"), "the switch is gone: {text}");
352        assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
353        assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
354    }
355
356    #[test]
357    fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
358        let (_, mut func) = built(&[7]);
359        let before = count(&func);
360        switches(&mut func);
361        // One case needs no chain block at all: the one compare goes to the arm or to the default.
362        assert_eq!(count(&func), before);
363        assert_eq!(blocks_for(1), 0);
364    }
365
366    #[test]
367    fn a_switch_with_only_a_default_is_a_jump() {
368        let (_, mut func) = built(&[]);
369        switches(&mut func);
370        let entry = func.entry().expect("an entry block");
371        let term = func.terminator(entry).expect("a terminator");
372        assert_eq!(func[term].opcode, Opcode::Jump);
373    }
374
375    /// The rewrite has to leave a function the verifier still accepts, since every check it makes
376    /// is one the rest of the back end assumes and none of them is rechecked after this runs.
377    #[test]
378    fn what_comes_out_is_valid_ir() {
379        let (mut names, mut func) = built(&[1, 2, 3, 4]);
380        switches(&mut func);
381        let module = Module::new(names.intern("sw.c"), &target());
382        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
383    }
384
385    /// Nothing else is touched, which matters because this runs over every function whether or not
386    /// one has a `switch` in it.
387    #[test]
388    fn a_function_with_no_switch_is_left_exactly_as_it_was() {
389        let mut names = Interner::new();
390        let int = Type::int(32);
391        let mut func =
392            Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
393        let entry = func.create_block();
394        let x = func.append_param(entry, int);
395        Builder::new(&mut func, entry).ret(&[x]);
396
397        let before = printed(&func, &mut names);
398        switches(&mut func);
399        assert_eq!(printed(&func, &mut names), before);
400    }
401
402    /// A function of one parameter and one result, with a body somebody else writes.
403    ///
404    /// The float rewrites are each one instruction becoming several in the middle of a block, so
405    /// what a test needs is a block with something around the instruction rather than a shape.
406    fn one(
407        params: &[Type],
408        returns: &[Type],
409        body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
410    ) -> (Interner, Func) {
411        let mut names = Interner::new();
412        let mut func = Func::new(
413            names.intern("f"),
414            Signature::new().with_params(params).with_returns(returns),
415        );
416        let entry = func.create_block();
417        let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
418        let mut build = Builder::new(&mut func, entry);
419        body(&mut build, &args);
420        (names, func)
421    }
422
423    fn f64() -> Type {
424        Type::float(Float::F64)
425    }
426
427    fn f32() -> Type {
428        Type::float(Float::F32)
429    }
430
431    /// `double c(void) { return 1.5; }`, which is the constant nothing in the rule set can name.
432    #[test]
433    fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
434        let (mut names, mut func) = one(&[], &[f64()], |build, _| {
435            let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
436            build.ret(&[k]);
437        });
438        floats(&mut func);
439
440        let text = printed(&func, &mut names);
441        assert!(!text.contains("fconst"), "the float constant is gone: {text}");
442        assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
443        assert!(text.contains("bitcast"), "read back as the float: {text}");
444    }
445
446    /// The width follows the format rather than being the widest one, so a `float` constant is an
447    /// `i32` and reaches `movd` rather than `movq`.
448    #[test]
449    fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
450        let (mut names, mut func) = one(&[], &[f32()], |build, _| {
451            let k = build.fconst(f32(), 0x4020_0000);
452            build.ret(&[k]);
453        });
454        floats(&mut func);
455        assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
456    }
457
458    /// `double n(double x) { return -x; }`. Flipping the sign bit is what C means and subtracting
459    /// from zero is not, so what this asserts is the exclusive or and the mask it is given.
460    #[test]
461    fn a_negation_flips_the_sign_bit_and_touches_no_other() {
462        let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
463            let n = build.unary(Opcode::FNeg, args[0], f64());
464            build.ret(&[n]);
465        });
466        floats(&mut func);
467
468        let text = printed(&func, &mut names);
469        assert!(!text.contains("fneg"), "the negation is gone: {text}");
470        assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
471        assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
472        assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
473        assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
474    }
475
476    /// `double u(unsigned x) { return x; }`, which is a widening and the signed conversion.
477    #[test]
478    fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
479        let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
480            let d = build.unary(Opcode::UIToFP, args[0], f64());
481            build.ret(&[d]);
482        });
483        floats(&mut func);
484
485        let text = printed(&func, &mut names);
486        assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
487        assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
488        assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
489    }
490
491    /// `unsigned t(double x) { return x; }`, which is the same argument the other way round.
492    #[test]
493    fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
494        let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
495            let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
496            build.ret(&[n]);
497        });
498        floats(&mut func);
499
500        let text = printed(&func, &mut names);
501        assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
502        assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
503        assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
504    }
505
506    /// `signed char a(double x) { return (signed char)x; }`, which the front end writes as a
507    /// conversion straight to eight bits and the machine has no instruction for at that width.
508    #[test]
509    fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
510        let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
511            let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
512            build.ret(&[n]);
513        });
514        floats(&mut func);
515
516        let text = printed(&func, &mut names);
517        assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
518        assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
519    }
520
521    /// The same the other way, where the widening carries the sign because the value has one.
522    #[test]
523    fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
524        let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
525            let d = build.unary(Opcode::SIToFP, args[0], f64());
526            build.ret(&[d]);
527        });
528        floats(&mut func);
529
530        let text = printed(&func, &mut names);
531        assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
532        assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
533        assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
534    }
535
536    /// The table the two of them share, which is where the whole argument about widths lives.
537    #[test]
538    fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
539        use super::holder;
540        for bits in [1, 8, 16, 32] {
541            assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
542        }
543        assert_eq!(holder(64, true), Some(64));
544        for bits in [1, 8, 16, 31] {
545            assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
546        }
547        // The one more bit an unsigned value needs is what makes these two the wider width.
548        assert_eq!(holder(32, false), Some(64));
549        assert_eq!(holder(64, false), None);
550    }
551
552    /// Sixty four bits is where the argument runs out, because an unsigned value of that width is
553    /// not a signed value of any width the IR has. Both are left for the lowering to refuse by
554    /// name, which is a better message than one about the instructions they would have become.
555    #[test]
556    fn the_unsigned_conversions_at_the_widest_width_are_left_alone() {
557        let (mut names, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
558            let d = build.unary(Opcode::UIToFP, args[0], f64());
559            build.ret(&[d]);
560        });
561        let before = printed(&func, &mut names);
562        floats(&mut func);
563        assert_eq!(printed(&func, &mut names), before);
564
565        let (mut names, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
566            let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
567            build.ret(&[n]);
568        });
569        let before = printed(&func, &mut names);
570        floats(&mut func);
571        assert_eq!(printed(&func, &mut names), before);
572    }
573
574    /// The same obligation the `switch` rewrite has, for the same reason: nothing after this
575    /// checks the IR again and everything after it assumes what the verifier would have said.
576    #[test]
577    fn what_the_float_rewrites_leave_is_valid_ir() {
578        let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
579            let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
580            let d = build.unary(Opcode::UIToFP, args[0], f64());
581            let n = build.unary(Opcode::FNeg, d, f64());
582            let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
583            build.ret(&[s]);
584        });
585        floats(&mut func);
586        let module = Module::new(names.intern("f.c"), &target());
587        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
588    }
589
590    /// Nothing else is touched, for the same reason the `switch` pass has that test: this runs
591    /// over every function whether or not one has a float in it.
592    #[test]
593    fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
594        let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
595            build.ret(&[args[0]]);
596        });
597        let before = printed(&func, &mut names);
598        floats(&mut func);
599        assert_eq!(printed(&func, &mut names), before);
600    }
601}