use crate::ir::op::{Cond, MemOp, Opcode};
use crate::ir::types::{Const, Temp, Type};
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct RegSlot(pub u16);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InsnStart {
pub pc: u64,
pub next_pc: u64,
pub ticks: u64,
pub live: Vec<(RegSlot, Temp)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Inst {
pub op: Opcode,
pub ty: Type,
pub dst: Option<Temp>,
pub dst2: Option<Temp>,
pub imm: Option<Const>,
pub mem: Option<MemOp>,
pub cond: Option<Cond>,
pub aux: u32,
src_start: u32,
src_len: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Block {
pub entry_pc: u64,
pub key: u64,
insts: Vec<Inst>,
operands: Vec<Temp>,
marks: Vec<InsnStart>,
types: Vec<Type>,
}
impl Block {
#[inline]
#[must_use]
pub fn insts(&self) -> &[Inst] {
&self.insts
}
#[must_use]
pub fn srcs(&self, index: usize) -> &[Temp] {
let inst = &self.insts[index];
let start = inst.src_start as usize;
&self.operands[start..start + inst.src_len as usize]
}
#[inline]
#[must_use]
pub fn marks(&self) -> &[InsnStart] {
&self.marks
}
#[inline]
#[must_use]
pub fn type_of(&self, temp: Temp) -> Option<Type> {
self.types.get(temp.index()).copied()
}
#[inline]
#[must_use]
pub fn temp_count(&self) -> usize {
self.types.len()
}
pub(crate) fn retain(&self, keep: &[bool]) -> Block {
let alive = |i: usize| keep.get(i).copied().unwrap_or(true);
let mut moved = Vec::with_capacity(self.insts.len() + 1);
let mut kept = 0u32;
for i in 0..=self.insts.len() {
moved.push(kept);
if i < self.insts.len() && alive(i) {
kept += 1;
}
}
let mut out = Block {
entry_pc: self.entry_pc,
key: self.key,
insts: Vec::with_capacity(self.insts.len()),
operands: Vec::with_capacity(self.operands.len()),
marks: self.marks.clone(),
types: self.types.clone(),
};
for (i, inst) in self.insts.iter().enumerate() {
if !alive(i) {
continue;
}
let src_start = out.operands.len() as u32;
out.operands.extend_from_slice(self.srcs(i));
let aux = if inst.op == Opcode::BRCOND {
moved.get(inst.aux as usize).copied().unwrap_or(kept)
} else {
inst.aux
};
out.insts.push(Inst {
src_start,
aux,
..inst.clone()
});
}
out
}
}
impl fmt::Display for Block {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "block {:#x} key {:#x}", self.entry_pc, self.key)?;
for (i, inst) in self.insts.iter().enumerate() {
match inst.dst {
Some(d) => write!(f, " {d} = {}", inst.op)?,
None => write!(f, " {}", inst.op)?,
}
if let Some(d2) = inst.dst2 {
write!(f, " -> {d2}")?;
}
write!(f, ".{}", inst.ty)?;
for src in self.srcs(i) {
write!(f, " {src}")?;
}
if let Some(imm) = inst.imm {
write!(f, " {imm}")?;
}
if inst.op == Opcode::INSN_START
&& let Some(mark) = self.marks.get(inst.aux as usize)
{
write!(f, " pc={:#x} ticks={}", mark.pc, mark.ticks)?;
}
writeln!(f)?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct BlockBuilder {
block: Block,
}
impl BlockBuilder {
#[must_use]
pub fn new(entry_pc: u64, key: u64) -> BlockBuilder {
BlockBuilder {
block: Block {
entry_pc,
key,
insts: Vec::new(),
operands: Vec::new(),
marks: Vec::new(),
types: Vec::new(),
},
}
}
pub fn temp(&mut self, ty: Type) -> Temp {
let t = Temp(self.block.types.len() as u32);
self.block.types.push(ty);
t
}
pub fn emit(&mut self, op: Opcode, ty: Type, srcs: &[Temp]) -> Temp {
let dst = self.temp(ty);
self.push(op, ty, Some(dst), None, srcs, None, None, 0);
dst
}
pub fn emit_void(&mut self, op: Opcode, ty: Type, srcs: &[Temp]) {
self.push(op, ty, None, None, srcs, None, None, 0);
}
pub fn imm(&mut self, ty: Type, value: Const) -> Temp {
let dst = self.temp(ty);
self.push(Opcode::MOV, ty, Some(dst), None, &[], Some(value), None, 0);
dst
}
pub fn binary(&mut self, op: Opcode, ty: Type, a: Temp, b: Temp) -> Temp {
self.emit(op, ty, &[a, b])
}
pub fn unary(&mut self, op: Opcode, ty: Type, a: Temp) -> Temp {
self.emit(op, ty, &[a])
}
pub fn addc(&mut self, op: Opcode, ty: Type, a: Temp, b: Temp, carry: Temp) -> (Temp, Temp) {
let dst = self.temp(ty);
let carry_out = self.temp(Type::I1);
self.push(
op,
ty,
Some(dst),
Some(carry_out),
&[a, b, carry],
None,
None,
0,
);
(dst, carry_out)
}
pub fn setcond(&mut self, cond: Cond, ty: Type, a: Temp, b: Temp) -> Temp {
let dst = self.temp(Type::I1);
self.push(
Opcode::SETCOND,
ty,
Some(dst),
None,
&[a, b],
None,
Some(cond),
0,
);
dst
}
pub fn load(&mut self, ty: Type, addr: Temp, mem: MemOp) -> Temp {
let dst = self.temp(ty);
let inst = self.push(Opcode::LD, ty, Some(dst), None, &[addr], None, None, 0);
self.block.insts[inst].mem = Some(mem);
dst
}
pub fn store(&mut self, ty: Type, addr: Temp, value: Temp, mem: MemOp) {
let inst = self.push(Opcode::ST, ty, None, None, &[addr, value], None, None, 0);
self.block.insts[inst].mem = Some(mem);
}
pub fn get_slot(&mut self, ty: Type, slot: RegSlot) -> Temp {
let dst = self.temp(ty);
self.push(
Opcode::GET_SLOT,
ty,
Some(dst),
None,
&[],
None,
None,
u32::from(slot.0),
);
dst
}
pub fn charge(&mut self, ticks: u64) {
self.push(
Opcode::CHARGE,
Type::I64,
None,
None,
&[],
Some(Const::Int(ticks as u128)),
None,
0,
);
}
pub fn insn_start(&mut self, mark: InsnStart) {
let index = self.block.marks.len() as u32;
self.block.marks.push(mark);
self.push(
Opcode::INSN_START,
Type::I64,
None,
None,
&[],
None,
None,
index,
);
}
pub fn exit_tb(&mut self) {
self.push(Opcode::EXIT_TB, Type::I64, None, None, &[], None, None, 0);
}
#[allow(clippy::too_many_arguments)]
pub fn emit_raw(
&mut self,
op: Opcode,
ty: Type,
dst: Option<Temp>,
dst2: Option<Temp>,
srcs: &[Temp],
imm: Option<Const>,
cond: Option<Cond>,
aux: u32,
) -> usize {
self.push(op, ty, dst, dst2, srcs, imm, cond, aux)
}
pub fn patch_aux(&mut self, inst: usize, aux: u32) {
self.block.insts[inst].aux = aux;
}
#[must_use]
pub fn next_index(&self) -> usize {
self.block.insts.len()
}
#[must_use]
pub fn finish(self) -> Block {
self.block
}
#[allow(clippy::too_many_arguments)]
fn push(
&mut self,
op: Opcode,
ty: Type,
dst: Option<Temp>,
dst2: Option<Temp>,
srcs: &[Temp],
imm: Option<Const>,
cond: Option<Cond>,
aux: u32,
) -> usize {
let src_start = self.block.operands.len() as u32;
self.block.operands.extend_from_slice(srcs);
self.block.insts.push(Inst {
op,
ty,
dst,
dst2,
imm,
mem: None,
cond,
aux,
src_start,
src_len: srcs.len() as u32,
});
self.block.insts.len() - 1
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::value::Width;
use crate::ir::op::MemOp;
use alloc::vec;
fn mark(pc: u64, ticks: u64) -> InsnStart {
InsnStart {
pc,
next_pc: pc + 4,
ticks,
live: Vec::new(),
}
}
#[test]
fn a_block_keeps_its_operands_flat_and_reachable() {
let mut b = BlockBuilder::new(0x8000_0000, 0);
b.insn_start(mark(0x8000_0000, 0));
let a = b.imm(Type::I64, Const::Int(1));
let c = b.imm(Type::I64, Const::Int(2));
let sum = b.binary(Opcode::ADD, Type::I64, a, c);
b.exit_tb();
let block = b.finish();
assert_eq!(block.temp_count(), 3);
assert_eq!(block.type_of(sum), Some(Type::I64));
let add = block
.insts()
.iter()
.position(|i| i.op == Opcode::ADD)
.expect("the add is in the block");
assert_eq!(block.srcs(add), &[a, c]);
assert!(block.srcs(1).is_empty());
}
#[test]
fn carry_producing_ops_get_a_second_result_of_one_bit() {
let mut b = BlockBuilder::new(0, 0);
let x = b.imm(Type::I32, Const::Int(0xffff_ffff));
let y = b.imm(Type::I32, Const::Int(1));
let carry_in = b.imm(Type::I1, Const::Int(0));
let (sum, carry_out) = b.addc(Opcode::ADDC, Type::I32, x, y, carry_in);
let block = b.finish();
assert_eq!(block.type_of(sum), Some(Type::I32));
assert_eq!(block.type_of(carry_out), Some(Type::I1));
}
#[test]
fn a_boundary_marker_points_at_its_record() {
let mut b = BlockBuilder::new(0x100, 0);
b.insn_start(mark(0x100, 0));
b.charge(1);
b.insn_start(mark(0x104, 1));
b.charge(1);
let block = b.finish();
assert_eq!(block.marks().len(), 2);
assert_eq!(block.marks()[1].pc, 0x104);
assert_eq!(block.marks()[1].ticks, 1);
let starts: Vec<u32> = block
.insts()
.iter()
.filter(|i| i.op == Opcode::INSN_START)
.map(|i| i.aux)
.collect();
assert_eq!(starts, vec![0, 1]);
}
#[test]
fn a_load_carries_its_descriptor() {
let mut b = BlockBuilder::new(0, 0);
let addr = b.imm(Type::I64, Const::Int(0x2002));
let mut mem = MemOp::load(Width::U8);
mem.volatile = true;
let value = b.load(Type::I32, addr, mem);
let block = b.finish();
let ld = block
.insts()
.iter()
.find(|i| i.op == Opcode::LD)
.expect("the load is in the block");
assert_eq!(ld.dst, Some(value));
assert!(ld.mem.expect("a load has a descriptor").volatile);
}
#[test]
fn a_block_dumps_itself_for_a_differential_report() {
let mut b = BlockBuilder::new(0x80, 0x1);
b.insn_start(mark(0x80, 0));
let a = b.imm(Type::I32, Const::Int(5));
let _ = b.unary(Opcode::NEG, Type::I32, a);
let text = alloc::format!("{}", b.finish());
assert!(text.starts_with("block 0x80 key 0x1\n"), "{text}");
assert!(text.contains("insn_start"), "{text}");
assert!(text.contains("t1 = neg.i32 t0"), "{text}");
}
}