Skip to main content

rucc_mir/
print.rs

1//! The printer: machine functions as text.
2//!
3//! Design: `spec/10-backend.md` section 10.1, which asks that `--emit=mir` and
4//! `--emit=mir-final` both round-trip.
5//!
6//! One form, printed before allocation and after it. Before, the registers are virtual and
7//! carry the class they are drawn from, because nothing else says it. After, they are physical
8//! and carry no class, because the register file says which class each register is in and a
9//! second copy of that is a thing that can disagree with the first.
10//!
11//! ```text
12//! mfunc @scale {
13//! block0(%0:gpr, %1:gpr):
14//!     %2:gpr = x64.mov_ri 4
15//!     %3:gpr = x64.imul_rr %1, %2
16//!     x64.jmp block1(%3)
17//!
18//! block1(%4:gpr):
19//!     %5:gpr = x64.lea [%4 + %1*4 + 16]
20//!     x64.ret $rax
21//! }
22//! ```
23//!
24//! What an instruction writes is to the left of the `=` and what it reads is to the right, in
25//! the order the operand vector holds them, and the registers a memory operand names appear
26//! only inside its brackets. So the text says the operand vector exactly, which is what lets
27//! the parser rebuild it, and it does not say anything twice.
28//!
29//! A virtual register says its class where it is written and nowhere else, which is at a block
30//! parameter or to the left of an `=`. Reading one is not the place to repeat it: the class is
31//! a fact about the register rather than about the reading of it, and a text that could say it
32//! twice is a text that could say it two ways.
33//!
34//! Virtual registers are numbered in the order they are defined rather than by the number they
35//! have, for the reason `rucc-ir`'s printer gives: the text is then a fact about the shape of
36//! the function rather than about which order somebody's pass happened to fill the tables in.
37//! Blocks are numbered by their place in the layout for the same reason.
38//!
39//! Spans are not printed. Debug information has its own form, and the round trip is a claim
40//! about the text rather than about the source locations behind it.
41
42use std::fmt::Write as _;
43
44use rucc_base::Interner;
45use rucc_target::{Constraint, PhysReg, RegClass, RegFile, Role};
46
47use crate::func::{Func, defs};
48use crate::inst::{Amode, Block, BlockCall, Inst, Operand, Param, Reg};
49
50/// Every function, as text, which is what `--emit=mir` writes.
51#[must_use]
52pub fn print(funcs: &[Func], names: &Interner, regs: &RegFile) -> String {
53    let mut printer = Printer::new(names, regs);
54    for (index, func) in funcs.iter().enumerate() {
55        if index > 0 {
56            printer.gap();
57        }
58        printer.func(func);
59    }
60    printer.finish()
61}
62
63/// One function, as text.
64#[must_use]
65pub fn print_func(func: &Func, names: &Interner, regs: &RegFile) -> String {
66    let mut printer = Printer::new(names, regs);
67    printer.func(func);
68    printer.finish()
69}
70
71/// A function being written out.
72#[derive(Debug)]
73pub struct Printer<'a> {
74    names: &'a Interner,
75    regs: &'a RegFile,
76    out: String,
77    /// The number each virtual register is printed as, in print order, indexed by the number it
78    /// has. `u32::MAX` for one that is read and never written, which is a function nothing
79    /// should have produced and which prints as `%?` so that the text does not claim otherwise.
80    numbers: Vec<u32>,
81    /// The number each block is printed as, indexed by its own.
82    labels: Vec<u32>,
83}
84
85impl<'a> Printer<'a> {
86    /// A printer whose names are in `names` and whose registers are those of `regs`.
87    #[must_use]
88    pub fn new(names: &'a Interner, regs: &'a RegFile) -> Printer<'a> {
89        Printer { names, regs, out: String::new(), numbers: Vec::new(), labels: Vec::new() }
90    }
91
92    /// The text written so far.
93    #[must_use]
94    pub fn finish(self) -> String {
95        self.out
96    }
97
98    /// A blank line, which is what separates one function from the next.
99    pub fn gap(&mut self) {
100        self.out.push('\n');
101    }
102
103    /// One function: its name, then its blocks.
104    pub fn func(&mut self, func: &Func) {
105        self.number(func);
106        let _ = writeln!(self.out, "mfunc @{} {{", self.names.resolve(func.name));
107        for (index, block) in func.blocks().enumerate() {
108            if index > 0 {
109                self.out.push('\n');
110            }
111            self.block(func, block, index);
112        }
113        self.out.push_str("}\n");
114    }
115
116    /// Gives every virtual register and every block the number it is printed as.
117    fn number(&mut self, func: &Func) {
118        self.numbers.clear();
119        self.numbers.resize(func.vregs(), u32::MAX);
120        self.labels.clear();
121        self.labels.resize(func.block_count(), u32::MAX);
122        let mut next = 0;
123        for (index, block) in func.blocks().enumerate() {
124            self.labels[block.index()] = index as u32;
125            for param in &func[block].params {
126                self.give(param.reg, &mut next);
127            }
128            for inst in func.insts(block) {
129                let operands = &func[func[inst].operands];
130                for operand in &operands[..defs(operands)] {
131                    self.give(operand.reg, &mut next);
132                }
133            }
134        }
135    }
136
137    /// Gives one register the next number, if it is virtual and has none yet.
138    fn give(&mut self, reg: Reg, next: &mut u32) {
139        let Some(number) = reg.number() else { return };
140        let Some(slot) = self.numbers.get_mut(number as usize) else { return };
141        if *slot == u32::MAX {
142            *slot = *next;
143            *next += 1;
144        }
145    }
146
147    /// One block: its label with its parameters, then its instructions.
148    fn block(&mut self, func: &Func, block: Block, index: usize) {
149        let _ = write!(self.out, "block{index}");
150        let params = &func[block].params;
151        if !params.is_empty() {
152            self.out.push('(');
153            for (at, param) in params.iter().enumerate() {
154                if at > 0 {
155                    self.out.push_str(", ");
156                }
157                self.param(*param);
158            }
159            self.out.push(')');
160        }
161        self.out.push_str(":\n");
162        let last = func.terminator(block);
163        for inst in func.insts(block) {
164            self.inst(func, block, inst, Some(inst) == last);
165        }
166        // Where a block goes is on the block, so a block with nothing in it still has somewhere to
167        // go, and splitting a critical edge makes exactly that: a block that is an edge and no
168        // instructions. The arms go on a line of their own, since there is no last instruction to
169        // put them after and printing nothing would lose them.
170        if last.is_none() && !func[block].succs.is_empty() {
171            let arms: Vec<String> = func[block]
172                .succs
173                .iter()
174                .map(|succ| self.text(|printer| printer.block_call(func, succ)))
175                .collect();
176            let _ = writeln!(self.out, "    {}", arms.join(", "));
177        }
178    }
179
180    /// One parameter, which is a register and the class it arrives in.
181    fn param(&mut self, param: Param) {
182        self.reg(param.reg, param.class, true);
183    }
184
185    /// One instruction, indented, on one line.
186    ///
187    /// The successors are printed on the terminator, which is where a reader looks for them,
188    /// although the block is what holds them.
189    fn inst(&mut self, func: &Func, block: Block, inst: Inst, terminator: bool) {
190        let data = func[inst];
191        let operands = &func[data.operands];
192        let written = defs(operands);
193        self.out.push_str("    ");
194        for (at, operand) in operands[..written].iter().enumerate() {
195            if at > 0 {
196                self.out.push_str(", ");
197            }
198            self.operand(*operand);
199        }
200        if written > 0 {
201            self.out.push_str(" = ");
202        }
203        self.out.push_str(self.names.resolve(data.opcode.name()));
204
205        // Everything to the right of the opcode is one comma-separated list, however many
206        // different kinds of thing are in it. A fixed order and one separator is what makes the
207        // text unambiguous to read back without the reader having to know what the opcode is.
208        let mut rest: Vec<String> = Vec::new();
209        let addressed = data.mem.map(|mem| func[mem]);
210        for (at, operand) in operands.iter().enumerate().skip(written) {
211            if names_operand(addressed.as_ref(), at) {
212                continue;
213            }
214            rest.push(self.text(|printer| printer.operand(*operand)));
215        }
216        if let Some(symbol) = data.symbol {
217            rest.push(format!("@{}", self.names.resolve(symbol)));
218        }
219        if let Some(amode) = addressed {
220            rest.push(self.text(|printer| printer.amode(operands, &amode)));
221        }
222        if let Some(imm) = data.imm {
223            rest.push(func[imm].0.to_string());
224        }
225        if terminator {
226            for succ in &func[block].succs {
227                rest.push(self.text(|printer| printer.block_call(func, succ)));
228            }
229        }
230        for (at, text) in rest.iter().enumerate() {
231            self.out.push_str(if at > 0 { ", " } else { " " });
232            self.out.push_str(text);
233        }
234        self.out.push('\n');
235    }
236
237    /// One operand: its register, and whatever is true of it besides.
238    fn operand(&mut self, operand: Operand) {
239        if operand.role == Role::EarlyDef {
240            self.out.push_str("early ");
241        }
242        self.reg(operand.reg, operand.class, operand.role.is_def());
243        match operand.constraint {
244            Constraint::Reg => {}
245            Constraint::Any => self.out.push_str("(any)"),
246            Constraint::Stack => self.out.push_str("(stack)"),
247            Constraint::Fixed(phys) => {
248                self.out.push('(');
249                self.phys(operand.class, phys);
250                self.out.push(')');
251            }
252            Constraint::Reuse(at) => {
253                let _ = write!(self.out, "(reuse {at})");
254            }
255        }
256    }
257
258    /// One register: virtual, with its class where it is being written, or physical with the
259    /// name the register file gives it.
260    fn reg(&mut self, reg: Reg, class: RegClass, declared: bool) {
261        if let Some(phys) = reg.phys() {
262            self.phys(class, phys);
263            return;
264        }
265        match self.printed(reg) {
266            Some(number) => {
267                let _ = write!(self.out, "%{number}");
268            }
269            None => self.out.push_str("%?"),
270        }
271        if declared {
272            let name = self.regs.class(class).map_or("?", |info| info.name);
273            let _ = write!(self.out, ":{name}");
274        }
275    }
276
277    /// One physical register, by the name the register file gives it.
278    fn phys(&mut self, class: RegClass, reg: PhysReg) {
279        let _ = write!(self.out, "${}", self.regs.name(class, reg).unwrap_or("?"));
280    }
281
282    /// The number a virtual register is printed as, or `None` for one nothing defines.
283    fn printed(&self, reg: Reg) -> Option<u32> {
284        let number = reg.number()?;
285        match self.numbers.get(number as usize).copied() {
286            Some(u32::MAX) | None => None,
287            Some(number) => Some(number),
288        }
289    }
290
291    /// One addressing mode, in brackets.
292    fn amode(&mut self, operands: &[Operand], amode: &Amode) {
293        self.out.push('[');
294        let mut written = false;
295        if let Some(symbol) = amode.symbol {
296            let _ = write!(self.out, "@{}", self.names.resolve(symbol));
297            written = true;
298        }
299        if let Some(operand) = amode.base.and_then(|at| operands.get(usize::from(at))) {
300            if written {
301                self.out.push_str(" + ");
302            }
303            self.reg(operand.reg, operand.class, false);
304            written = true;
305        }
306        if let Some(operand) = amode.index.and_then(|at| operands.get(usize::from(at))) {
307            if written {
308                self.out.push_str(" + ");
309            }
310            self.reg(operand.reg, operand.class, false);
311            if amode.scale != 1 {
312                let _ = write!(self.out, "*{}", amode.scale);
313            }
314            written = true;
315        }
316        // A mode that names nothing at all still prints a number, because an empty pair of
317        // brackets would say less than the mode does.
318        if amode.disp != 0 || !written {
319            if written {
320                let sign = if amode.disp < 0 { '-' } else { '+' };
321                let _ = write!(self.out, " {sign} {}", i64::from(amode.disp).abs());
322            } else {
323                let _ = write!(self.out, "{}", amode.disp);
324            }
325        }
326        self.out.push(']');
327    }
328
329    /// One arm of a terminator: where it goes, and what it takes.
330    ///
331    /// The arguments carry no class, because the parameters they arrive as are declared at the
332    /// block they arrive in.
333    fn block_call(&mut self, func: &Func, call: &BlockCall) {
334        match self.labels.get(call.block.index()).copied() {
335            Some(u32::MAX) | None => self.out.push_str("block?"),
336            Some(number) => {
337                let _ = write!(self.out, "block{number}");
338            }
339        }
340        if call.args.is_empty() {
341            return;
342        }
343        self.out.push('(');
344        for (at, &arg) in call.args.iter().enumerate() {
345            if at > 0 {
346                self.out.push_str(", ");
347            }
348            // Which class an argument is in is the class of the parameter it arrives as, which
349            // the block it goes to is what declares. It is needed only to name a physical
350            // register, which is named per class.
351            let class = func[call.block]
352                .params
353                .get(at)
354                .map_or_else(|| RegClass::new(0), |param| param.class);
355            self.reg(arg, class, false);
356        }
357        self.out.push(')');
358    }
359
360    /// What one of the printing methods writes, on its own, for a list that is joined later.
361    fn text(&mut self, write: impl FnOnce(&mut Self)) -> String {
362        let held = std::mem::take(&mut self.out);
363        write(self);
364        std::mem::replace(&mut self.out, held)
365    }
366}
367
368/// Whether the operand at that index is one an addressing mode names, and so is printed inside
369/// its brackets rather than in the operand list.
370fn names_operand(amode: Option<&Amode>, at: usize) -> bool {
371    let Some(amode) = amode else { return false };
372    let at = u8::try_from(at).ok();
373    amode.base == at || amode.index == at
374}
375
376#[cfg(test)]
377mod tests {
378    use rucc_target::PhysReg;
379
380    use super::*;
381    use crate::fixtures::{BEFORE, REGS};
382    use crate::inst::{BlockCall, Mem, Opcode};
383
384    /// The function `BEFORE` is the text of, built by hand.
385    ///
386    /// Written out rather than parsed, because a printer checked against text its own parser
387    /// produced is a printer checked against itself.
388    fn scale() -> (Interner, Func) {
389        let mut names = Interner::new();
390        let gpr = REGS.class_named("gpr").expect("the fixture file has a gpr class");
391        let xmm = REGS.class_named("xmm").expect("the fixture file has an xmm class");
392        let rax = named("rax");
393        let rdx = named("rdx");
394        let mut func = Func::new(names.intern("scale"));
395        let op = |names: &mut Interner, text: &str| Opcode::new(names.intern(text));
396
397        let entry = func.create_block();
398        let body = func.create_block();
399        let exit = func.create_block();
400
401        let n = func.append_param(entry, gpr);
402        let stride = func.append_param(entry, gpr);
403        let four = func.new_vreg(gpr);
404        let scaled = func.new_vreg(gpr);
405        let opcode = op(&mut names, "x64.mov_ri");
406        func.build(entry, opcode).def(four, gpr).imm(4).finish();
407        let opcode = op(&mut names, "x64.imul_rr");
408        func.build(entry, opcode)
409            .operand(Operand::write(scaled, gpr).with(Constraint::Reuse(1)))
410            .uses(stride, gpr)
411            .uses(four, gpr)
412            .finish();
413        let opcode = op(&mut names, "x64.cmp_ri");
414        func.build(entry, opcode).uses(n, gpr).imm(0).finish();
415        let opcode = op(&mut names, "x64.jle");
416        func.build(entry, opcode).finish();
417        *func.succs_mut(entry) =
418            vec![BlockCall::with(exit, vec![n]), BlockCall::with(body, vec![scaled, stride])];
419
420        let base = func.append_param(body, gpr);
421        let index = func.append_param(body, gpr);
422        let addr = func.new_vreg(gpr);
423        let loaded = func.new_vreg(gpr);
424        let quotient = func.new_vreg(gpr);
425        let remainder = func.new_vreg(gpr);
426        let opcode = op(&mut names, "x64.lea");
427        func.build(body, opcode)
428            .def(addr, gpr)
429            .mem(Mem::at(Operand::read(base, gpr)).indexed(Operand::read(index, gpr), 4).plus(16))
430            .finish();
431        let counter = names.intern("counter");
432        let opcode = op(&mut names, "x64.mov_rm");
433        func.build(body, opcode).def(loaded, gpr).mem(Mem::of(counter).plus(8)).finish();
434        let opcode = op(&mut names, "x64.mov_mi");
435        func.build(body, opcode).mem(Mem::at(Operand::read(addr, gpr)).plus(-4)).imm(1).finish();
436        let opcode = op(&mut names, "x64.idiv_rr");
437        func.build(body, opcode)
438            .operand(Operand::write(quotient, gpr).with(Constraint::Fixed(rax)))
439            .operand(Operand::write_early(remainder, gpr).with(Constraint::Fixed(rdx)))
440            .operand(Operand::read(loaded, gpr).with(Constraint::Fixed(rax)))
441            .operand(Operand::read(addr, gpr).with(Constraint::Any))
442            .finish();
443        let opcode = op(&mut names, "x64.cmp_rr");
444        func.build(body, opcode)
445            .uses(quotient, gpr)
446            .operand(Operand::read(remainder, gpr).with(Constraint::Stack))
447            .finish();
448        let opcode = op(&mut names, "x64.jmp");
449        func.build(body, opcode).finish();
450        *func.succs_mut(body) = vec![BlockCall::with(exit, vec![quotient])];
451
452        let result = func.append_param(exit, gpr);
453        let moved = func.new_vreg(xmm);
454        let opcode = op(&mut names, "x64.movd_xr");
455        func.build(exit, opcode).def(moved, xmm).uses(result, gpr).finish();
456        let opcode = op(&mut names, "x64.ret");
457        func.build(exit, opcode).uses(Reg::physical(rax), gpr).finish();
458
459        (names, func)
460    }
461
462    /// One physical register of the fixture file, by name.
463    fn named(name: &str) -> PhysReg {
464        REGS.reg_named(name).expect("the fixture file has that register").1
465    }
466
467    #[test]
468    fn a_function_prints_as_the_fixture_says() {
469        let (names, func) = scale();
470        assert_eq!(print_func(&func, &names, &REGS), BEFORE);
471    }
472
473    #[test]
474    fn two_functions_are_printed_with_a_blank_line_between_them() {
475        let (names, func) = scale();
476        let empty = Func::new(func.name);
477        let text = print(&[empty, func], &names, &REGS);
478        assert_eq!(text, format!("mfunc @scale {{\n}}\n\n{BEFORE}"));
479    }
480
481    #[test]
482    fn a_register_nothing_writes_prints_as_one_nothing_writes() {
483        let mut names = Interner::new();
484        let gpr = REGS.class_named("gpr").expect("the fixture file has a gpr class");
485        let mut func = Func::new(names.intern("f"));
486        let block = func.create_block();
487        let missing = func.new_vreg(gpr);
488        let opcode = Opcode::new(names.intern("x64.ret"));
489        func.build(block, opcode).uses(missing, gpr).finish();
490        assert_eq!(print_func(&func, &names, &REGS), "mfunc @f {\nblock0:\n    x64.ret %?\n}\n");
491    }
492
493    #[test]
494    fn a_block_with_nothing_in_it_still_prints_where_it_goes() {
495        let mut names = Interner::new();
496        let gpr = REGS.class_named("gpr").expect("the fixture file has a gpr class");
497        let mut func = Func::new(names.intern("f"));
498        let entry = func.create_block();
499        let exit = func.create_block();
500        let value = func.append_param(entry, gpr);
501        func.append_param(exit, gpr);
502        *func.succs_mut(entry) = vec![BlockCall::with(exit, vec![value])];
503
504        // Splitting a critical edge makes exactly this: a block that is an edge and nothing else.
505        // There is no last instruction to hang the arm off, so it goes on a line of its own, and
506        // printing nothing would lose the only thing the block is.
507        assert_eq!(
508            print_func(&func, &names, &REGS),
509            "mfunc @f {\nblock0(%0:gpr):\n    block1(%0)\n\nblock1(%1:gpr):\n}\n"
510        );
511    }
512}