use llvm_ir::{Context, Function, Module};
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct VReg(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PReg(pub u8);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct MOpcode(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DebugLoc {
pub line: u32,
pub column: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MOperand {
VReg(VReg),
PReg(PReg),
Imm(i64),
Block(usize),
}
#[derive(Clone, Debug)]
pub struct MInstr {
pub opcode: MOpcode,
pub dst: Option<VReg>,
pub operands: Vec<MOperand>,
pub phys_uses: Vec<PReg>,
pub clobbers: Vec<PReg>,
pub debug_loc: Option<DebugLoc>,
}
impl MInstr {
pub fn new(opcode: MOpcode) -> Self {
Self {
opcode,
dst: None,
operands: Vec::new(),
phys_uses: Vec::new(),
clobbers: Vec::new(),
debug_loc: None,
}
}
pub fn with_dst(mut self, dst: VReg) -> Self {
self.dst = Some(dst);
self
}
pub fn with_vreg(mut self, r: VReg) -> Self {
self.operands.push(MOperand::VReg(r));
self
}
pub fn with_preg(mut self, r: PReg) -> Self {
self.operands.push(MOperand::PReg(r));
self
}
pub fn with_imm(mut self, imm: i64) -> Self {
self.operands.push(MOperand::Imm(imm));
self
}
pub fn with_block(mut self, b: usize) -> Self {
self.operands.push(MOperand::Block(b));
self
}
}
#[derive(Clone, Debug, Default)]
pub struct MachineBlock {
pub label: String,
pub instrs: Vec<MInstr>,
}
#[derive(Clone, Debug)]
pub struct MachineFunction {
pub name: String,
pub blocks: Vec<MachineBlock>,
pub(crate) next_vreg: u32,
pub allocatable_pregs: Vec<PReg>,
pub callee_saved_pregs: Vec<PReg>,
pub frame_size: u32,
pub spill_slots: HashMap<VReg, u32>,
pub used_callee_saved: Vec<PReg>,
next_slot: u32,
pub debug_source: Option<String>,
pub debug_line_start: Option<u32>,
pub current_debug_loc: Option<DebugLoc>,
}
impl MachineFunction {
pub fn new(name: String) -> Self {
Self {
name,
blocks: Vec::new(),
next_vreg: 0,
allocatable_pregs: Vec::new(),
callee_saved_pregs: Vec::new(),
frame_size: 0,
spill_slots: HashMap::new(),
used_callee_saved: Vec::new(),
next_slot: 0,
debug_source: None,
debug_line_start: None,
current_debug_loc: None,
}
}
pub fn fresh_vreg(&mut self) -> VReg {
let id = self.next_vreg;
self.next_vreg += 1;
VReg(id)
}
pub fn alloc_spill_slot(&mut self, vreg: VReg) -> u32 {
if let Some(&existing) = self.spill_slots.get(&vreg) {
return existing;
}
let slot = self.next_slot;
self.next_slot += 1;
self.spill_slots.insert(vreg, slot);
self.frame_size = self.next_slot * 8;
slot
}
pub fn add_block(&mut self, label: impl Into<String>) -> usize {
let idx = self.blocks.len();
self.blocks.push(MachineBlock {
label: label.into(),
instrs: Vec::new(),
});
idx
}
pub fn push(&mut self, block_idx: usize, mut instr: MInstr) {
if instr.debug_loc.is_none() {
instr.debug_loc = self.current_debug_loc;
}
self.blocks[block_idx].instrs.push(instr);
}
}
pub trait IselBackend {
fn lower_function(
&mut self,
ctx: &Context,
module: &Module,
func: &Function,
) -> MachineFunction;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn machine_function_fresh_vreg() {
let mut mf = MachineFunction::new("f".into());
let v0 = mf.fresh_vreg();
let v1 = mf.fresh_vreg();
assert_eq!(v0, VReg(0));
assert_eq!(v1, VReg(1));
}
#[test]
fn machine_function_add_block() {
let mut mf = MachineFunction::new("f".into());
let b0 = mf.add_block("entry");
let b1 = mf.add_block("exit");
assert_eq!(b0, 0);
assert_eq!(b1, 1);
assert_eq!(mf.blocks[0].label, "entry");
}
#[test]
fn minstr_builder() {
let v = VReg(0);
let p = PReg(1);
let mi = MInstr::new(MOpcode(42))
.with_dst(v)
.with_vreg(v)
.with_preg(p)
.with_imm(-7)
.with_block(3);
assert_eq!(mi.dst, Some(v));
assert_eq!(mi.operands.len(), 4);
assert_eq!(mi.operands[0], MOperand::VReg(v));
assert_eq!(mi.operands[1], MOperand::PReg(p));
assert_eq!(mi.operands[2], MOperand::Imm(-7));
assert_eq!(mi.operands[3], MOperand::Block(3));
}
}