use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use ir_lang::{BinOp, UnOp};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Reg(pub u32);
impl fmt::Display for Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "r{}", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Label(pub u32);
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "L{}", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Const {
Int(i64),
Float(f64),
Bool(bool),
}
impl fmt::Display for Const {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Const::Int(value) => write!(f, "{value}"),
Const::Float(value) => write!(f, "{value}"),
Const::Bool(value) => write!(f, "{value}"),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Op {
Const {
dst: Reg,
value: Const,
},
Bin {
op: BinOp,
dst: Reg,
lhs: Reg,
rhs: Reg,
},
Un {
op: UnOp,
dst: Reg,
src: Reg,
},
Move {
dst: Reg,
src: Reg,
},
Jump {
target: Label,
},
JumpUnless {
cond: Reg,
target: Label,
},
Return {
value: Option<Reg>,
},
}
impl fmt::Display for Op {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Op::Const { dst, value } => write!(f, "{dst} = const {value}"),
Op::Bin { op, dst, lhs, rhs } => write!(f, "{dst} = {op} {lhs}, {rhs}"),
Op::Un { op, dst, src } => write!(f, "{dst} = {op} {src}"),
Op::Move { dst, src } => write!(f, "{dst} = {src}"),
Op::Jump { target } => write!(f, "jump {target}"),
Op::JumpUnless { cond, target } => write!(f, "jump_unless {cond}, {target}"),
Op::Return { value: Some(reg) } => write!(f, "ret {reg}"),
Op::Return { value: None } => write!(f, "ret"),
}
}
}
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Program {
pub(crate) name: String,
pub(crate) params: Vec<Reg>,
pub(crate) registers: u32,
pub(crate) ops: Vec<Op>,
pub(crate) labels: Vec<u32>,
}
impl Program {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn params(&self) -> &[Reg] {
&self.params
}
#[must_use]
pub const fn register_count(&self) -> u32 {
self.registers
}
#[must_use]
pub fn ops(&self) -> &[Op] {
&self.ops
}
#[must_use]
pub fn len(&self) -> usize {
self.ops.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.ops.is_empty()
}
#[must_use]
pub fn label_offset(&self, label: Label) -> Option<usize> {
self.labels
.get(label.0 as usize)
.map(|&offset| offset as usize)
}
#[must_use]
pub const fn entry(&self) -> Label {
Label(0)
}
}
impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", self.name)?;
for (i, param) in self.params.iter().enumerate() {
if i != 0 {
f.write_str(", ")?;
}
write!(f, "{param}")?;
}
writeln!(f, ") regs={}", self.registers)?;
for (index, op) in self.ops.iter().enumerate() {
for (id, &offset) in self.labels.iter().enumerate() {
if offset as usize == index {
writeln!(f, "{}:", Label(id as u32))?;
}
}
writeln!(f, " {op}")?;
}
Ok(())
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
reason = "tests build known-valid functions, so compilation cannot fail"
)]
mod tests {
use super::{Const, Label, Op, Reg};
use crate::compile;
use ir_lang::{BinOp, Builder, Type};
#[test]
fn test_reg_and_label_display_use_short_prefixes() {
assert_eq!(Reg(0).to_string(), "r0");
assert_eq!(Reg(41).to_string(), "r41");
assert_eq!(Label(0).to_string(), "L0");
}
#[test]
fn test_const_display_matches_payload() {
assert_eq!(Const::Int(5).to_string(), "5");
assert_eq!(Const::Int(-5).to_string(), "-5");
assert_eq!(Const::Bool(false).to_string(), "false");
}
#[test]
fn test_op_display_renders_each_form() {
assert_eq!(
Op::Const {
dst: Reg(0),
value: Const::Int(3)
}
.to_string(),
"r0 = const 3"
);
assert_eq!(
Op::Bin {
op: BinOp::Add,
dst: Reg(2),
lhs: Reg(0),
rhs: Reg(1)
}
.to_string(),
"r2 = add r0, r1"
);
assert_eq!(
Op::Move {
dst: Reg(1),
src: Reg(0)
}
.to_string(),
"r1 = r0"
);
assert_eq!(Op::Jump { target: Label(1) }.to_string(), "jump L1");
assert_eq!(
Op::JumpUnless {
cond: Reg(0),
target: Label(2)
}
.to_string(),
"jump_unless r0, L2"
);
assert_eq!(Op::Return { value: None }.to_string(), "ret");
}
#[test]
fn test_program_disassembly_prints_header_label_and_ops() {
let mut b = Builder::new("double", &[Type::Int], Type::Int);
let x = b.block_params(b.entry())[0];
let sum = b.bin(BinOp::Add, x, x);
b.ret(Some(sum));
let text = compile(&b.finish()).unwrap().to_string();
assert!(text.starts_with("double(r0) regs=2"));
assert!(text.contains("L0:"));
assert!(text.contains("r1 = add r0, r0"));
assert!(text.contains("ret r1"));
assert_eq!(
text,
"double(r0) regs=2\nL0:\n r1 = add r0, r0\n ret r1\n"
);
}
#[test]
fn test_entry_label_resolves_to_first_op() {
let mut b = Builder::new("f", &[], Type::Unit);
b.ret(None);
let program = compile(&b.finish()).unwrap();
assert_eq!(program.entry(), Label(0));
assert_eq!(program.label_offset(program.entry()), Some(0));
assert_eq!(program.label_offset(Label(999)), None);
}
}