use std::fmt::Write as _;
use rucc_base::Interner;
use rucc_target::{PhysReg, RegClass, RegFile};
use crate::func::{Func, defs};
use crate::inst::{Amode, Block, BlockCall, Constraint, Inst, Operand, Param, Reg, Role};
#[must_use]
pub fn print(funcs: &[Func], names: &Interner, regs: &RegFile) -> String {
let mut printer = Printer::new(names, regs);
for (index, func) in funcs.iter().enumerate() {
if index > 0 {
printer.gap();
}
printer.func(func);
}
printer.finish()
}
#[must_use]
pub fn print_func(func: &Func, names: &Interner, regs: &RegFile) -> String {
let mut printer = Printer::new(names, regs);
printer.func(func);
printer.finish()
}
#[derive(Debug)]
pub struct Printer<'a> {
names: &'a Interner,
regs: &'a RegFile,
out: String,
numbers: Vec<u32>,
labels: Vec<u32>,
}
impl<'a> Printer<'a> {
#[must_use]
pub fn new(names: &'a Interner, regs: &'a RegFile) -> Printer<'a> {
Printer { names, regs, out: String::new(), numbers: Vec::new(), labels: Vec::new() }
}
#[must_use]
pub fn finish(self) -> String {
self.out
}
pub fn gap(&mut self) {
self.out.push('\n');
}
pub fn func(&mut self, func: &Func) {
self.number(func);
let _ = writeln!(self.out, "mfunc @{} {{", self.names.resolve(func.name));
for (index, block) in func.blocks().enumerate() {
if index > 0 {
self.out.push('\n');
}
self.block(func, block, index);
}
self.out.push_str("}\n");
}
fn number(&mut self, func: &Func) {
self.numbers.clear();
self.numbers.resize(func.vregs(), u32::MAX);
self.labels.clear();
self.labels.resize(func.block_count(), u32::MAX);
let mut next = 0;
for (index, block) in func.blocks().enumerate() {
self.labels[block.index()] = index as u32;
for param in &func[block].params {
self.give(param.reg, &mut next);
}
for inst in func.insts(block) {
let operands = &func[func[inst].operands];
for operand in &operands[..defs(operands)] {
self.give(operand.reg, &mut next);
}
}
}
}
fn give(&mut self, reg: Reg, next: &mut u32) {
let Some(number) = reg.number() else { return };
let Some(slot) = self.numbers.get_mut(number as usize) else { return };
if *slot == u32::MAX {
*slot = *next;
*next += 1;
}
}
fn block(&mut self, func: &Func, block: Block, index: usize) {
let _ = write!(self.out, "block{index}");
let params = &func[block].params;
if !params.is_empty() {
self.out.push('(');
for (at, param) in params.iter().enumerate() {
if at > 0 {
self.out.push_str(", ");
}
self.param(*param);
}
self.out.push(')');
}
self.out.push_str(":\n");
let last = func.terminator(block);
for inst in func.insts(block) {
self.inst(func, block, inst, Some(inst) == last);
}
}
fn param(&mut self, param: Param) {
self.reg(param.reg, param.class, true);
}
fn inst(&mut self, func: &Func, block: Block, inst: Inst, terminator: bool) {
let data = func[inst];
let operands = &func[data.operands];
let written = defs(operands);
self.out.push_str(" ");
for (at, operand) in operands[..written].iter().enumerate() {
if at > 0 {
self.out.push_str(", ");
}
self.operand(*operand);
}
if written > 0 {
self.out.push_str(" = ");
}
self.out.push_str(self.names.resolve(data.opcode.name()));
let mut rest: Vec<String> = Vec::new();
let addressed = data.mem.map(|mem| func[mem]);
for (at, operand) in operands.iter().enumerate().skip(written) {
if names_operand(addressed.as_ref(), at) {
continue;
}
rest.push(self.text(|printer| printer.operand(*operand)));
}
if let Some(symbol) = data.symbol {
rest.push(format!("@{}", self.names.resolve(symbol)));
}
if let Some(amode) = addressed {
rest.push(self.text(|printer| printer.amode(operands, &amode)));
}
if let Some(imm) = data.imm {
rest.push(func[imm].0.to_string());
}
if terminator {
for succ in &func[block].succs {
rest.push(self.text(|printer| printer.block_call(func, succ)));
}
}
for (at, text) in rest.iter().enumerate() {
self.out.push_str(if at > 0 { ", " } else { " " });
self.out.push_str(text);
}
self.out.push('\n');
}
fn operand(&mut self, operand: Operand) {
if operand.role == Role::EarlyDef {
self.out.push_str("early ");
}
self.reg(operand.reg, operand.class, operand.role.is_def());
match operand.constraint {
Constraint::Reg => {}
Constraint::Any => self.out.push_str("(any)"),
Constraint::Stack => self.out.push_str("(stack)"),
Constraint::Fixed(phys) => {
self.out.push('(');
self.phys(operand.class, phys);
self.out.push(')');
}
Constraint::Reuse(at) => {
let _ = write!(self.out, "(reuse {at})");
}
}
}
fn reg(&mut self, reg: Reg, class: RegClass, declared: bool) {
if let Some(phys) = reg.phys() {
self.phys(class, phys);
return;
}
match self.printed(reg) {
Some(number) => {
let _ = write!(self.out, "%{number}");
}
None => self.out.push_str("%?"),
}
if declared {
let name = self.regs.class(class).map_or("?", |info| info.name);
let _ = write!(self.out, ":{name}");
}
}
fn phys(&mut self, class: RegClass, reg: PhysReg) {
let _ = write!(self.out, "${}", self.regs.name(class, reg).unwrap_or("?"));
}
fn printed(&self, reg: Reg) -> Option<u32> {
let number = reg.number()?;
match self.numbers.get(number as usize).copied() {
Some(u32::MAX) | None => None,
Some(number) => Some(number),
}
}
fn amode(&mut self, operands: &[Operand], amode: &Amode) {
self.out.push('[');
let mut written = false;
if let Some(symbol) = amode.symbol {
let _ = write!(self.out, "@{}", self.names.resolve(symbol));
written = true;
}
if let Some(operand) = amode.base.and_then(|at| operands.get(usize::from(at))) {
if written {
self.out.push_str(" + ");
}
self.reg(operand.reg, operand.class, false);
written = true;
}
if let Some(operand) = amode.index.and_then(|at| operands.get(usize::from(at))) {
if written {
self.out.push_str(" + ");
}
self.reg(operand.reg, operand.class, false);
if amode.scale != 1 {
let _ = write!(self.out, "*{}", amode.scale);
}
written = true;
}
if amode.disp != 0 || !written {
if written {
let sign = if amode.disp < 0 { '-' } else { '+' };
let _ = write!(self.out, " {sign} {}", i64::from(amode.disp).abs());
} else {
let _ = write!(self.out, "{}", amode.disp);
}
}
self.out.push(']');
}
fn block_call(&mut self, func: &Func, call: &BlockCall) {
match self.labels.get(call.block.index()).copied() {
Some(u32::MAX) | None => self.out.push_str("block?"),
Some(number) => {
let _ = write!(self.out, "block{number}");
}
}
if call.args.is_empty() {
return;
}
self.out.push('(');
for (at, &arg) in call.args.iter().enumerate() {
if at > 0 {
self.out.push_str(", ");
}
let class = func[call.block]
.params
.get(at)
.map_or_else(|| RegClass::new(0), |param| param.class);
self.reg(arg, class, false);
}
self.out.push(')');
}
fn text(&mut self, write: impl FnOnce(&mut Self)) -> String {
let held = std::mem::take(&mut self.out);
write(self);
std::mem::replace(&mut self.out, held)
}
}
fn names_operand(amode: Option<&Amode>, at: usize) -> bool {
let Some(amode) = amode else { return false };
let at = u8::try_from(at).ok();
amode.base == at || amode.index == at
}
#[cfg(test)]
mod tests {
use rucc_target::PhysReg;
use super::*;
use crate::fixtures::{BEFORE, REGS};
use crate::inst::{BlockCall, Mem, Opcode};
fn scale() -> (Interner, Func) {
let mut names = Interner::new();
let gpr = REGS.class_named("gpr").expect("the fixture file has a gpr class");
let xmm = REGS.class_named("xmm").expect("the fixture file has an xmm class");
let rax = named("rax");
let rdx = named("rdx");
let mut func = Func::new(names.intern("scale"));
let op = |names: &mut Interner, text: &str| Opcode::new(names.intern(text));
let entry = func.create_block();
let body = func.create_block();
let exit = func.create_block();
let n = func.append_param(entry, gpr);
let stride = func.append_param(entry, gpr);
let four = func.new_vreg(gpr);
let scaled = func.new_vreg(gpr);
let opcode = op(&mut names, "x64.mov_ri");
func.build(entry, opcode).def(four, gpr).imm(4).finish();
let opcode = op(&mut names, "x64.imul_rr");
func.build(entry, opcode)
.operand(Operand::write(scaled, gpr).with(Constraint::Reuse(1)))
.uses(stride, gpr)
.uses(four, gpr)
.finish();
let opcode = op(&mut names, "x64.cmp_ri");
func.build(entry, opcode).uses(n, gpr).imm(0).finish();
let opcode = op(&mut names, "x64.jle");
func.build(entry, opcode).finish();
*func.succs_mut(entry) =
vec![BlockCall::with(exit, vec![n]), BlockCall::with(body, vec![scaled, stride])];
let base = func.append_param(body, gpr);
let index = func.append_param(body, gpr);
let addr = func.new_vreg(gpr);
let loaded = func.new_vreg(gpr);
let quotient = func.new_vreg(gpr);
let remainder = func.new_vreg(gpr);
let opcode = op(&mut names, "x64.lea");
func.build(body, opcode)
.def(addr, gpr)
.mem(Mem::at(Operand::read(base, gpr)).indexed(Operand::read(index, gpr), 4).plus(16))
.finish();
let counter = names.intern("counter");
let opcode = op(&mut names, "x64.mov_rm");
func.build(body, opcode).def(loaded, gpr).mem(Mem::of(counter).plus(8)).finish();
let opcode = op(&mut names, "x64.mov_mi");
func.build(body, opcode).mem(Mem::at(Operand::read(addr, gpr)).plus(-4)).imm(1).finish();
let opcode = op(&mut names, "x64.idiv_rr");
func.build(body, opcode)
.operand(Operand::write(quotient, gpr).with(Constraint::Fixed(rax)))
.operand(Operand::write_early(remainder, gpr).with(Constraint::Fixed(rdx)))
.operand(Operand::read(loaded, gpr).with(Constraint::Fixed(rax)))
.operand(Operand::read(addr, gpr).with(Constraint::Any))
.finish();
let opcode = op(&mut names, "x64.cmp_rr");
func.build(body, opcode)
.uses(quotient, gpr)
.operand(Operand::read(remainder, gpr).with(Constraint::Stack))
.finish();
let opcode = op(&mut names, "x64.jmp");
func.build(body, opcode).finish();
*func.succs_mut(body) = vec![BlockCall::with(exit, vec![quotient])];
let result = func.append_param(exit, gpr);
let moved = func.new_vreg(xmm);
let opcode = op(&mut names, "x64.movd_xr");
func.build(exit, opcode).def(moved, xmm).uses(result, gpr).finish();
let opcode = op(&mut names, "x64.ret");
func.build(exit, opcode).uses(Reg::physical(rax), gpr).finish();
(names, func)
}
fn named(name: &str) -> PhysReg {
REGS.reg_named(name).expect("the fixture file has that register").1
}
#[test]
fn a_function_prints_as_the_fixture_says() {
let (names, func) = scale();
assert_eq!(print_func(&func, &names, ®S), BEFORE);
}
#[test]
fn two_functions_are_printed_with_a_blank_line_between_them() {
let (names, func) = scale();
let empty = Func::new(func.name);
let text = print(&[empty, func], &names, ®S);
assert_eq!(text, format!("mfunc @scale {{\n}}\n\n{BEFORE}"));
}
#[test]
fn a_register_nothing_writes_prints_as_one_nothing_writes() {
let mut names = Interner::new();
let gpr = REGS.class_named("gpr").expect("the fixture file has a gpr class");
let mut func = Func::new(names.intern("f"));
let block = func.create_block();
let missing = func.new_vreg(gpr);
let opcode = Opcode::new(names.intern("x64.ret"));
func.build(block, opcode).uses(missing, gpr).finish();
assert_eq!(print_func(&func, &names, ®S), "mfunc @f {\nblock0:\n x64.ret %?\n}\n");
}
}