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::{PhysReg, RegClass, RegFile};
46
47use crate::func::{Func, defs};
48use crate::inst::{Amode, Block, BlockCall, Constraint, Inst, Operand, Param, Reg, Role};
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    }
167
168    /// One parameter, which is a register and the class it arrives in.
169    fn param(&mut self, param: Param) {
170        self.reg(param.reg, param.class, true);
171    }
172
173    /// One instruction, indented, on one line.
174    ///
175    /// The successors are printed on the terminator, which is where a reader looks for them,
176    /// although the block is what holds them.
177    fn inst(&mut self, func: &Func, block: Block, inst: Inst, terminator: bool) {
178        let data = func[inst];
179        let operands = &func[data.operands];
180        let written = defs(operands);
181        self.out.push_str("    ");
182        for (at, operand) in operands[..written].iter().enumerate() {
183            if at > 0 {
184                self.out.push_str(", ");
185            }
186            self.operand(*operand);
187        }
188        if written > 0 {
189            self.out.push_str(" = ");
190        }
191        self.out.push_str(self.names.resolve(data.opcode.name()));
192
193        // Everything to the right of the opcode is one comma-separated list, however many
194        // different kinds of thing are in it. A fixed order and one separator is what makes the
195        // text unambiguous to read back without the reader having to know what the opcode is.
196        let mut rest: Vec<String> = Vec::new();
197        let addressed = data.mem.map(|mem| func[mem]);
198        for (at, operand) in operands.iter().enumerate().skip(written) {
199            if names_operand(addressed.as_ref(), at) {
200                continue;
201            }
202            rest.push(self.text(|printer| printer.operand(*operand)));
203        }
204        if let Some(symbol) = data.symbol {
205            rest.push(format!("@{}", self.names.resolve(symbol)));
206        }
207        if let Some(amode) = addressed {
208            rest.push(self.text(|printer| printer.amode(operands, &amode)));
209        }
210        if let Some(imm) = data.imm {
211            rest.push(func[imm].0.to_string());
212        }
213        if terminator {
214            for succ in &func[block].succs {
215                rest.push(self.text(|printer| printer.block_call(func, succ)));
216            }
217        }
218        for (at, text) in rest.iter().enumerate() {
219            self.out.push_str(if at > 0 { ", " } else { " " });
220            self.out.push_str(text);
221        }
222        self.out.push('\n');
223    }
224
225    /// One operand: its register, and whatever is true of it besides.
226    fn operand(&mut self, operand: Operand) {
227        if operand.role == Role::EarlyDef {
228            self.out.push_str("early ");
229        }
230        self.reg(operand.reg, operand.class, operand.role.is_def());
231        match operand.constraint {
232            Constraint::Reg => {}
233            Constraint::Any => self.out.push_str("(any)"),
234            Constraint::Stack => self.out.push_str("(stack)"),
235            Constraint::Fixed(phys) => {
236                self.out.push('(');
237                self.phys(operand.class, phys);
238                self.out.push(')');
239            }
240            Constraint::Reuse(at) => {
241                let _ = write!(self.out, "(reuse {at})");
242            }
243        }
244    }
245
246    /// One register: virtual, with its class where it is being written, or physical with the
247    /// name the register file gives it.
248    fn reg(&mut self, reg: Reg, class: RegClass, declared: bool) {
249        if let Some(phys) = reg.phys() {
250            self.phys(class, phys);
251            return;
252        }
253        match self.printed(reg) {
254            Some(number) => {
255                let _ = write!(self.out, "%{number}");
256            }
257            None => self.out.push_str("%?"),
258        }
259        if declared {
260            let name = self.regs.class(class).map_or("?", |info| info.name);
261            let _ = write!(self.out, ":{name}");
262        }
263    }
264
265    /// One physical register, by the name the register file gives it.
266    fn phys(&mut self, class: RegClass, reg: PhysReg) {
267        let _ = write!(self.out, "${}", self.regs.name(class, reg).unwrap_or("?"));
268    }
269
270    /// The number a virtual register is printed as, or `None` for one nothing defines.
271    fn printed(&self, reg: Reg) -> Option<u32> {
272        let number = reg.number()?;
273        match self.numbers.get(number as usize).copied() {
274            Some(u32::MAX) | None => None,
275            Some(number) => Some(number),
276        }
277    }
278
279    /// One addressing mode, in brackets.
280    fn amode(&mut self, operands: &[Operand], amode: &Amode) {
281        self.out.push('[');
282        let mut written = false;
283        if let Some(symbol) = amode.symbol {
284            let _ = write!(self.out, "@{}", self.names.resolve(symbol));
285            written = true;
286        }
287        if let Some(operand) = amode.base.and_then(|at| operands.get(usize::from(at))) {
288            if written {
289                self.out.push_str(" + ");
290            }
291            self.reg(operand.reg, operand.class, false);
292            written = true;
293        }
294        if let Some(operand) = amode.index.and_then(|at| operands.get(usize::from(at))) {
295            if written {
296                self.out.push_str(" + ");
297            }
298            self.reg(operand.reg, operand.class, false);
299            if amode.scale != 1 {
300                let _ = write!(self.out, "*{}", amode.scale);
301            }
302            written = true;
303        }
304        // A mode that names nothing at all still prints a number, because an empty pair of
305        // brackets would say less than the mode does.
306        if amode.disp != 0 || !written {
307            if written {
308                let sign = if amode.disp < 0 { '-' } else { '+' };
309                let _ = write!(self.out, " {sign} {}", i64::from(amode.disp).abs());
310            } else {
311                let _ = write!(self.out, "{}", amode.disp);
312            }
313        }
314        self.out.push(']');
315    }
316
317    /// One arm of a terminator: where it goes, and what it takes.
318    ///
319    /// The arguments carry no class, because the parameters they arrive as are declared at the
320    /// block they arrive in.
321    fn block_call(&mut self, func: &Func, call: &BlockCall) {
322        match self.labels.get(call.block.index()).copied() {
323            Some(u32::MAX) | None => self.out.push_str("block?"),
324            Some(number) => {
325                let _ = write!(self.out, "block{number}");
326            }
327        }
328        if call.args.is_empty() {
329            return;
330        }
331        self.out.push('(');
332        for (at, &arg) in call.args.iter().enumerate() {
333            if at > 0 {
334                self.out.push_str(", ");
335            }
336            // Which class an argument is in is the class of the parameter it arrives as, which
337            // the block it goes to is what declares. It is needed only to name a physical
338            // register, which is named per class.
339            let class = func[call.block]
340                .params
341                .get(at)
342                .map_or_else(|| RegClass::new(0), |param| param.class);
343            self.reg(arg, class, false);
344        }
345        self.out.push(')');
346    }
347
348    /// What one of the printing methods writes, on its own, for a list that is joined later.
349    fn text(&mut self, write: impl FnOnce(&mut Self)) -> String {
350        let held = std::mem::take(&mut self.out);
351        write(self);
352        std::mem::replace(&mut self.out, held)
353    }
354}
355
356/// Whether the operand at that index is one an addressing mode names, and so is printed inside
357/// its brackets rather than in the operand list.
358fn names_operand(amode: Option<&Amode>, at: usize) -> bool {
359    let Some(amode) = amode else { return false };
360    let at = u8::try_from(at).ok();
361    amode.base == at || amode.index == at
362}
363
364#[cfg(test)]
365mod tests {
366    use rucc_target::PhysReg;
367
368    use super::*;
369    use crate::fixtures::{BEFORE, REGS};
370    use crate::inst::{BlockCall, Mem, Opcode};
371
372    /// The function `BEFORE` is the text of, built by hand.
373    ///
374    /// Written out rather than parsed, because a printer checked against text its own parser
375    /// produced is a printer checked against itself.
376    fn scale() -> (Interner, Func) {
377        let mut names = Interner::new();
378        let gpr = REGS.class_named("gpr").expect("the fixture file has a gpr class");
379        let xmm = REGS.class_named("xmm").expect("the fixture file has an xmm class");
380        let rax = named("rax");
381        let rdx = named("rdx");
382        let mut func = Func::new(names.intern("scale"));
383        let op = |names: &mut Interner, text: &str| Opcode::new(names.intern(text));
384
385        let entry = func.create_block();
386        let body = func.create_block();
387        let exit = func.create_block();
388
389        let n = func.append_param(entry, gpr);
390        let stride = func.append_param(entry, gpr);
391        let four = func.new_vreg(gpr);
392        let scaled = func.new_vreg(gpr);
393        let opcode = op(&mut names, "x64.mov_ri");
394        func.build(entry, opcode).def(four, gpr).imm(4).finish();
395        let opcode = op(&mut names, "x64.imul_rr");
396        func.build(entry, opcode)
397            .operand(Operand::write(scaled, gpr).with(Constraint::Reuse(1)))
398            .uses(stride, gpr)
399            .uses(four, gpr)
400            .finish();
401        let opcode = op(&mut names, "x64.cmp_ri");
402        func.build(entry, opcode).uses(n, gpr).imm(0).finish();
403        let opcode = op(&mut names, "x64.jle");
404        func.build(entry, opcode).finish();
405        *func.succs_mut(entry) =
406            vec![BlockCall::with(exit, vec![n]), BlockCall::with(body, vec![scaled, stride])];
407
408        let base = func.append_param(body, gpr);
409        let index = func.append_param(body, gpr);
410        let addr = func.new_vreg(gpr);
411        let loaded = func.new_vreg(gpr);
412        let quotient = func.new_vreg(gpr);
413        let remainder = func.new_vreg(gpr);
414        let opcode = op(&mut names, "x64.lea");
415        func.build(body, opcode)
416            .def(addr, gpr)
417            .mem(Mem::at(Operand::read(base, gpr)).indexed(Operand::read(index, gpr), 4).plus(16))
418            .finish();
419        let counter = names.intern("counter");
420        let opcode = op(&mut names, "x64.mov_rm");
421        func.build(body, opcode).def(loaded, gpr).mem(Mem::of(counter).plus(8)).finish();
422        let opcode = op(&mut names, "x64.mov_mi");
423        func.build(body, opcode).mem(Mem::at(Operand::read(addr, gpr)).plus(-4)).imm(1).finish();
424        let opcode = op(&mut names, "x64.idiv_rr");
425        func.build(body, opcode)
426            .operand(Operand::write(quotient, gpr).with(Constraint::Fixed(rax)))
427            .operand(Operand::write_early(remainder, gpr).with(Constraint::Fixed(rdx)))
428            .operand(Operand::read(loaded, gpr).with(Constraint::Fixed(rax)))
429            .operand(Operand::read(addr, gpr).with(Constraint::Any))
430            .finish();
431        let opcode = op(&mut names, "x64.cmp_rr");
432        func.build(body, opcode)
433            .uses(quotient, gpr)
434            .operand(Operand::read(remainder, gpr).with(Constraint::Stack))
435            .finish();
436        let opcode = op(&mut names, "x64.jmp");
437        func.build(body, opcode).finish();
438        *func.succs_mut(body) = vec![BlockCall::with(exit, vec![quotient])];
439
440        let result = func.append_param(exit, gpr);
441        let moved = func.new_vreg(xmm);
442        let opcode = op(&mut names, "x64.movd_xr");
443        func.build(exit, opcode).def(moved, xmm).uses(result, gpr).finish();
444        let opcode = op(&mut names, "x64.ret");
445        func.build(exit, opcode).uses(Reg::physical(rax), gpr).finish();
446
447        (names, func)
448    }
449
450    /// One physical register of the fixture file, by name.
451    fn named(name: &str) -> PhysReg {
452        REGS.reg_named(name).expect("the fixture file has that register").1
453    }
454
455    #[test]
456    fn a_function_prints_as_the_fixture_says() {
457        let (names, func) = scale();
458        assert_eq!(print_func(&func, &names, &REGS), BEFORE);
459    }
460
461    #[test]
462    fn two_functions_are_printed_with_a_blank_line_between_them() {
463        let (names, func) = scale();
464        let empty = Func::new(func.name);
465        let text = print(&[empty, func], &names, &REGS);
466        assert_eq!(text, format!("mfunc @scale {{\n}}\n\n{BEFORE}"));
467    }
468
469    #[test]
470    fn a_register_nothing_writes_prints_as_one_nothing_writes() {
471        let mut names = Interner::new();
472        let gpr = REGS.class_named("gpr").expect("the fixture file has a gpr class");
473        let mut func = Func::new(names.intern("f"));
474        let block = func.create_block();
475        let missing = func.new_vreg(gpr);
476        let opcode = Opcode::new(names.intern("x64.ret"));
477        func.build(block, opcode).uses(missing, gpr).finish();
478        assert_eq!(print_func(&func, &names, &REGS), "mfunc @f {\nblock0:\n    x64.ret %?\n}\n");
479    }
480}