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 what is done about each of them instead is 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. Four of them are floats: a float constant, a negation, and the two conversions
20//! between a float and an unsigned integer. The other two move a block of memory, where the
21//! arithmetic is the offset of each word from the front of it.
22//!
23//! # Why a copy is a run of moves and not a call
24//!
25//! A `memcpy` in the IR is not a call to `memcpy`. It is what the front end writes for a structure
26//! assigned, passed or returned by value, and a `memset` is what it writes for the part of an
27//! object an initialiser left unnamed, so a program with a `struct` in it reaches one almost at
28//! once and the size is a constant every time.
29//!
30//! A constant size is what makes the moves the right answer. A four byte copy written as a call
31//! costs the call and the two arguments and gives back four bytes moved, which is more instructions
32//! than the move it replaced and slower than all of them. Every real compiler writes the moves
33//! under some threshold for that reason, and above the threshold writes the call, which is where
34//! this stops: the call needs a `memcpy` to exist, and a statically linked program has nowhere to
35//! get one from until the compiler runtime in tamnd/rucc#277 exists. So a copy larger than the
36//! threshold is refused by name rather than written wrong.
37//!
38//! # Why the chain a `switch` becomes is the backend's and not the front end's
39//!
40//! What a `switch` should become is a target decision and not a language one. A chain of compares
41//! is right for three cases and wrong for two hundred, where the answer is a jump table, and wrong
42//! again for twenty spread over a million, where it is a binary search on the value. A front end
43//! that picked one would be picking for every target at once, and the IR would no longer hold what
44//! the program said. So the `switch` survives as far as here, and here is where it is given up.
45//!
46//! What is written today is the chain, which `spec/10-backend.md` calls the version every compiler
47//! starts with. It is correct for any number of cases and it is slow for a large one. A jump table
48//! wants a read only section to put the table in and a relocation to reach it, and neither exists
49//! yet, so the chain is also the only one that could be written today.
50
51use rucc_base::Interner;
52use rucc_ir::{
53    BlockCall, Builder, CallInfo, Def, Extra, Flags, Func, Imm, Inst, InstData, IntPred, MemInfo,
54    Opcode, Signature, Type, Value,
55};
56
57/// Rewrites every `switch` in the function into branches, and leaves everything else alone.
58///
59/// The function is changed in place, which is what makes this the last thing that reads the IR as
60/// the front end built it. `--emit=ir` prints before this runs, and nothing after this asks what
61/// the program said, only what the machine has to do.
62pub fn switches(func: &mut Func) {
63    let found: Vec<Inst> = func
64        .blocks()
65        .filter_map(|block| func.terminator(block))
66        .filter(|&inst| func[inst].opcode == Opcode::Switch)
67        .collect();
68    for inst in found {
69        chain(func, inst);
70    }
71}
72
73/// One `switch`, as a compare and a branch for each case in the order they were written.
74///
75/// The block the `switch` was in gets the first compare, and each case after the first gets a
76/// block of its own that the one before it falls to when its compare failed. The last of them
77/// falls to the default, so the default is not a block anything is created for and the chain costs
78/// one block per case less one.
79///
80/// The order is the order the cases are in, which is the order the program wrote them and not a
81/// sorted one. Sorting would be the first half of a binary search and the second half is not here,
82/// so it would cost a reader the ability to look at the assembly and see their own `switch`, and
83/// buy nothing.
84fn chain(func: &mut Func, inst: Inst) {
85    let block = func.block_of(inst).expect("a terminator is in a block");
86    let span = func.span(inst);
87    let Extra::Switch(info) = func[inst].extra else { return };
88    let info = func[info];
89    let value = func[func[inst].args][0];
90    // The lane, because a `switch` on a vector is not a thing C can write and the immediates are
91    // an integer's either way.
92    let ty = func[value].ty.lane();
93    let calls: Vec<BlockCall> = func[info.targets].to_vec();
94    let cases: Vec<Imm> = func[info.cases].to_vec();
95    let Some((default, arms)) = calls.split_first() else { return };
96
97    // Before anything is written, because the builder appends and the `switch` is where the
98    // appending has to happen.
99    func.remove_inst(inst);
100
101    // A `switch` with nothing but a default is a jump, which is worth writing down rather than
102    // refusing: it is what a `switch` whose only label is `default` is, and it is also what one
103    // whose cases were all folded away by a later pass would be.
104    let Some((first, rest)) = arms.split_first() else {
105        let args: Vec<Value> = func[default.args].to_vec();
106        Builder::new(func, block).at(span).jump(default.block, &args);
107        return;
108    };
109
110    let mut at = block;
111    for (index, arm) in std::iter::once(first).chain(rest).enumerate() {
112        let last = index + 1 == arms.len();
113        let next = if last { default.block } else { func.create_block() };
114        let onward: Vec<Value> = if last { func[default.args].to_vec() } else { Vec::new() };
115        let taken: Vec<Value> = func[arm.args].to_vec();
116        let case = cases[index].signed(ty);
117
118        let mut build = Builder::new(func, at).at(span);
119        let want = build.iconst(ty, case);
120        let same = build.icmp(IntPred::Eq, value, want);
121        build.br_if(same, arm.block, &taken, next, &onward);
122        at = next;
123    }
124}
125
126/// Rewrites the float instructions no rule can be written for, and leaves the rest alone.
127///
128/// Each of them needs a value worked out from one the pattern matched, which is the one thing the
129/// rule language deliberately cannot do. A float constant is an integer constant read as a float,
130/// and reading it is arithmetic on the immediate. A negation is an exclusive or with a mask that
131/// depends on the format. A conversion between a float and an integer is that conversion at a
132/// width the machine has, which is a width neither the pattern nor the replacement can work out.
133///
134/// What is left after this is a function whose float instructions are each one machine
135/// instruction, so what a rule is asked stays a table. The one thing that is not rewritten is a
136/// conversion between a float and an unsigned sixty four bit integer, which is refused by name:
137/// there is no signed width that holds those values, so it is not the signed conversion anywhere,
138/// and what it is instead is a compare and a branch that this would have to write blocks for. No
139/// program in the corpus has asked for one yet.
140pub fn floats(func: &mut Func) {
141    let found: Vec<Inst> =
142        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
143    for inst in found {
144        match func[inst].opcode {
145            Opcode::FConst => constant(func, inst),
146            Opcode::FNeg => negate(func, inst),
147            Opcode::SIToFP | Opcode::UIToFP => widen_then_convert(func, inst),
148            Opcode::FPToSI | Opcode::FPToUI => convert_then_narrow(func, inst),
149            _ => {}
150        }
151    }
152}
153
154/// A float constant, as the integer that spells it and a reading of those bits as the float.
155///
156/// This is the whole of what a `movsd` from a literal would be if there were a section to put the
157/// literal in, and there is not one yet. Two instructions in a register beats a constant pool that
158/// nothing else needs, and it is exactly what the bits of the immediate already say, since the IR
159/// holds a float constant as its bit pattern rather than as a number.
160fn constant(func: &mut Func, inst: Inst) {
161    let ty = produced(func, inst);
162    let Extra::Imm(imm) = func[inst].extra else { return };
163    if !ty.is_float() || !ty.is_scalar() {
164        return;
165    }
166    let int = Type::int(ty.bits());
167    let bits = func[imm].bits();
168    // The cast is the bits as they are stored, and `Imm::int` keeps the width, so a constant whose
169    // top bit is set stays the negative integer that spells it rather than becoming a wider one.
170    let spelled = ahead_const(func, inst, Imm::int(bits as i128, int), int);
171    becomes(func, inst, Opcode::Bitcast, &[spelled]);
172}
173
174/// A negation, as an exclusive or with the sign bit.
175///
176/// C says negation flips the sign and says nothing else about it, which is not what subtracting
177/// from zero does to a zero or to a not a number, so this is the operation the IR already calls
178/// out as not being `0 - x`. Flipping the bit is the whole of it, and it is right for every value
179/// a float can hold, the payload of a not a number included, because no other bit is touched.
180///
181/// The bit is flipped in a general purpose register rather than in the one the float is in. The
182/// other way is one instruction rather than three and it wants the mask in memory aligned to the
183/// register, which is the same section a constant pool would need.
184fn negate(func: &mut Func, inst: Inst) {
185    let ty = produced(func, inst);
186    let Some(&arg) = func[func[inst].args].first() else { return };
187    if !ty.is_float() || !ty.is_scalar() {
188        return;
189    }
190    let int = Type::int(ty.bits());
191    let bits = ahead(func, inst, Opcode::Bitcast, &[arg], int);
192    let mask = ahead_const(func, inst, Imm::int(1i128 << (ty.bits() - 1), int), int);
193    let flipped = ahead(func, inst, Opcode::Xor, &[bits, mask], int);
194    becomes(func, inst, Opcode::Bitcast, &[flipped]);
195}
196
197/// An integer becoming a float, as a widening and the signed conversion at a width there is one at.
198///
199/// The widening is with the sign for a signed integer and with zeroes for an unsigned one, and
200/// after it the value is the same number in a signed integer the machine converts from, so the
201/// conversion is the same value and the same rounding. That is the whole of why the machine needs
202/// no unsigned conversion and none at a width narrower than an `int`.
203fn widen_then_convert(func: &mut Func, inst: Inst) {
204    let signed = func[inst].opcode == Opcode::SIToFP;
205    let Some(&arg) = func[func[inst].args].first() else { return };
206    let from = func[arg].ty;
207    if !from.is_int() || !from.is_scalar() {
208        return;
209    }
210    let Some(width) = holder(from.bits(), signed) else { return };
211    if width == from.bits() {
212        return;
213    }
214    let widen = if signed { Opcode::SExt } else { Opcode::ZExt };
215    let wide = ahead(func, inst, widen, &[arg], Type::int(width));
216    becomes(func, inst, Opcode::SIToFP, &[wide]);
217}
218
219/// A float becoming an integer, as the signed conversion at such a width and a narrowing.
220///
221/// The same argument the other way round. A float the program says fits in the integer it asked
222/// for fits in the signed one that holds every value of it, so converting there and keeping the
223/// low bits is that value however it is read, and a float that does not fit is undefined in C and
224/// unspecified in the model at either width.
225fn convert_then_narrow(func: &mut Func, inst: Inst) {
226    let signed = func[inst].opcode == Opcode::FPToSI;
227    let ty = produced(func, inst);
228    let Some(&arg) = func[func[inst].args].first() else { return };
229    if !ty.is_int() || !ty.is_scalar() {
230        return;
231    }
232    let Some(width) = holder(ty.bits(), signed) else { return };
233    if width == ty.bits() {
234        return;
235    }
236    let wide = ahead(func, inst, Opcode::FPToSI, &[arg], Type::int(width));
237    becomes(func, inst, Opcode::Trunc, &[wide]);
238}
239
240/// Rewrites every byte swap into the shifts and masks that are one, and leaves the rest alone.
241///
242/// A byte swap is a rule on a machine that has the instruction and this everywhere else, and until
243/// `x64.bswap` is a term the model knows about, this is what x86-64 gets too. That is tamnd/rucc#307
244/// and the whole of what is left of it: what is written below is correct at every width and slower
245/// than the one instruction, which is the trade `spec/10-backend.md` section 10.3 says the fast path
246/// makes everywhere.
247///
248/// It is here rather than in the front end because the masks are worked out from the width, and
249/// arithmetic on a value a pattern matched is the one thing the rule language deliberately cannot
250/// do. It is here rather than in the walk to the IR because a byte swap is one instruction in the
251/// IR and should stay one for as long as anything is reading the IR, so that the day the rule
252/// exists nothing above the backend has to change.
253pub fn bytes(func: &mut Func) {
254    let found: Vec<Inst> =
255        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
256    for inst in found {
257        if func[inst].opcode == Opcode::Bswap {
258            swap(func, inst);
259        }
260    }
261}
262
263/// One byte swap, as a halving run of swaps of adjacent groups of bits.
264///
265/// Reversing eight bytes is swapping the two halves, then the two halves of each half, then the two
266/// bytes of each of those, and the three steps commute because each is a permutation of positions
267/// the others do not touch. So the run goes from the widest group down to a byte, and every step is
268/// the same five instructions: keep the even numbered groups, move them up, move the odd numbered
269/// ones down, keep those, and put the two together.
270///
271/// Nine instructions for two bytes, seventeen for four, twenty five for eight, before the constants.
272/// Writing it as a shift and a mask per byte instead is fewer steps to read and more instructions at
273/// every width above two, since the cost there grows with the number of bytes rather than with the
274/// logarithm of it.
275///
276/// A width that is not a whole number of bytes is left alone. The verifier does not allow one, and
277/// silently reversing something else would be worse than the instruction surviving to a selector
278/// that has no rule for it and says so.
279fn swap(func: &mut Func, inst: Inst) {
280    let ty = produced(func, inst);
281    let Some(&arg) = func[func[inst].args].first() else { return };
282    if !ty.is_int() || !ty.is_scalar() || ty.bits() < 16 || ty.bits() % 8 != 0 {
283        return;
284    }
285
286    let mut value = arg;
287    let mut group = ty.bits() / 2;
288    while group >= 8 {
289        // The pattern that keeps every other run of `group` bits, counting the run at the bottom as
290        // the first one kept. It is what says which half of each pair moves up and which moves down.
291        let mask = alternating(ty.bits(), group);
292        let keep = ahead_const(func, inst, Imm::int(mask, ty), ty);
293        let count = ahead_const(func, inst, Imm::int(i128::from(group), ty), ty);
294        let low = ahead(func, inst, Opcode::And, &[value, keep], ty);
295        let up = ahead(func, inst, Opcode::Shl, &[low, count], ty);
296        let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
297        let high = ahead(func, inst, Opcode::And, &[down, keep], ty);
298        // The last step of the last round is the instruction itself, so the value everything
299        // downstream already reads is the answer and nothing has to be substituted.
300        if group == 8 {
301            becomes(func, inst, Opcode::Or, &[up, high]);
302            return;
303        }
304        value = ahead(func, inst, Opcode::Or, &[up, high], ty);
305        group /= 2;
306    }
307}
308
309/// The mask that keeps every other run of `group` bits out of `width` of them, starting with the
310/// run at the bottom.
311///
312/// Sixteen bits in groups of eight is `0x00ff`, thirty two in groups of eight is `0x00ff00ff`, and
313/// thirty two in groups of sixteen is `0x0000ffff`. Built rather than written down because there is
314/// one of these per width per group and a table of them is a table to get wrong.
315///
316/// The top group is always one of the dropped ones, since the run at the bottom is kept and the
317/// width is an even number of groups, so the answer never has its sign bit set and reads the same
318/// as a number as it does as a pattern.
319fn alternating(width: u32, group: u32) -> i128 {
320    every(width, group * 2, group)
321}
322
323/// The pattern with the low `run` bits of every `step` bit group set, out of `width` of them.
324///
325/// `every(32, 2, 1)` is `0x55555555` and `every(64, 8, 1)` is `0x0101010101010101`. Built rather
326/// than written down for the reason the byte swap masks are: there is one of these per width per
327/// group and a table of them is a table to get wrong.
328///
329/// The top group is never a full one when `run` is less than `step`, so the answer never has its
330/// sign bit set and reads the same as a number as it does as a pattern.
331fn every(width: u32, step: u32, run: u32) -> i128 {
332    let ones = (1i128 << run) - 1;
333    let mut mask = 0i128;
334    let mut at = 0;
335    while at < width {
336        mask |= ones << at;
337        at += step;
338    }
339    mask
340}
341
342/// Rewrites every bit count into the arithmetic that is one, and leaves the rest alone.
343///
344/// Three instructions and no rules, which is tamnd/rucc#310. `popcnt` is one instruction on a
345/// machine that has it and `bsr` and `bsf` are the two searches, and none of the three is a term the
346/// model knows about yet, so what runs today is what runs everywhere. The trade is the one
347/// `spec/10-backend.md` section 10.3 describes and `expand::bytes` above makes for the same reason:
348/// slower than the instruction, right on every target, and built only out of rules the verifier has
349/// already discharged.
350///
351/// The two searches are rewritten first, into a set bit count and a little arithmetic, and then
352/// every set bit count is rewritten. That is one pass rather than two because the second sweep picks
353/// up what the first one wrote, and it means there is one place that knows how to count bits rather
354/// than three.
355pub fn counts(func: &mut Func) {
356    let found: Vec<Inst> =
357        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
358    for inst in found {
359        match func[inst].opcode {
360            Opcode::Ctlz => searched(func, inst, true),
361            Opcode::Cttz => searched(func, inst, false),
362            _ => {}
363        }
364    }
365    let found: Vec<Inst> =
366        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
367    for inst in found {
368        if func[inst].opcode == Opcode::Ctpop {
369            counted(func, inst);
370        }
371    }
372}
373
374/// A leading or trailing zero count, as the set bit count of a value with those zeroes turned into
375/// the only bits that are set.
376///
377/// For trailing zeroes that is `~x & (x - 1)`, which is exactly the run of zeroes below the lowest
378/// set bit and nothing else, because `x - 1` sets that run and clears the bit above it while `~x`
379/// keeps only positions `x` did not have.
380///
381/// For leading zeroes it is the same idea upside down. Smearing every set bit downwards, by folding
382/// the value into itself shifted right by one, two, four and so on, leaves ones everywhere at or
383/// below the highest set bit, so the complement is exactly the leading zeroes. That is five extra
384/// steps at thirty two bits and six at sixty four, which is why the search instruction is worth
385/// having and why #310 stays open for it.
386///
387/// Both answer the width for a zero argument, which is what they have to answer for `ffs` to be
388/// masked correctly and is more than C asks for: `__builtin_clz(0)` and `__builtin_ctz(0)` are
389/// undefined, so nothing may rely on this, and the point of writing it down is that it is defined
390/// here rather than being whatever a register happened to hold.
391fn searched(func: &mut Func, inst: Inst, leading: bool) {
392    let ty = produced(func, inst);
393    let Some(&arg) = func[func[inst].args].first() else { return };
394    if !countable(ty) {
395        return;
396    }
397    let ones = ahead_const(func, inst, Imm::int(-1, ty), ty);
398    if leading {
399        let mut value = arg;
400        let mut by = 1;
401        while by < ty.bits() {
402            let count = ahead_const(func, inst, Imm::int(i128::from(by), ty), ty);
403            let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
404            value = ahead(func, inst, Opcode::Or, &[value, down], ty);
405            by *= 2;
406        }
407        let above = ahead(func, inst, Opcode::Xor, &[value, ones], ty);
408        becomes(func, inst, Opcode::Ctpop, &[above]);
409        return;
410    }
411    let missing = ahead(func, inst, Opcode::Xor, &[arg, ones], ty);
412    let less = ahead(func, inst, Opcode::Add, &[arg, ones], ty);
413    let below = ahead(func, inst, Opcode::And, &[missing, less], ty);
414    becomes(func, inst, Opcode::Ctpop, &[below]);
415}
416
417/// One set bit count, as the halving sum every bit counting routine is written as.
418///
419/// Adjacent bits are added into pairs, pairs into nibbles, nibbles into bytes, and then the bytes
420/// are added together at once by a multiply whose top byte is their sum. The first step is written
421/// as a subtraction rather than as two masks and an add, which is the usual form and is one
422/// instruction shorter: a two bit field minus its own high bit is the number of bits set in it.
423///
424/// Twelve instructions and four constants at sixty four bits, against one `popcnt`, which is the
425/// size of what #310 is worth.
426///
427/// The multiply is the last step only because the byte sums are each at most eight and there are at
428/// most eight of them, so the running total in the top byte cannot carry out of it. At eight bits
429/// there are no bytes to add and the third step is already the answer.
430fn counted(func: &mut Func, inst: Inst) {
431    let ty = produced(func, inst);
432    let Some(&arg) = func[func[inst].args].first() else { return };
433    if !countable(ty) {
434        return;
435    }
436    let width = ty.bits();
437    let pairs = ahead_const(func, inst, Imm::int(alternating(width, 1), ty), ty);
438    let two = ahead_const(func, inst, Imm::int(2, ty), ty);
439    let one = ahead_const(func, inst, Imm::int(1, ty), ty);
440    let high = ahead(func, inst, Opcode::LShr, &[arg, one], ty);
441    let odd = ahead(func, inst, Opcode::And, &[high, pairs], ty);
442    let bits = ahead(func, inst, Opcode::Sub, &[arg, odd], ty);
443
444    let quads = ahead_const(func, inst, Imm::int(alternating(width, 2), ty), ty);
445    let low = ahead(func, inst, Opcode::And, &[bits, quads], ty);
446    let up = ahead(func, inst, Opcode::LShr, &[bits, two], ty);
447    let rest = ahead(func, inst, Opcode::And, &[up, quads], ty);
448    let nibbles = ahead(func, inst, Opcode::Add, &[low, rest], ty);
449
450    let four = ahead_const(func, inst, Imm::int(4, ty), ty);
451    let bytes = ahead_const(func, inst, Imm::int(alternating(width, 4), ty), ty);
452    let folded = ahead(func, inst, Opcode::LShr, &[nibbles, four], ty);
453    let summed = ahead(func, inst, Opcode::Add, &[nibbles, folded], ty);
454    if width == 8 {
455        becomes(func, inst, Opcode::And, &[summed, bytes]);
456        return;
457    }
458    let held = ahead(func, inst, Opcode::And, &[summed, bytes], ty);
459
460    let spread = ahead_const(func, inst, Imm::int(every(width, 8, 1), ty), ty);
461    let top = ahead_const(func, inst, Imm::int(i128::from(width - 8), ty), ty);
462    let total = ahead(func, inst, Opcode::Mul, &[held, spread], ty);
463    becomes(func, inst, Opcode::LShr, &[total, top]);
464}
465
466/// Whether the arithmetic below counts correctly at this type.
467///
468/// A whole number of bytes and a power of two of them, which every width the front end can ask about
469/// is. Anything else is left as the instruction it was, so a selector with no rule for it says so
470/// rather than the program getting a number that was counted in the wrong shape.
471fn countable(ty: Type) -> bool {
472    ty.is_int()
473        && ty.is_scalar()
474        && ty.bits() >= 8
475        && ty.bits() <= 64
476        && ty.bits().is_power_of_two()
477}
478
479/// The most moves a copy or a fill becomes before it is left alone for a call instead.
480///
481/// Thirty two, which is two hundred and fifty six bytes at a word a time and is a structure larger
482/// than almost every one a program writes. What the number is trading is code size against a call,
483/// and the exchange rate is a machine's rather than a language's, so the number lives here next to
484/// the code it bounds and not in a target description that would have to be right about it for
485/// every target at once.
486///
487/// It is a count of moves and not a count of bytes because that is what the cost is. A copy of
488/// sixty four bytes between two addresses aligned to eight is eight moves and a copy of the same
489/// sixty four bytes between two addresses aligned to one is sixty four, and the second is the
490/// expensive one whatever the size says.
491pub const UNROLL: usize = 32;
492
493/// Rewrites every bulk copy and bulk fill, into moves when that is worth it and into a call to the
494/// runtime when it is not.
495///
496/// A copy of more than [`UNROLL`] moves becomes a call, and so does a fill whose byte is not a
497/// constant, which the front end does not write today and which would need the byte spread across
498/// a word at runtime. A `memmove` is always a call, because the two sides may overlap and a run of
499/// moves in one direction is only right for one of the two ways they can.
500///
501/// `word` is how many bytes the widest move on this machine carries. Nothing here reads a target
502/// otherwise, and a copy is the same run of loads and stores everywhere.
503pub fn bulk(func: &mut Func, names: &mut Interner, word: u32) {
504    let found: Vec<Inst> =
505        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
506    for inst in found {
507        match func[inst].opcode {
508            Opcode::Memcpy => copy(func, names, inst, word),
509            Opcode::Memset => fill(func, names, inst, word),
510            Opcode::Memmove => library(func, names, inst, "memmove", word),
511            _ => {}
512        }
513    }
514}
515
516/// One `memcpy`, as a load and a store for each word of it.
517///
518/// Each word is read and then written before the next is read, rather than every read being built
519/// before any write the way [`crate::varargs`] copies a list. A `memcpy` is the copy whose two
520/// sides the front end promises do not overlap, so what is at the source when the last word is read
521/// is what was there when the first was, and reading a word at a time costs one register where
522/// reading all of them first would cost as many registers as the copy has words.
523fn copy(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
524    let [into, from] = func[func[inst].args] else { return };
525    let Extra::Mem(mem) = func[inst].extra else { return };
526    let info = func[mem];
527    let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memcpy", word) };
528    for (at, width) in plan {
529        let ty = Type::int(width * 8);
530        let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
531        let there = stepped(func, inst, from, at);
532        let word = read(func, inst, there, access, ty);
533        let here = stepped(func, inst, into, at);
534        write(func, inst, word, here, access);
535    }
536    func.remove_inst(inst);
537}
538
539/// One `memset`, as a store of the byte spread across each word of it.
540///
541/// The byte is a constant, so the word it spreads into is a constant too and the spreading is done
542/// here rather than by the program. The front end writes a `memset` for the part of an object an
543/// initialiser did not name, where the byte is always zero, and the general case is written anyway
544/// because the arithmetic is the same and being right about `0xff` costs nothing.
545fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
546    let [into, byte] = func[func[inst].args] else { return };
547    let Extra::Mem(mem) = func[inst].extra else { return };
548    let info = func[mem];
549    let Some(spelled) = literal(func, byte) else {
550        return library(func, names, inst, "memset", word);
551    };
552    let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memset", word) };
553    for (at, width) in plan {
554        let ty = Type::int(width * 8);
555        let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
556        let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
557        let here = stepped(func, inst, into, at);
558        write(func, inst, value, here, access);
559    }
560    func.remove_inst(inst);
561}
562
563/// One bulk operation as a call to the routine of that name in the runtime.
564///
565/// This is what a copy too large to unroll becomes, and what a `memmove` and a fill with a
566/// computed byte become whatever their size. The routine is `rucc-builtins`' on a freestanding
567/// target and the C library's on a hosted one, and the call is the same either way because the two
568/// have the same names and the same signatures on purpose.
569///
570/// The arguments are the C ones and not the IR ones. The IR holds the size beside the instruction
571/// where C passes it, and holds a fill byte as a byte where C passes an `int`, so the size becomes
572/// a constant in a register and the byte is widened. The value each returns is its first argument,
573/// which nothing reads, so the call is built as returning nothing rather than as returning a
574/// pointer nobody looks at.
575fn library(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, word: u32) {
576    let [into, second] = func[func[inst].args] else { return };
577    let Extra::Mem(mem) = func[inst].extra else { return };
578    let size = func[mem].size;
579
580    // `size_t`, which is as wide as a general purpose register on every target here. Taken from
581    // the machine rather than written as sixty four so that a thirty two bit target gets the
582    // argument its own C library declares.
583    let words = Type::int(word * 8);
584    let count = ahead_const(func, inst, Imm::int(i128::from(size), words), words);
585    // A fill passes an `int` where the IR passes the byte itself, and the widening is a zero
586    // extension because the routine looks at the low eight bits and nothing else.
587    let second = match routine {
588        "memset" => widened(func, inst, second),
589        _ => second,
590    };
591
592    let sig = func.add_signature(Signature::new().with_params(&[
593        Type::PTR,
594        if routine == "memset" { Type::int(32) } else { Type::PTR },
595        words,
596    ]));
597    let callee = names.intern(routine);
598    let varargs = func.push_abis(&[]);
599    let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
600    let args = func.push_values(&[into, second, count]);
601    let data = &mut func[inst];
602    data.opcode = Opcode::Call;
603    data.args = args;
604    data.extra = Extra::Call(info);
605    data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
606}
607
608/// A value widened to an `int`, or the value itself when it is one already.
609fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
610    let int = Type::int(32);
611    let ty = func[value].ty;
612    if ty == int {
613        return value;
614    }
615    ahead(func, inst, Opcode::ZExt, &[value], int)
616}
617
618/// Where each word of a block of memory starts and how wide it is, or nothing for a block that is
619/// more words than [`UNROLL`].
620///
621/// The widest word is the smaller of what the machine moves at once and what the block is known to
622/// be aligned to, because a load wider than the alignment is a fault on a machine that checks and
623/// this pass does not know whether the one it is compiling for does. That costs a copy of a
624/// character array a move per byte, which is exactly the copy the threshold sends to a call.
625///
626/// The width halves whenever what is left is narrower than it, so a block of thirteen bytes aligned
627/// to eight is eight, four and one rather than thirteen ones. Every offset is a multiple of the
628/// width at it, since each width divides the sum of the wider ones in front of it, which is what
629/// lets the alignment of each access be written down as the width.
630fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
631    plan(info.size, info.align, word)
632}
633
634/// The same, as the two numbers rather than as an access, for the one caller that has no access to
635/// ask about.
636///
637/// [`crate::abi`] copies a structure passed by value into the argument area, and that copy is not a
638/// `memcpy` in the IR: it is written straight into the machine IR, because where it goes is an
639/// offset the placement walk gives and nothing before this pass knows it. The plan has to be the
640/// same plan either way, so it is one function.
641pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
642    let widest = word.min(align).max(1);
643    if !widest.is_power_of_two() {
644        return None;
645    }
646    let mut plan = Vec::new();
647    let mut at = 0;
648    let mut width = u64::from(widest);
649    while at < size {
650        while width > size - at {
651            width /= 2;
652        }
653        plan.push((at, u32::try_from(width).ok()?));
654        at += width;
655        if plan.len() > UNROLL {
656            return None;
657        }
658    }
659    Some(plan)
660}
661
662/// The byte a fill writes, when the program said which one rather than working it out.
663fn literal(func: &Func, value: Value) -> Option<u8> {
664    let Def::Result { inst, .. } = func[value].def else { return None };
665    if func[inst].opcode != Opcode::IConst {
666        return None;
667    }
668    let Extra::Imm(imm) = func[inst].extra else { return None };
669    u8::try_from(func[imm].bits() & 0xff).ok()
670}
671
672/// One byte repeated across a word of that many bytes, which is what a fill stores.
673fn spread(byte: u8, width: u32) -> u64 {
674    (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
675}
676
677/// The address that far into a block, written in front of an instruction, or the block itself for
678/// the word at the front of it.
679fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
680    if at == 0 {
681        return block;
682    }
683    let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
684    ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
685}
686
687/// A load put in front of an instruction, and the value it reads.
688fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
689    let extra = Extra::Mem(func.add_mem(info));
690    let args = func.push_values(&[from]);
691    written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
692}
693
694/// A store put in front of an instruction, which produces nothing and is only its effect.
695fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
696    let span = func.span(inst);
697    let extra = Extra::Mem(func.add_mem(info));
698    let args = func.push_values(&[value, into]);
699    let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
700    let made = func.create_inst(data, &[], span);
701    func.insert_before(made, inst);
702}
703
704/// The width the machine converts at that holds every value of an integer of this one.
705///
706/// The machine converts between a float and a signed integer at thirty two bits and at sixty four
707/// and at no other width, so a conversion anywhere else is one of those two with a widening in
708/// front of it or a narrowing behind it. Which of the two it is, is the narrower one the values
709/// fit in, and an unsigned integer of `bits` bits needs one more bit than that to be signed in.
710///
711/// `None` is a width no signed integer here holds, which is only an unsigned sixty four bit one.
712fn holder(bits: u32, signed: bool) -> Option<u32> {
713    match if signed { bits } else { bits + 1 } {
714        ..=32 => Some(32),
715        33..=64 => Some(64),
716        _ => None,
717    }
718}
719
720/// The type of the one value an instruction produces.
721///
722/// Every opcode this pass touches produces exactly one, so an instruction that produces none is
723/// one the caller has already gone wrong about and the void type says so without panicking.
724fn produced(func: &Func, inst: Inst) -> Type {
725    func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
726}
727
728/// Puts an instruction over these operands in front of another one, and gives back its value.
729fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
730    let args = func.push_values(args);
731    written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
732}
733
734/// The same for a constant, which carries an immediate rather than operands.
735fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
736    let extra = Extra::Imm(func.add_imm(imm));
737    written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
738}
739
740/// Creates the instruction, puts it where those two asked, and reads its value back out.
741fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
742    let span = func.span(inst);
743    let made = func.create_inst(data, &[ty], span);
744    func.insert_before(made, inst);
745    func[made].first_result.expect("an instruction created with one result has one")
746}
747
748/// Turns an instruction into a different one over different operands, in place.
749///
750/// The last instruction of a rewrite is the original rather than a new one, so the value the rest
751/// of the function reads is the value it already read and nothing has to be substituted anywhere.
752/// The type of that value does not change either, because every rewrite here ends at the type it
753/// started at.
754fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
755    let args = func.push_values(args);
756    let data = &mut func[inst];
757    data.opcode = opcode;
758    data.args = args;
759    data.extra = Extra::None;
760    // What the program said about rounding and about not a numbers is still true of the
761    // instructions it became, and what is no longer meaningful is dropped rather than carried.
762    data.flags = data.flags.intersection(Flags::legal_on(opcode));
763}
764
765/// The blocks a chain of `n` cases needs beyond the ones the program already had.
766///
767/// Here so that a test can say the number rather than count it, and so that whoever writes the
768/// jump table has one place to compare against.
769#[must_use]
770pub fn blocks_for(cases: usize) -> usize {
771    cases.saturating_sub(1)
772}
773
774#[cfg(test)]
775mod tests {
776    use rucc_base::Interner;
777    use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
778    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
779
780    use rucc_ir::{Extra, InstData, MemInfo, MemOrder, Restrict};
781
782    use super::{
783        UNROLL, alternating, blocks_for, bulk, bytes, chunks, counts, every, floats, spread,
784        switches,
785    };
786
787    fn target() -> TargetInfo {
788        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
789    }
790
791    /// `int sw(int x) { switch (x) { case 1: return 10; case 2: return 20; default: return 30; } }`
792    /// as the walk builds it, which is the program in issue 275.
793    fn built(cases: &[i128]) -> (Interner, Func) {
794        let mut names = Interner::new();
795        let int = Type::int(32);
796        let mut func = Func::new(
797            names.intern("sw"),
798            Signature::new().with_params(&[int]).with_returns(&[int]),
799        );
800        let entry = func.create_block();
801        let x = func.append_param(entry, int);
802
803        let default = func.create_block();
804        let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
805        let table: Vec<(i128, rucc_ir::Block)> =
806            cases.iter().copied().zip(arms.iter().copied()).collect();
807        Builder::new(&mut func, entry).switch(x, default, &table);
808
809        for (index, &arm) in arms.iter().enumerate() {
810            let mut build = Builder::new(&mut func, arm);
811            let what = i128::try_from(index).expect("a small number of cases");
812            let v = build.iconst(int, (what + 1) * 10);
813            build.ret(&[v]);
814        }
815        let mut build = Builder::new(&mut func, default);
816        let v = build.iconst(int, 30);
817        build.ret(&[v]);
818        (names, func)
819    }
820
821    fn count(func: &Func) -> usize {
822        func.blocks().count()
823    }
824
825    fn printed(func: &Func, names: &mut Interner) -> String {
826        let module = Module::new(names.intern("sw.c"), &target());
827        rucc_ir::print_func(&module, func, names)
828    }
829
830    #[test]
831    fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
832        let (mut names, mut func) = built(&[1, 2]);
833        let before = count(&func);
834        switches(&mut func);
835        assert_eq!(count(&func), before + blocks_for(2));
836
837        let text = printed(&func, &mut names);
838        assert!(!text.contains("switch"), "the switch is gone: {text}");
839        assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
840        assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
841    }
842
843    #[test]
844    fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
845        let (_, mut func) = built(&[7]);
846        let before = count(&func);
847        switches(&mut func);
848        // One case needs no chain block at all: the one compare goes to the arm or to the default.
849        assert_eq!(count(&func), before);
850        assert_eq!(blocks_for(1), 0);
851    }
852
853    #[test]
854    fn a_switch_with_only_a_default_is_a_jump() {
855        let (_, mut func) = built(&[]);
856        switches(&mut func);
857        let entry = func.entry().expect("an entry block");
858        let term = func.terminator(entry).expect("a terminator");
859        assert_eq!(func[term].opcode, Opcode::Jump);
860    }
861
862    /// The rewrite has to leave a function the verifier still accepts, since every check it makes
863    /// is one the rest of the back end assumes and none of them is rechecked after this runs.
864    #[test]
865    fn what_comes_out_is_valid_ir() {
866        let (mut names, mut func) = built(&[1, 2, 3, 4]);
867        switches(&mut func);
868        let module = Module::new(names.intern("sw.c"), &target());
869        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
870    }
871
872    /// Nothing else is touched, which matters because this runs over every function whether or not
873    /// one has a `switch` in it.
874    #[test]
875    fn a_function_with_no_switch_is_left_exactly_as_it_was() {
876        let mut names = Interner::new();
877        let int = Type::int(32);
878        let mut func =
879            Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
880        let entry = func.create_block();
881        let x = func.append_param(entry, int);
882        Builder::new(&mut func, entry).ret(&[x]);
883
884        let before = printed(&func, &mut names);
885        switches(&mut func);
886        assert_eq!(printed(&func, &mut names), before);
887    }
888
889    /// A function of one parameter and one result, with a body somebody else writes.
890    ///
891    /// The float rewrites are each one instruction becoming several in the middle of a block, so
892    /// what a test needs is a block with something around the instruction rather than a shape.
893    fn one(
894        params: &[Type],
895        returns: &[Type],
896        body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
897    ) -> (Interner, Func) {
898        let mut names = Interner::new();
899        let mut func = Func::new(
900            names.intern("f"),
901            Signature::new().with_params(params).with_returns(returns),
902        );
903        let entry = func.create_block();
904        let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
905        let mut build = Builder::new(&mut func, entry);
906        body(&mut build, &args);
907        (names, func)
908    }
909
910    fn f64() -> Type {
911        Type::float(Float::F64)
912    }
913
914    fn f32() -> Type {
915        Type::float(Float::F32)
916    }
917
918    /// `double c(void) { return 1.5; }`, which is the constant nothing in the rule set can name.
919    #[test]
920    fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
921        let (mut names, mut func) = one(&[], &[f64()], |build, _| {
922            let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
923            build.ret(&[k]);
924        });
925        floats(&mut func);
926
927        let text = printed(&func, &mut names);
928        assert!(!text.contains("fconst"), "the float constant is gone: {text}");
929        assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
930        assert!(text.contains("bitcast"), "read back as the float: {text}");
931    }
932
933    /// The width follows the format rather than being the widest one, so a `float` constant is an
934    /// `i32` and reaches `movd` rather than `movq`.
935    #[test]
936    fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
937        let (mut names, mut func) = one(&[], &[f32()], |build, _| {
938            let k = build.fconst(f32(), 0x4020_0000);
939            build.ret(&[k]);
940        });
941        floats(&mut func);
942        assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
943    }
944
945    /// `double n(double x) { return -x; }`. Flipping the sign bit is what C means and subtracting
946    /// from zero is not, so what this asserts is the exclusive or and the mask it is given.
947    #[test]
948    fn a_negation_flips_the_sign_bit_and_touches_no_other() {
949        let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
950            let n = build.unary(Opcode::FNeg, args[0], f64());
951            build.ret(&[n]);
952        });
953        floats(&mut func);
954
955        let text = printed(&func, &mut names);
956        assert!(!text.contains("fneg"), "the negation is gone: {text}");
957        assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
958        assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
959        assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
960        assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
961    }
962
963    /// `double u(unsigned x) { return x; }`, which is a widening and the signed conversion.
964    #[test]
965    fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
966        let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
967            let d = build.unary(Opcode::UIToFP, args[0], f64());
968            build.ret(&[d]);
969        });
970        floats(&mut func);
971
972        let text = printed(&func, &mut names);
973        assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
974        assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
975        assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
976    }
977
978    /// `unsigned t(double x) { return x; }`, which is the same argument the other way round.
979    #[test]
980    fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
981        let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
982            let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
983            build.ret(&[n]);
984        });
985        floats(&mut func);
986
987        let text = printed(&func, &mut names);
988        assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
989        assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
990        assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
991    }
992
993    /// `signed char a(double x) { return (signed char)x; }`, which the front end writes as a
994    /// conversion straight to eight bits and the machine has no instruction for at that width.
995    #[test]
996    fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
997        let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
998            let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
999            build.ret(&[n]);
1000        });
1001        floats(&mut func);
1002
1003        let text = printed(&func, &mut names);
1004        assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
1005        assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
1006    }
1007
1008    /// The same the other way, where the widening carries the sign because the value has one.
1009    #[test]
1010    fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
1011        let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
1012            let d = build.unary(Opcode::SIToFP, args[0], f64());
1013            build.ret(&[d]);
1014        });
1015        floats(&mut func);
1016
1017        let text = printed(&func, &mut names);
1018        assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
1019        assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
1020        assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
1021    }
1022
1023    /// The table the two of them share, which is where the whole argument about widths lives.
1024    #[test]
1025    fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
1026        use super::holder;
1027        for bits in [1, 8, 16, 32] {
1028            assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
1029        }
1030        assert_eq!(holder(64, true), Some(64));
1031        for bits in [1, 8, 16, 31] {
1032            assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
1033        }
1034        // The one more bit an unsigned value needs is what makes these two the wider width.
1035        assert_eq!(holder(32, false), Some(64));
1036        assert_eq!(holder(64, false), None);
1037    }
1038
1039    /// Sixty four bits is where the argument runs out, because an unsigned value of that width is
1040    /// not a signed value of any width the IR has. Both are left for the lowering to refuse by
1041    /// name, which is a better message than one about the instructions they would have become.
1042    #[test]
1043    fn the_unsigned_conversions_at_the_widest_width_are_left_alone() {
1044        let (mut names, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
1045            let d = build.unary(Opcode::UIToFP, args[0], f64());
1046            build.ret(&[d]);
1047        });
1048        let before = printed(&func, &mut names);
1049        floats(&mut func);
1050        assert_eq!(printed(&func, &mut names), before);
1051
1052        let (mut names, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
1053            let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1054            build.ret(&[n]);
1055        });
1056        let before = printed(&func, &mut names);
1057        floats(&mut func);
1058        assert_eq!(printed(&func, &mut names), before);
1059    }
1060
1061    /// The same obligation the `switch` rewrite has, for the same reason: nothing after this
1062    /// checks the IR again and everything after it assumes what the verifier would have said.
1063    #[test]
1064    fn what_the_float_rewrites_leave_is_valid_ir() {
1065        let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1066            let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1067            let d = build.unary(Opcode::UIToFP, args[0], f64());
1068            let n = build.unary(Opcode::FNeg, d, f64());
1069            let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
1070            build.ret(&[s]);
1071        });
1072        floats(&mut func);
1073        let module = Module::new(names.intern("f.c"), &target());
1074        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1075    }
1076
1077    /// Nothing else is touched, for the same reason the `switch` pass has that test: this runs
1078    /// over every function whether or not one has a float in it.
1079    #[test]
1080    fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
1081        let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1082            build.ret(&[args[0]]);
1083        });
1084        let before = printed(&func, &mut names);
1085        floats(&mut func);
1086        assert_eq!(printed(&func, &mut names), before);
1087    }
1088    fn access(size: u64, align: u32) -> MemInfo {
1089        MemInfo { size, align, order: MemOrder::NotAtomic, tbaa: None, restrict: Restrict::NONE }
1090    }
1091
1092    /// `void c(void *to, const void *from) { *(T *)to = *(const T *)from; }` for a `T` of that
1093    /// size and alignment, which is what the front end writes for a structure assignment.
1094    fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
1095        one(&[Type::PTR, Type::PTR], &[], |build, args| {
1096            let second = match byte {
1097                Some(value) => build.iconst(Type::int(8), value),
1098                None => args[1],
1099            };
1100            let mem = build.func().add_mem(access(size, align));
1101            let operands = build.func().push_values(&[args[0], second]);
1102            let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1103            build.inst(data, &[]);
1104            build.ret(&[]);
1105        })
1106    }
1107
1108    fn copying(size: u64, align: u32) -> (Interner, Func) {
1109        moving(Opcode::Memcpy, size, align, None)
1110    }
1111
1112    fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
1113        moving(Opcode::Memset, size, align, Some(byte))
1114    }
1115
1116    /// The plan a copy of that size and alignment becomes, as widths, which is what the offsets
1117    /// follow from.
1118    fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
1119        Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
1120    }
1121
1122    /// `struct point { int x, y; } a, b; a = b;`, which is sixteen bytes aligned to eight.
1123    #[test]
1124    fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
1125        let (mut names, mut func) = copying(16, 8);
1126        bulk(&mut func, &mut names, 8);
1127
1128        let text = printed(&func, &mut names);
1129        assert!(!text.contains("memcpy"), "the copy is gone: {text}");
1130        assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
1131        assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1132        assert_eq!(
1133            text.matches("ptr_add").count(),
1134            2,
1135            "no offset for the word at the front: {text}"
1136        );
1137    }
1138
1139    /// A word is as wide as the block is known to be aligned to and no wider, because a load
1140    /// wider than that faults on a machine that checks and this does not know whether the one it
1141    /// is compiling for does.
1142    #[test]
1143    fn a_word_is_as_wide_as_the_block_is_aligned_to() {
1144        assert_eq!(widths(16, 8), Some(vec![8, 8]));
1145        assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
1146        assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
1147    }
1148
1149    /// What is left over is narrower words rather than a run of bytes, so thirteen bytes aligned
1150    /// to eight is three moves and not six.
1151    #[test]
1152    fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
1153        assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
1154        assert_eq!(widths(3, 8), Some(vec![2, 1]));
1155        assert_eq!(widths(1, 8), Some(vec![1]));
1156    }
1157
1158    /// Every offset is a multiple of the width at it, which is what lets the alignment of each
1159    /// access be written down as its width.
1160    #[test]
1161    fn every_word_starts_somewhere_it_is_aligned_for() {
1162        for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
1163            assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
1164        }
1165    }
1166
1167    /// `struct big b = { 0 };`, where the part the initialiser did not name is zeroed.
1168    #[test]
1169    fn a_fill_is_the_byte_spread_across_each_word() {
1170        let (mut names, mut func) = filling(16, 8, 0);
1171        bulk(&mut func, &mut names, 8);
1172
1173        let text = printed(&func, &mut names);
1174        assert!(!text.contains("memset"), "the fill is gone: {text}");
1175        assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1176        assert!(!text.contains("load"), "a fill reads nothing: {text}");
1177    }
1178
1179    /// The spreading is arithmetic on the byte, which is the thing a rule cannot do and the
1180    /// reason this pass exists at all.
1181    #[test]
1182    fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
1183        assert_eq!(spread(0, 8), 0);
1184        assert_eq!(spread(0xff, 1), 0xff);
1185        assert_eq!(spread(0xff, 4), 0xffff_ffff);
1186        assert_eq!(spread(0xab, 2), 0xabab);
1187        assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
1188    }
1189
1190    /// A copy larger than the threshold is a call to the runtime rather than a run of moves.
1191    #[test]
1192    fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
1193        let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1194        let (mut names, mut func) = copying(size, 1);
1195        bulk(&mut func, &mut names, 8);
1196        let text = printed(&func, &mut names);
1197        assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
1198
1199        // And the one word under it is moves, because the threshold counts moves rather than
1200        // bytes and the whole point of the threshold is that a small copy does not pay for a call.
1201        let (mut names, mut func) = copying(size - 1, 1);
1202        bulk(&mut func, &mut names, 8);
1203        assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
1204    }
1205
1206    /// The call passes what C passes, which is not what the IR holds. The size lives beside the
1207    /// instruction in the IR and travels in a register in the call.
1208    #[test]
1209    fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
1210        let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1211        let (mut names, mut func) = copying(size, 1);
1212        bulk(&mut func, &mut names, 8);
1213        let text = printed(&func, &mut names);
1214        assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
1215    }
1216
1217    /// A `memmove` is a call whatever its size, because the two sides may overlap and a run of
1218    /// moves in one direction is right for only one of the two ways they can.
1219    #[test]
1220    fn a_move_is_a_call_however_small_it_is() {
1221        let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
1222        bulk(&mut func, &mut names, 8);
1223        let text = printed(&func, &mut names);
1224        assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
1225    }
1226
1227    /// A fill whose byte the program works out rather than names. Spreading a value across a
1228    /// word at runtime is a multiply, so this is a call rather than moves however small it is.
1229    #[test]
1230    fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
1231        let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
1232            let mem = build.func().add_mem(access(8, 8));
1233            let operands = build.func().push_values(&[args[0], args[1]]);
1234            let data = InstData {
1235                args: operands,
1236                extra: Extra::Mem(mem),
1237                ..InstData::new(Opcode::Memset)
1238            };
1239            build.inst(data, &[]);
1240            build.ret(&[]);
1241        });
1242        bulk(&mut func, &mut names, 8);
1243        let text = printed(&func, &mut names);
1244        assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1245        // Widened, because C passes the byte as an `int` and the IR holds it as a byte.
1246        assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1247    }
1248
1249    /// A machine whose widest move is four bytes gets four byte words out of an eight byte block,
1250    /// however well aligned the block is.
1251    #[test]
1252    fn no_word_is_wider_than_the_machine_moves_at_once() {
1253        assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
1254        assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
1255    }
1256
1257    #[test]
1258    fn what_a_copy_becomes_is_ir_that_verifies() {
1259        let (mut names, mut func) = copying(13, 8);
1260        bulk(&mut func, &mut names, 8);
1261        let module = Module::new(names.intern("c.c"), &target());
1262        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1263    }
1264
1265    #[test]
1266    fn what_a_fill_becomes_is_ir_that_verifies() {
1267        let (mut names, mut func) = filling(13, 8, 0xff);
1268        bulk(&mut func, &mut names, 8);
1269        let module = Module::new(names.intern("f.c"), &target());
1270        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1271    }
1272
1273    #[test]
1274    fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
1275        let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1276        let (mut names, mut func) = copying(size, 1);
1277        bulk(&mut func, &mut names, 8);
1278        let module = Module::new(names.intern("c.c"), &target());
1279        rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
1280    }
1281
1282    /// Nothing else is touched, for the same reason the other two passes have that test.
1283    #[test]
1284    fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
1285        let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1286            build.ret(&[args[0]]);
1287        });
1288        let before = printed(&func, &mut names);
1289        bulk(&mut func, &mut names, 8);
1290        assert_eq!(printed(&func, &mut names), before);
1291    }
1292
1293    /// A function whose body is one byte swap of the given width, which is what a call to
1294    /// `__builtin_bswap16` and its neighbours has become by the time this pass runs.
1295    fn swapping(width: u32) -> (Interner, Func) {
1296        let ty = Type::int(width);
1297        one(&[ty], &[ty], |build, args| {
1298            let s = build.unary(Opcode::Bswap, args[0], ty);
1299            build.ret(&[s]);
1300        })
1301    }
1302
1303    /// The masks are the alternating runs the halving needs, and they are the constants a reader
1304    /// checking this against a byte swap written by hand would expect to see.
1305    ///
1306    /// At thirty two bits the first step swaps sixteen bit halves and so keeps the low half of each
1307    /// pair, which is `0x0000ffff`, and the second swaps bytes within those halves and keeps
1308    /// `0x00ff00ff`. Written as signed because that is what the IR holds an immediate as.
1309    #[test]
1310    fn the_masks_are_the_alternating_runs_of_the_group_being_swapped() {
1311        assert_eq!(alternating(32, 16), 0x0000_ffff);
1312        assert_eq!(alternating(32, 8), 0x00ff_00ff);
1313        assert_eq!(alternating(16, 8), 0x00ff);
1314        assert_eq!(alternating(64, 32), 0x0000_0000_ffff_ffff);
1315        assert_eq!(alternating(64, 16), 0x0000_ffff_0000_ffff);
1316        assert_eq!(alternating(64, 8), 0x00ff_00ff_00ff_00ff);
1317    }
1318
1319    /// The two byte swap is the one step there is, so it is one mask and one pair of shifts.
1320    #[test]
1321    fn a_two_byte_swap_is_one_exchange_of_neighbouring_bytes() {
1322        let (mut names, mut func) = swapping(16);
1323        bytes(&mut func);
1324
1325        let text = printed(&func, &mut names);
1326        assert!(!text.contains("bswap"), "the instruction is gone: {text}");
1327        assert!(text.contains("iconst.i16 255"), "the low byte of the pair: {text}");
1328        assert_eq!(text.matches("shl").count(), 1, "one shift up: {text}");
1329        assert_eq!(text.matches("lshr").count(), 1, "one shift down: {text}");
1330        assert_eq!(text.matches(" or ").count(), 1, "and the two put together: {text}");
1331    }
1332
1333    /// The wider two are the same step done again at half the group, which is what makes the count
1334    /// grow by a fixed amount per doubling rather than per byte.
1335    #[test]
1336    fn a_wider_swap_is_the_same_exchange_once_per_halving() {
1337        for (width, steps) in [(16u32, 1usize), (32, 2), (64, 3)] {
1338            let (mut names, mut func) = swapping(width);
1339            bytes(&mut func);
1340            let text = printed(&func, &mut names);
1341            assert_eq!(text.matches("shl").count(), steps, "at {width}: {text}");
1342            assert_eq!(text.matches("lshr").count(), steps, "at {width}: {text}");
1343            assert_eq!(text.matches(" and ").count(), steps * 2, "at {width}: {text}");
1344            assert_eq!(text.matches(" or ").count(), steps, "at {width}: {text}");
1345        }
1346    }
1347
1348    /// The shift counts are the group being exchanged and nothing else, so a reader can read the
1349    /// halving straight off the constants.
1350    #[test]
1351    fn the_shift_counts_are_the_group_width_halving_as_it_goes() {
1352        let (mut names, mut func) = swapping(64);
1353        bytes(&mut func);
1354        let text = printed(&func, &mut names);
1355        for count in ["iconst.i64 32", "iconst.i64 16", "iconst.i64 8"] {
1356            assert!(text.contains(count), "{count} is a step: {text}");
1357        }
1358    }
1359
1360    /// The rewrite has to leave a function the verifier still accepts, for the reason the switch
1361    /// rewrite has the same test: nothing rechecks it.
1362    #[test]
1363    fn what_a_byte_swap_becomes_is_ir_that_verifies() {
1364        let (mut names, mut func) = swapping(32);
1365        bytes(&mut func);
1366        let module = Module::new(names.intern("b.c"), &target());
1367        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1368    }
1369
1370    /// Nothing else is touched, which matters because this runs over every function in the program
1371    /// and nearly none of them reverses any bytes.
1372    #[test]
1373    fn a_function_with_no_byte_swap_in_it_is_left_exactly_as_it_was() {
1374        let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1375            build.ret(&[args[0]]);
1376        });
1377        let before = printed(&func, &mut names);
1378        bytes(&mut func);
1379        assert_eq!(printed(&func, &mut names), before);
1380    }
1381
1382    /// A function whose body is one bit count of the given opcode and width.
1383    fn counting(op: Opcode, width: u32) -> (Interner, Func) {
1384        let ty = Type::int(width);
1385        one(&[ty], &[ty], |build, args| {
1386            let c = build.unary(op, args[0], ty);
1387            build.ret(&[c]);
1388        })
1389    }
1390
1391    /// The masks the halving sum needs, which are the ones any bit counting routine is written with
1392    /// and are worth being able to read off against one.
1393    #[test]
1394    fn the_counting_masks_are_the_ones_the_halving_sum_is_written_with() {
1395        assert_eq!(alternating(32, 1), 0x5555_5555);
1396        assert_eq!(alternating(32, 2), 0x3333_3333);
1397        assert_eq!(alternating(32, 4), 0x0f0f_0f0f);
1398        assert_eq!(every(32, 8, 1), 0x0101_0101);
1399        assert_eq!(every(64, 8, 1), 0x0101_0101_0101_0101);
1400    }
1401
1402    /// The set bit count is arithmetic and the multiply is what adds the bytes together, which is
1403    /// the step a reader is most likely to want to check.
1404    #[test]
1405    fn a_set_bit_count_is_the_halving_sum_and_a_multiply_that_adds_the_bytes() {
1406        let (mut names, mut func) = counting(Opcode::Ctpop, 32);
1407        counts(&mut func);
1408
1409        let text = printed(&func, &mut names);
1410        assert!(!text.contains("ctpop"), "the instruction is gone: {text}");
1411        assert!(text.contains("iconst.i32 1431655765"), "the pairs mask: {text}");
1412        assert!(text.contains("iconst.i32 858993459"), "the nibbles mask: {text}");
1413        assert!(text.contains("iconst.i32 252645135"), "the bytes mask: {text}");
1414        assert_eq!(text.matches(" mul ").count(), 1, "one multiply: {text}");
1415        assert!(text.contains("iconst.i32 24"), "and the top byte is the answer: {text}");
1416    }
1417
1418    /// At eight bits there are no bytes left to add, so the multiply is not written at all.
1419    #[test]
1420    fn a_count_of_one_byte_stops_before_the_multiply() {
1421        let (mut names, mut func) = counting(Opcode::Ctpop, 8);
1422        counts(&mut func);
1423        let text = printed(&func, &mut names);
1424        assert!(!text.contains("ctpop"), "{text}");
1425        assert!(!text.contains(" mul "), "nothing to add together: {text}");
1426    }
1427
1428    /// A leading zero count smears every set bit downwards and counts what is left unset above it,
1429    /// which is one shift and one or per doubling and then the count.
1430    #[test]
1431    fn a_leading_zero_count_smears_the_value_down_and_counts_the_complement() {
1432        let (mut names, mut func) = counting(Opcode::Ctlz, 32);
1433        counts(&mut func);
1434
1435        let text = printed(&func, &mut names);
1436        assert!(!text.contains("ctlz"), "the instruction is gone: {text}");
1437        assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
1438        for by in ["iconst.i32 1", "iconst.i32 2", "iconst.i32 4", "iconst.i32 8", "iconst.i32 16"]
1439        {
1440            assert!(text.contains(by), "{by} is a smearing step: {text}");
1441        }
1442        assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
1443    }
1444
1445    /// A trailing zero count is the bits below the lowest set one, which is a mask and no smearing.
1446    #[test]
1447    fn a_trailing_zero_count_masks_the_bits_below_the_lowest_set_one() {
1448        let (mut names, mut func) = counting(Opcode::Cttz, 32);
1449        counts(&mut func);
1450
1451        let text = printed(&func, &mut names);
1452        assert!(!text.contains("cttz"), "the instruction is gone: {text}");
1453        assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
1454        assert!(text.contains("iconst.i32 -1"), "the complement and the decrement: {text}");
1455        assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
1456        // Far fewer instructions than the leading count, because there is no smearing to do.
1457        assert!(text.matches(" or ").count() <= 1, "no smearing run: {text}");
1458    }
1459
1460    /// The rewrites have to leave a function the verifier still accepts, at every width and for all
1461    /// three, because nothing rechecks what comes out of here.
1462    #[test]
1463    fn what_a_bit_count_becomes_is_ir_that_verifies() {
1464        for op in [Opcode::Ctpop, Opcode::Ctlz, Opcode::Cttz] {
1465            for width in [8u32, 16, 32, 64] {
1466                let (mut names, mut func) = counting(op, width);
1467                counts(&mut func);
1468                let module = Module::new(names.intern("c.c"), &target());
1469                rucc_ir::verify_func(&module, &func, &names)
1470                    .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
1471            }
1472        }
1473    }
1474
1475    /// A width the arithmetic is not written for is left as the instruction it was, so a selector
1476    /// with no rule for it says so rather than the program getting a number counted in the wrong
1477    /// shape.
1478    #[test]
1479    fn a_width_the_halving_sum_is_not_written_for_is_left_alone() {
1480        let (mut names, mut func) = counting(Opcode::Ctpop, 24);
1481        counts(&mut func);
1482        assert!(printed(&func, &mut names).contains("ctpop"), "left as it was");
1483    }
1484
1485    /// Nothing else is touched, for the same reason the other passes have that test.
1486    #[test]
1487    fn a_function_with_no_bit_count_in_it_is_left_exactly_as_it_was() {
1488        let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1489            build.ret(&[args[0]]);
1490        });
1491        let before = printed(&func, &mut names);
1492        counts(&mut func);
1493        assert_eq!(printed(&func, &mut names), before);
1494    }
1495}