use crate::Error;
use crate::generator::Generator;
use crate::register::{RegisterFile, RegisterId};
use fixed_capacity_vec::FixedCapacityVec;
use rand_core::RngCore;
use std::fmt;
use std::ops::BitXor;
pub(crate) const NUM_INSTRUCTIONS: usize = 512;
pub(crate) type InstructionArray = [Instruction; NUM_INSTRUCTIONS];
pub(crate) type InstructionVec = FixedCapacityVec<Instruction, NUM_INSTRUCTIONS>;
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) enum Instruction {
Mul {
dst: RegisterId,
src: RegisterId,
},
UMulH {
dst: RegisterId,
src: RegisterId,
},
SMulH {
dst: RegisterId,
src: RegisterId,
},
AddShift {
dst: RegisterId,
src: RegisterId,
left_shift: u8,
},
AddConst {
dst: RegisterId,
src: i32,
},
Sub {
dst: RegisterId,
src: RegisterId,
},
Xor {
dst: RegisterId,
src: RegisterId,
},
XorConst {
dst: RegisterId,
src: i32,
},
Rotate {
dst: RegisterId,
right_rotate: u8,
},
Target,
Branch {
mask: u32,
},
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum Opcode {
Mul,
UMulH,
SMulH,
AddShift,
AddConst,
Sub,
Xor,
XorConst,
Rotate,
Target,
Branch,
}
impl Instruction {
#[inline(always)]
pub(crate) fn opcode(&self) -> Opcode {
match self {
Instruction::AddConst { .. } => Opcode::AddConst,
Instruction::AddShift { .. } => Opcode::AddShift,
Instruction::Branch { .. } => Opcode::Branch,
Instruction::Mul { .. } => Opcode::Mul,
Instruction::Rotate { .. } => Opcode::Rotate,
Instruction::SMulH { .. } => Opcode::SMulH,
Instruction::Sub { .. } => Opcode::Sub,
Instruction::Target => Opcode::Target,
Instruction::UMulH { .. } => Opcode::UMulH,
Instruction::Xor { .. } => Opcode::Xor,
Instruction::XorConst { .. } => Opcode::XorConst,
}
}
#[inline(always)]
pub(crate) fn destination(&self) -> Option<RegisterId> {
match self {
Instruction::AddConst { dst, .. } => Some(*dst),
Instruction::AddShift { dst, .. } => Some(*dst),
Instruction::Branch { .. } => None,
Instruction::Mul { dst, .. } => Some(*dst),
Instruction::Rotate { dst, .. } => Some(*dst),
Instruction::SMulH { dst, .. } => Some(*dst),
Instruction::Sub { dst, .. } => Some(*dst),
Instruction::Target => None,
Instruction::UMulH { dst, .. } => Some(*dst),
Instruction::Xor { dst, .. } => Some(*dst),
Instruction::XorConst { dst, .. } => Some(*dst),
}
}
}
#[derive(Clone)]
pub struct Program(Box<InstructionArray>);
impl fmt::Debug for Program {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Program {{")?;
for (addr, inst) in self.0.iter().enumerate() {
writeln!(f, " [{:3}]: {:?}", addr, inst)?;
}
write!(f, "}}")
}
}
impl Program {
pub(crate) fn generate<T: RngCore>(rng: &mut T) -> Result<Self, Error> {
let mut instructions = FixedCapacityVec::new();
Generator::new(rng).generate_program(&mut instructions)?;
Ok(Program(
instructions
.try_into()
.map_err(|_| ())
.expect("wrong length!"),
))
}
pub(crate) fn interpret(&self, regs: &mut RegisterFile) {
let mut program_counter = 0;
let mut allow_branch = true;
let mut branch_target = None;
let mut mulh_result: u32 = 0;
macro_rules! binary_reg_op {
($dst:ident, $src:ident, $fn:ident, $pc:ident) => {{
let a = regs.load(*$dst);
let b = regs.load(*$src);
regs.store(*$dst, a.$fn(b));
$pc
}};
}
macro_rules! binary_const_op {
($dst:ident, $src:ident, $fn:ident, $pc:ident) => {{
let a = regs.load(*$dst);
let b_sign_extended = i64::from(*$src) as u64;
regs.store(*$dst, a.$fn(b_sign_extended));
$pc
}};
}
macro_rules! mulh_op {
($dst:ident, $src:ident, $sign:ty, $wide:ty, $pc:ident) => {{
let a = <$wide>::from(regs.load(*$dst) as $sign);
let b = <$wide>::from(regs.load(*$src) as $sign);
let r = (a.wrapping_mul(b) >> 64) as u64;
mulh_result = r as u32;
regs.store(*$dst, r);
$pc
}};
}
while program_counter < self.0.len() {
let next_pc = program_counter + 1;
program_counter = match &self.0[program_counter] {
Instruction::Target => {
branch_target = Some(program_counter);
next_pc
}
Instruction::Branch { mask } => {
if allow_branch && (mask & mulh_result) == 0 {
allow_branch = false;
branch_target
.expect("generated programs always have a target before branch")
} else {
next_pc
}
}
Instruction::AddShift {
dst,
src,
left_shift,
} => {
let a = regs.load(*dst);
let b = regs.load(*src);
let r = a.wrapping_add(b.wrapping_shl((*left_shift).into()));
regs.store(*dst, r);
next_pc
}
Instruction::Rotate { dst, right_rotate } => {
let a = regs.load(*dst);
let r = a.rotate_right((*right_rotate).into());
regs.store(*dst, r);
next_pc
}
Instruction::Mul { dst, src } => binary_reg_op!(dst, src, wrapping_mul, next_pc),
Instruction::Sub { dst, src } => binary_reg_op!(dst, src, wrapping_sub, next_pc),
Instruction::Xor { dst, src } => binary_reg_op!(dst, src, bitxor, next_pc),
Instruction::UMulH { dst, src } => mulh_op!(dst, src, u64, u128, next_pc),
Instruction::SMulH { dst, src } => mulh_op!(dst, src, i64, i128, next_pc),
Instruction::XorConst { dst, src } => binary_const_op!(dst, src, bitxor, next_pc),
Instruction::AddConst { dst, src } => {
binary_const_op!(dst, src, wrapping_add, next_pc)
}
}
}
}
}
impl<'a> From<&'a Program> for &'a InstructionArray {
#[inline(always)]
fn from(prog: &'a Program) -> Self {
&prog.0
}
}