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/// The most moves a copy or a fill becomes before it is left alone for a call instead.
241///
242/// Thirty two, which is two hundred and fifty six bytes at a word a time and is a structure larger
243/// than almost every one a program writes. What the number is trading is code size against a call,
244/// and the exchange rate is a machine's rather than a language's, so the number lives here next to
245/// the code it bounds and not in a target description that would have to be right about it for
246/// every target at once.
247///
248/// It is a count of moves and not a count of bytes because that is what the cost is. A copy of
249/// sixty four bytes between two addresses aligned to eight is eight moves and a copy of the same
250/// sixty four bytes between two addresses aligned to one is sixty four, and the second is the
251/// expensive one whatever the size says.
252pub const UNROLL: usize = 32;
253
254/// Rewrites every bulk copy and bulk fill, into moves when that is worth it and into a call to the
255/// runtime when it is not.
256///
257/// A copy of more than [`UNROLL`] moves becomes a call, and so does a fill whose byte is not a
258/// constant, which the front end does not write today and which would need the byte spread across
259/// a word at runtime. A `memmove` is always a call, because the two sides may overlap and a run of
260/// moves in one direction is only right for one of the two ways they can.
261///
262/// `word` is how many bytes the widest move on this machine carries. Nothing here reads a target
263/// otherwise, and a copy is the same run of loads and stores everywhere.
264pub fn bulk(func: &mut Func, names: &mut Interner, word: u32) {
265    let found: Vec<Inst> =
266        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
267    for inst in found {
268        match func[inst].opcode {
269            Opcode::Memcpy => copy(func, names, inst, word),
270            Opcode::Memset => fill(func, names, inst, word),
271            Opcode::Memmove => library(func, names, inst, "memmove", word),
272            _ => {}
273        }
274    }
275}
276
277/// One `memcpy`, as a load and a store for each word of it.
278///
279/// Each word is read and then written before the next is read, rather than every read being built
280/// before any write the way [`crate::varargs`] copies a list. A `memcpy` is the copy whose two
281/// sides the front end promises do not overlap, so what is at the source when the last word is read
282/// is what was there when the first was, and reading a word at a time costs one register where
283/// reading all of them first would cost as many registers as the copy has words.
284fn copy(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
285    let [into, from] = func[func[inst].args] else { return };
286    let Extra::Mem(mem) = func[inst].extra else { return };
287    let info = func[mem];
288    let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memcpy", word) };
289    for (at, width) in plan {
290        let ty = Type::int(width * 8);
291        let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
292        let there = stepped(func, inst, from, at);
293        let word = read(func, inst, there, access, ty);
294        let here = stepped(func, inst, into, at);
295        write(func, inst, word, here, access);
296    }
297    func.remove_inst(inst);
298}
299
300/// One `memset`, as a store of the byte spread across each word of it.
301///
302/// The byte is a constant, so the word it spreads into is a constant too and the spreading is done
303/// here rather than by the program. The front end writes a `memset` for the part of an object an
304/// initialiser did not name, where the byte is always zero, and the general case is written anyway
305/// because the arithmetic is the same and being right about `0xff` costs nothing.
306fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
307    let [into, byte] = func[func[inst].args] else { return };
308    let Extra::Mem(mem) = func[inst].extra else { return };
309    let info = func[mem];
310    let Some(spelled) = literal(func, byte) else {
311        return library(func, names, inst, "memset", word);
312    };
313    let Some(plan) = chunks(info, word) else { return library(func, names, inst, "memset", word) };
314    for (at, width) in plan {
315        let ty = Type::int(width * 8);
316        let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
317        let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
318        let here = stepped(func, inst, into, at);
319        write(func, inst, value, here, access);
320    }
321    func.remove_inst(inst);
322}
323
324/// One bulk operation as a call to the routine of that name in the runtime.
325///
326/// This is what a copy too large to unroll becomes, and what a `memmove` and a fill with a
327/// computed byte become whatever their size. The routine is `rucc-builtins`' on a freestanding
328/// target and the C library's on a hosted one, and the call is the same either way because the two
329/// have the same names and the same signatures on purpose.
330///
331/// The arguments are the C ones and not the IR ones. The IR holds the size beside the instruction
332/// where C passes it, and holds a fill byte as a byte where C passes an `int`, so the size becomes
333/// a constant in a register and the byte is widened. The value each returns is its first argument,
334/// which nothing reads, so the call is built as returning nothing rather than as returning a
335/// pointer nobody looks at.
336fn library(func: &mut Func, names: &mut Interner, inst: Inst, routine: &str, word: u32) {
337    let [into, second] = func[func[inst].args] else { return };
338    let Extra::Mem(mem) = func[inst].extra else { return };
339    let size = func[mem].size;
340
341    // `size_t`, which is as wide as a general purpose register on every target here. Taken from
342    // the machine rather than written as sixty four so that a thirty two bit target gets the
343    // argument its own C library declares.
344    let words = Type::int(word * 8);
345    let count = ahead_const(func, inst, Imm::int(i128::from(size), words), words);
346    // A fill passes an `int` where the IR passes the byte itself, and the widening is a zero
347    // extension because the routine looks at the low eight bits and nothing else.
348    let second = match routine {
349        "memset" => widened(func, inst, second),
350        _ => second,
351    };
352
353    let sig = func.add_signature(Signature::new().with_params(&[
354        Type::PTR,
355        if routine == "memset" { Type::int(32) } else { Type::PTR },
356        words,
357    ]));
358    let callee = names.intern(routine);
359    let varargs = func.push_abis(&[]);
360    let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
361    let args = func.push_values(&[into, second, count]);
362    let data = &mut func[inst];
363    data.opcode = Opcode::Call;
364    data.args = args;
365    data.extra = Extra::Call(info);
366    data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
367}
368
369/// A value widened to an `int`, or the value itself when it is one already.
370fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
371    let int = Type::int(32);
372    let ty = func[value].ty;
373    if ty == int {
374        return value;
375    }
376    ahead(func, inst, Opcode::ZExt, &[value], int)
377}
378
379/// Where each word of a block of memory starts and how wide it is, or nothing for a block that is
380/// more words than [`UNROLL`].
381///
382/// The widest word is the smaller of what the machine moves at once and what the block is known to
383/// be aligned to, because a load wider than the alignment is a fault on a machine that checks and
384/// this pass does not know whether the one it is compiling for does. That costs a copy of a
385/// character array a move per byte, which is exactly the copy the threshold sends to a call.
386///
387/// The width halves whenever what is left is narrower than it, so a block of thirteen bytes aligned
388/// to eight is eight, four and one rather than thirteen ones. Every offset is a multiple of the
389/// width at it, since each width divides the sum of the wider ones in front of it, which is what
390/// lets the alignment of each access be written down as the width.
391fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
392    plan(info.size, info.align, word)
393}
394
395/// The same, as the two numbers rather than as an access, for the one caller that has no access to
396/// ask about.
397///
398/// [`crate::abi`] copies a structure passed by value into the argument area, and that copy is not a
399/// `memcpy` in the IR: it is written straight into the machine IR, because where it goes is an
400/// offset the placement walk gives and nothing before this pass knows it. The plan has to be the
401/// same plan either way, so it is one function.
402pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
403    let widest = word.min(align).max(1);
404    if !widest.is_power_of_two() {
405        return None;
406    }
407    let mut plan = Vec::new();
408    let mut at = 0;
409    let mut width = u64::from(widest);
410    while at < size {
411        while width > size - at {
412            width /= 2;
413        }
414        plan.push((at, u32::try_from(width).ok()?));
415        at += width;
416        if plan.len() > UNROLL {
417            return None;
418        }
419    }
420    Some(plan)
421}
422
423/// The byte a fill writes, when the program said which one rather than working it out.
424fn literal(func: &Func, value: Value) -> Option<u8> {
425    let Def::Result { inst, .. } = func[value].def else { return None };
426    if func[inst].opcode != Opcode::IConst {
427        return None;
428    }
429    let Extra::Imm(imm) = func[inst].extra else { return None };
430    u8::try_from(func[imm].bits() & 0xff).ok()
431}
432
433/// One byte repeated across a word of that many bytes, which is what a fill stores.
434fn spread(byte: u8, width: u32) -> u64 {
435    (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
436}
437
438/// The address that far into a block, written in front of an instruction, or the block itself for
439/// the word at the front of it.
440fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
441    if at == 0 {
442        return block;
443    }
444    let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
445    ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
446}
447
448/// A load put in front of an instruction, and the value it reads.
449fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
450    let extra = Extra::Mem(func.add_mem(info));
451    let args = func.push_values(&[from]);
452    written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
453}
454
455/// A store put in front of an instruction, which produces nothing and is only its effect.
456fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
457    let span = func.span(inst);
458    let extra = Extra::Mem(func.add_mem(info));
459    let args = func.push_values(&[value, into]);
460    let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
461    let made = func.create_inst(data, &[], span);
462    func.insert_before(made, inst);
463}
464
465/// The width the machine converts at that holds every value of an integer of this one.
466///
467/// The machine converts between a float and a signed integer at thirty two bits and at sixty four
468/// and at no other width, so a conversion anywhere else is one of those two with a widening in
469/// front of it or a narrowing behind it. Which of the two it is, is the narrower one the values
470/// fit in, and an unsigned integer of `bits` bits needs one more bit than that to be signed in.
471///
472/// `None` is a width no signed integer here holds, which is only an unsigned sixty four bit one.
473fn holder(bits: u32, signed: bool) -> Option<u32> {
474    match if signed { bits } else { bits + 1 } {
475        ..=32 => Some(32),
476        33..=64 => Some(64),
477        _ => None,
478    }
479}
480
481/// The type of the one value an instruction produces.
482///
483/// Every opcode this pass touches produces exactly one, so an instruction that produces none is
484/// one the caller has already gone wrong about and the void type says so without panicking.
485fn produced(func: &Func, inst: Inst) -> Type {
486    func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
487}
488
489/// Puts an instruction over these operands in front of another one, and gives back its value.
490fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
491    let args = func.push_values(args);
492    written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
493}
494
495/// The same for a constant, which carries an immediate rather than operands.
496fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
497    let extra = Extra::Imm(func.add_imm(imm));
498    written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
499}
500
501/// Creates the instruction, puts it where those two asked, and reads its value back out.
502fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
503    let span = func.span(inst);
504    let made = func.create_inst(data, &[ty], span);
505    func.insert_before(made, inst);
506    func[made].first_result.expect("an instruction created with one result has one")
507}
508
509/// Turns an instruction into a different one over different operands, in place.
510///
511/// The last instruction of a rewrite is the original rather than a new one, so the value the rest
512/// of the function reads is the value it already read and nothing has to be substituted anywhere.
513/// The type of that value does not change either, because every rewrite here ends at the type it
514/// started at.
515fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
516    let args = func.push_values(args);
517    let data = &mut func[inst];
518    data.opcode = opcode;
519    data.args = args;
520    data.extra = Extra::None;
521    // What the program said about rounding and about not a numbers is still true of the
522    // instructions it became, and what is no longer meaningful is dropped rather than carried.
523    data.flags = data.flags.intersection(Flags::legal_on(opcode));
524}
525
526/// The blocks a chain of `n` cases needs beyond the ones the program already had.
527///
528/// Here so that a test can say the number rather than count it, and so that whoever writes the
529/// jump table has one place to compare against.
530#[must_use]
531pub fn blocks_for(cases: usize) -> usize {
532    cases.saturating_sub(1)
533}
534
535#[cfg(test)]
536mod tests {
537    use rucc_base::Interner;
538    use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
539    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
540
541    use rucc_ir::{Extra, InstData, MemInfo, MemOrder};
542
543    use super::{UNROLL, blocks_for, bulk, chunks, floats, spread, switches};
544
545    fn target() -> TargetInfo {
546        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
547    }
548
549    /// `int sw(int x) { switch (x) { case 1: return 10; case 2: return 20; default: return 30; } }`
550    /// as the walk builds it, which is the program in issue 275.
551    fn built(cases: &[i128]) -> (Interner, Func) {
552        let mut names = Interner::new();
553        let int = Type::int(32);
554        let mut func = Func::new(
555            names.intern("sw"),
556            Signature::new().with_params(&[int]).with_returns(&[int]),
557        );
558        let entry = func.create_block();
559        let x = func.append_param(entry, int);
560
561        let default = func.create_block();
562        let arms: Vec<_> = cases.iter().map(|_| func.create_block()).collect();
563        let table: Vec<(i128, rucc_ir::Block)> =
564            cases.iter().copied().zip(arms.iter().copied()).collect();
565        Builder::new(&mut func, entry).switch(x, default, &table);
566
567        for (index, &arm) in arms.iter().enumerate() {
568            let mut build = Builder::new(&mut func, arm);
569            let what = i128::try_from(index).expect("a small number of cases");
570            let v = build.iconst(int, (what + 1) * 10);
571            build.ret(&[v]);
572        }
573        let mut build = Builder::new(&mut func, default);
574        let v = build.iconst(int, 30);
575        build.ret(&[v]);
576        (names, func)
577    }
578
579    fn count(func: &Func) -> usize {
580        func.blocks().count()
581    }
582
583    fn printed(func: &Func, names: &mut Interner) -> String {
584        let module = Module::new(names.intern("sw.c"), &target());
585        rucc_ir::print_func(&module, func, names)
586    }
587
588    #[test]
589    fn a_switch_becomes_a_compare_and_a_branch_for_each_case() {
590        let (mut names, mut func) = built(&[1, 2]);
591        let before = count(&func);
592        switches(&mut func);
593        assert_eq!(count(&func), before + blocks_for(2));
594
595        let text = printed(&func, &mut names);
596        assert!(!text.contains("switch"), "the switch is gone: {text}");
597        assert_eq!(text.matches("icmp eq").count(), 2, "one compare per case: {text}");
598        assert_eq!(text.matches("br_if").count(), 2, "one branch per case: {text}");
599    }
600
601    #[test]
602    fn the_last_case_falls_to_the_default_rather_than_to_a_block_of_its_own() {
603        let (_, mut func) = built(&[7]);
604        let before = count(&func);
605        switches(&mut func);
606        // One case needs no chain block at all: the one compare goes to the arm or to the default.
607        assert_eq!(count(&func), before);
608        assert_eq!(blocks_for(1), 0);
609    }
610
611    #[test]
612    fn a_switch_with_only_a_default_is_a_jump() {
613        let (_, mut func) = built(&[]);
614        switches(&mut func);
615        let entry = func.entry().expect("an entry block");
616        let term = func.terminator(entry).expect("a terminator");
617        assert_eq!(func[term].opcode, Opcode::Jump);
618    }
619
620    /// The rewrite has to leave a function the verifier still accepts, since every check it makes
621    /// is one the rest of the back end assumes and none of them is rechecked after this runs.
622    #[test]
623    fn what_comes_out_is_valid_ir() {
624        let (mut names, mut func) = built(&[1, 2, 3, 4]);
625        switches(&mut func);
626        let module = Module::new(names.intern("sw.c"), &target());
627        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
628    }
629
630    /// Nothing else is touched, which matters because this runs over every function whether or not
631    /// one has a `switch` in it.
632    #[test]
633    fn a_function_with_no_switch_is_left_exactly_as_it_was() {
634        let mut names = Interner::new();
635        let int = Type::int(32);
636        let mut func =
637            Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
638        let entry = func.create_block();
639        let x = func.append_param(entry, int);
640        Builder::new(&mut func, entry).ret(&[x]);
641
642        let before = printed(&func, &mut names);
643        switches(&mut func);
644        assert_eq!(printed(&func, &mut names), before);
645    }
646
647    /// A function of one parameter and one result, with a body somebody else writes.
648    ///
649    /// The float rewrites are each one instruction becoming several in the middle of a block, so
650    /// what a test needs is a block with something around the instruction rather than a shape.
651    fn one(
652        params: &[Type],
653        returns: &[Type],
654        body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
655    ) -> (Interner, Func) {
656        let mut names = Interner::new();
657        let mut func = Func::new(
658            names.intern("f"),
659            Signature::new().with_params(params).with_returns(returns),
660        );
661        let entry = func.create_block();
662        let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
663        let mut build = Builder::new(&mut func, entry);
664        body(&mut build, &args);
665        (names, func)
666    }
667
668    fn f64() -> Type {
669        Type::float(Float::F64)
670    }
671
672    fn f32() -> Type {
673        Type::float(Float::F32)
674    }
675
676    /// `double c(void) { return 1.5; }`, which is the constant nothing in the rule set can name.
677    #[test]
678    fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
679        let (mut names, mut func) = one(&[], &[f64()], |build, _| {
680            let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
681            build.ret(&[k]);
682        });
683        floats(&mut func);
684
685        let text = printed(&func, &mut names);
686        assert!(!text.contains("fconst"), "the float constant is gone: {text}");
687        assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
688        assert!(text.contains("bitcast"), "read back as the float: {text}");
689    }
690
691    /// The width follows the format rather than being the widest one, so a `float` constant is an
692    /// `i32` and reaches `movd` rather than `movq`.
693    #[test]
694    fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
695        let (mut names, mut func) = one(&[], &[f32()], |build, _| {
696            let k = build.fconst(f32(), 0x4020_0000);
697            build.ret(&[k]);
698        });
699        floats(&mut func);
700        assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
701    }
702
703    /// `double n(double x) { return -x; }`. Flipping the sign bit is what C means and subtracting
704    /// from zero is not, so what this asserts is the exclusive or and the mask it is given.
705    #[test]
706    fn a_negation_flips_the_sign_bit_and_touches_no_other() {
707        let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
708            let n = build.unary(Opcode::FNeg, args[0], f64());
709            build.ret(&[n]);
710        });
711        floats(&mut func);
712
713        let text = printed(&func, &mut names);
714        assert!(!text.contains("fneg"), "the negation is gone: {text}");
715        assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
716        assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
717        assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
718        assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
719    }
720
721    /// `double u(unsigned x) { return x; }`, which is a widening and the signed conversion.
722    #[test]
723    fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
724        let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
725            let d = build.unary(Opcode::UIToFP, args[0], f64());
726            build.ret(&[d]);
727        });
728        floats(&mut func);
729
730        let text = printed(&func, &mut names);
731        assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
732        assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
733        assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
734    }
735
736    /// `unsigned t(double x) { return x; }`, which is the same argument the other way round.
737    #[test]
738    fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
739        let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
740            let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
741            build.ret(&[n]);
742        });
743        floats(&mut func);
744
745        let text = printed(&func, &mut names);
746        assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
747        assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
748        assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
749    }
750
751    /// `signed char a(double x) { return (signed char)x; }`, which the front end writes as a
752    /// conversion straight to eight bits and the machine has no instruction for at that width.
753    #[test]
754    fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
755        let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
756            let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
757            build.ret(&[n]);
758        });
759        floats(&mut func);
760
761        let text = printed(&func, &mut names);
762        assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
763        assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
764    }
765
766    /// The same the other way, where the widening carries the sign because the value has one.
767    #[test]
768    fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
769        let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
770            let d = build.unary(Opcode::SIToFP, args[0], f64());
771            build.ret(&[d]);
772        });
773        floats(&mut func);
774
775        let text = printed(&func, &mut names);
776        assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
777        assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
778        assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
779    }
780
781    /// The table the two of them share, which is where the whole argument about widths lives.
782    #[test]
783    fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
784        use super::holder;
785        for bits in [1, 8, 16, 32] {
786            assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
787        }
788        assert_eq!(holder(64, true), Some(64));
789        for bits in [1, 8, 16, 31] {
790            assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
791        }
792        // The one more bit an unsigned value needs is what makes these two the wider width.
793        assert_eq!(holder(32, false), Some(64));
794        assert_eq!(holder(64, false), None);
795    }
796
797    /// Sixty four bits is where the argument runs out, because an unsigned value of that width is
798    /// not a signed value of any width the IR has. Both are left for the lowering to refuse by
799    /// name, which is a better message than one about the instructions they would have become.
800    #[test]
801    fn the_unsigned_conversions_at_the_widest_width_are_left_alone() {
802        let (mut names, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
803            let d = build.unary(Opcode::UIToFP, args[0], f64());
804            build.ret(&[d]);
805        });
806        let before = printed(&func, &mut names);
807        floats(&mut func);
808        assert_eq!(printed(&func, &mut names), before);
809
810        let (mut names, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
811            let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
812            build.ret(&[n]);
813        });
814        let before = printed(&func, &mut names);
815        floats(&mut func);
816        assert_eq!(printed(&func, &mut names), before);
817    }
818
819    /// The same obligation the `switch` rewrite has, for the same reason: nothing after this
820    /// checks the IR again and everything after it assumes what the verifier would have said.
821    #[test]
822    fn what_the_float_rewrites_leave_is_valid_ir() {
823        let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
824            let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
825            let d = build.unary(Opcode::UIToFP, args[0], f64());
826            let n = build.unary(Opcode::FNeg, d, f64());
827            let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
828            build.ret(&[s]);
829        });
830        floats(&mut func);
831        let module = Module::new(names.intern("f.c"), &target());
832        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
833    }
834
835    /// Nothing else is touched, for the same reason the `switch` pass has that test: this runs
836    /// over every function whether or not one has a float in it.
837    #[test]
838    fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
839        let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
840            build.ret(&[args[0]]);
841        });
842        let before = printed(&func, &mut names);
843        floats(&mut func);
844        assert_eq!(printed(&func, &mut names), before);
845    }
846    fn access(size: u64, align: u32) -> MemInfo {
847        MemInfo { size, align, order: MemOrder::NotAtomic, tbaa: None }
848    }
849
850    /// `void c(void *to, const void *from) { *(T *)to = *(const T *)from; }` for a `T` of that
851    /// size and alignment, which is what the front end writes for a structure assignment.
852    fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
853        one(&[Type::PTR, Type::PTR], &[], |build, args| {
854            let second = match byte {
855                Some(value) => build.iconst(Type::int(8), value),
856                None => args[1],
857            };
858            let mem = build.func().add_mem(access(size, align));
859            let operands = build.func().push_values(&[args[0], second]);
860            let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
861            build.inst(data, &[]);
862            build.ret(&[]);
863        })
864    }
865
866    fn copying(size: u64, align: u32) -> (Interner, Func) {
867        moving(Opcode::Memcpy, size, align, None)
868    }
869
870    fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
871        moving(Opcode::Memset, size, align, Some(byte))
872    }
873
874    /// The plan a copy of that size and alignment becomes, as widths, which is what the offsets
875    /// follow from.
876    fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
877        Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
878    }
879
880    /// `struct point { int x, y; } a, b; a = b;`, which is sixteen bytes aligned to eight.
881    #[test]
882    fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
883        let (mut names, mut func) = copying(16, 8);
884        bulk(&mut func, &mut names, 8);
885
886        let text = printed(&func, &mut names);
887        assert!(!text.contains("memcpy"), "the copy is gone: {text}");
888        assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
889        assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
890        assert_eq!(
891            text.matches("ptr_add").count(),
892            2,
893            "no offset for the word at the front: {text}"
894        );
895    }
896
897    /// A word is as wide as the block is known to be aligned to and no wider, because a load
898    /// wider than that faults on a machine that checks and this does not know whether the one it
899    /// is compiling for does.
900    #[test]
901    fn a_word_is_as_wide_as_the_block_is_aligned_to() {
902        assert_eq!(widths(16, 8), Some(vec![8, 8]));
903        assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
904        assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
905    }
906
907    /// What is left over is narrower words rather than a run of bytes, so thirteen bytes aligned
908    /// to eight is three moves and not six.
909    #[test]
910    fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
911        assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
912        assert_eq!(widths(3, 8), Some(vec![2, 1]));
913        assert_eq!(widths(1, 8), Some(vec![1]));
914    }
915
916    /// Every offset is a multiple of the width at it, which is what lets the alignment of each
917    /// access be written down as its width.
918    #[test]
919    fn every_word_starts_somewhere_it_is_aligned_for() {
920        for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
921            assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
922        }
923    }
924
925    /// `struct big b = { 0 };`, where the part the initialiser did not name is zeroed.
926    #[test]
927    fn a_fill_is_the_byte_spread_across_each_word() {
928        let (mut names, mut func) = filling(16, 8, 0);
929        bulk(&mut func, &mut names, 8);
930
931        let text = printed(&func, &mut names);
932        assert!(!text.contains("memset"), "the fill is gone: {text}");
933        assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
934        assert!(!text.contains("load"), "a fill reads nothing: {text}");
935    }
936
937    /// The spreading is arithmetic on the byte, which is the thing a rule cannot do and the
938    /// reason this pass exists at all.
939    #[test]
940    fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
941        assert_eq!(spread(0, 8), 0);
942        assert_eq!(spread(0xff, 1), 0xff);
943        assert_eq!(spread(0xff, 4), 0xffff_ffff);
944        assert_eq!(spread(0xab, 2), 0xabab);
945        assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
946    }
947
948    /// A copy larger than the threshold is a call to the runtime rather than a run of moves.
949    #[test]
950    fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
951        let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
952        let (mut names, mut func) = copying(size, 1);
953        bulk(&mut func, &mut names, 8);
954        let text = printed(&func, &mut names);
955        assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
956
957        // And the one word under it is moves, because the threshold counts moves rather than
958        // bytes and the whole point of the threshold is that a small copy does not pay for a call.
959        let (mut names, mut func) = copying(size - 1, 1);
960        bulk(&mut func, &mut names, 8);
961        assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
962    }
963
964    /// The call passes what C passes, which is not what the IR holds. The size lives beside the
965    /// instruction in the IR and travels in a register in the call.
966    #[test]
967    fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
968        let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
969        let (mut names, mut func) = copying(size, 1);
970        bulk(&mut func, &mut names, 8);
971        let text = printed(&func, &mut names);
972        assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
973    }
974
975    /// A `memmove` is a call whatever its size, because the two sides may overlap and a run of
976    /// moves in one direction is right for only one of the two ways they can.
977    #[test]
978    fn a_move_is_a_call_however_small_it_is() {
979        let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
980        bulk(&mut func, &mut names, 8);
981        let text = printed(&func, &mut names);
982        assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
983    }
984
985    /// A fill whose byte the program works out rather than names. Spreading a value across a
986    /// word at runtime is a multiply, so this is a call rather than moves however small it is.
987    #[test]
988    fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
989        let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
990            let mem = build.func().add_mem(access(8, 8));
991            let operands = build.func().push_values(&[args[0], args[1]]);
992            let data = InstData {
993                args: operands,
994                extra: Extra::Mem(mem),
995                ..InstData::new(Opcode::Memset)
996            };
997            build.inst(data, &[]);
998            build.ret(&[]);
999        });
1000        bulk(&mut func, &mut names, 8);
1001        let text = printed(&func, &mut names);
1002        assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1003        // Widened, because C passes the byte as an `int` and the IR holds it as a byte.
1004        assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1005    }
1006
1007    /// A machine whose widest move is four bytes gets four byte words out of an eight byte block,
1008    /// however well aligned the block is.
1009    #[test]
1010    fn no_word_is_wider_than_the_machine_moves_at_once() {
1011        assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
1012        assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
1013    }
1014
1015    #[test]
1016    fn what_a_copy_becomes_is_ir_that_verifies() {
1017        let (mut names, mut func) = copying(13, 8);
1018        bulk(&mut func, &mut names, 8);
1019        let module = Module::new(names.intern("c.c"), &target());
1020        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1021    }
1022
1023    #[test]
1024    fn what_a_fill_becomes_is_ir_that_verifies() {
1025        let (mut names, mut func) = filling(13, 8, 0xff);
1026        bulk(&mut func, &mut names, 8);
1027        let module = Module::new(names.intern("f.c"), &target());
1028        rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1029    }
1030
1031    #[test]
1032    fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
1033        let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1034        let (mut names, mut func) = copying(size, 1);
1035        bulk(&mut func, &mut names, 8);
1036        let module = Module::new(names.intern("c.c"), &target());
1037        rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
1038    }
1039
1040    /// Nothing else is touched, for the same reason the other two passes have that test.
1041    #[test]
1042    fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
1043        let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1044            build.ret(&[args[0]]);
1045        });
1046        let before = printed(&func, &mut names);
1047        bulk(&mut func, &mut names, 8);
1048        assert_eq!(printed(&func, &mut names), before);
1049    }
1050}