//! HoleyBytes Virtual Machine //! //! All unsafe code here should be sound, if input bytecode passes validation. // # General safety notice: // - Validation has to assure there is 60 registers (r0 - r59) // - Instructions have to be valid as specified (values and sizes) // - Mapped pages should be at least 8 KiB // - Yes, I am aware of the UB when jumping in-mid of instruction where // the read byte corresponds to an instruction whose lenght exceets the // program size. If you are (rightfully) worried about the UB, for now just // append your program with 11 zeroes. mod mem; mod value; use { crate::validate, core::ops, hbbytecode::{OpParam, ParamRI, ParamRR, ParamRRI, ParamRRR}, mem::{ma_size, Memory}, static_assertions::assert_impl_one, value::Value, }; macro_rules! param { ($self:expr, $ty:ty) => {{ assert_impl_one!($ty: OpParam); let data = $self .program .as_ptr() .add($self.pc + 1) .cast::<$ty>() .read(); $self.pc += 1 + core::mem::size_of::<$ty>(); data }}; } macro_rules! binary_op { ($self:expr, $ty:ident, $handler:expr) => {{ let ParamRRR(tg, a0, a1) = param!($self, ParamRRR); $self.write_reg( tg, $handler( Value::$ty(&$self.read_reg(a0)), Value::$ty(&$self.read_reg(a1)), ) .into(), ); }}; } macro_rules! binary_op_imm { ($self:expr, $ty:ident, $handler:expr) => {{ let ParamRRI(tg, a0, imm) = param!($self, ParamRRI); $self.write_reg( tg, $handler(Value::$ty(&$self.read_reg(a0)), Value::$ty(&imm.into())).into(), ); }}; } macro_rules! load { ($self:expr, $size:ty) => {{ let ParamRRI(tg, a0, offset) = param!($self, ParamRRI); $self.write_reg( tg, match $self .memory .load::<$size>($self.read_reg(a0).int() + offset) { Some(x) => x, None => return HaltReason::LoadAccessEx, }, ); }}; } macro_rules! store { ($self:expr, $size:ty) => {{ let ParamRRI(src, a0, offset) = param!($self, ParamRRI); if let Err(()) = $self .memory .store::<$size>($self.read_reg(a0).int() + offset, $self.read_reg(src)) { return HaltReason::StoreAccessEx; } }}; } macro_rules! cond_jump { ($self:expr, $ty:ident, $expected:ident) => {{ let ParamRRI(a0, a1, jt) = param!($self, ParamRRI); if core::cmp::Ord::cmp(&$self.read_reg(a0), &$self.read_reg(a1)) == core::cmp::Ordering::$expected { $self.pc = jt as usize; } }}; } pub struct Vm<'a> { pub registers: [Value; 60], pub memory: Memory, pc: usize, program: &'a [u8], } impl<'a> Vm<'a> { /// # Safety /// Program code has to be validated pub unsafe fn new_unchecked(program: &'a [u8]) -> Self { Self { registers: [Value::from(0_u64); 60], memory: Default::default(), pc: 0, program, } } pub fn new_validated(program: &'a [u8]) -> Result { validate::validate(program)?; Ok(unsafe { Self::new_unchecked(program) }) } pub fn run(&mut self) -> HaltReason { use hbbytecode::opcode::*; loop { let Some(&opcode) = self.program.get(self.pc) else { return HaltReason::ProgramEnd }; unsafe { match opcode { NOP => param!(self, ()), ADD => binary_op!(self, int, u64::wrapping_add), SUB => binary_op!(self, int, u64::wrapping_sub), MUL => binary_op!(self, int, u64::wrapping_mul), DIV => binary_op!(self, int, u64::wrapping_div), REM => binary_op!(self, int, u64::wrapping_rem), AND => binary_op!(self, int, ops::BitAnd::bitand), OR => binary_op!(self, int, ops::BitOr::bitor), XOR => binary_op!(self, int, ops::BitXor::bitxor), SL => binary_op!(self, int, ops::Shl::shl), SR => binary_op!(self, int, ops::Shr::shr), SRS => binary_op!(self, sint, ops::Shr::shr), CMP => { let ParamRRR(tg, a0, a1) = param!(self, ParamRRR); self.write_reg( tg, (self.read_reg(a0).sint().cmp(&self.read_reg(a1).sint()) as i64).into(), ); } CMPU => { let ParamRRR(tg, a0, a1) = param!(self, ParamRRR); self.write_reg( tg, (self.read_reg(a0).int().cmp(&self.read_reg(a1).int()) as i64).into(), ); } NOT => { let param = param!(self, ParamRR); self.write_reg(param.0, (!self.read_reg(param.1).int()).into()); } ADDF => binary_op!(self, float, ops::Add::add), SUBF => binary_op!(self, float, ops::Sub::sub), MULF => binary_op!(self, float, ops::Mul::mul), DIVF => binary_op!(self, float, ops::Div::div), ADDI => binary_op_imm!(self, int, ops::Add::add), MULI => binary_op_imm!(self, int, ops::Mul::mul), REMI => binary_op_imm!(self, int, ops::Rem::rem), ANDI => binary_op_imm!(self, int, ops::BitAnd::bitand), ORI => binary_op_imm!(self, int, ops::BitOr::bitor), XORI => binary_op_imm!(self, int, ops::BitXor::bitxor), SLI => binary_op_imm!(self, int, ops::Shl::shl), SRI => binary_op_imm!(self, int, ops::Shr::shr), SRSI => binary_op_imm!(self, sint, ops::Shr::shr), ADDFI => binary_op_imm!(self, float, ops::Add::add), MULFI => binary_op_imm!(self, float, ops::Mul::mul), CMPI => { let ParamRRI(tg, a0, imm) = param!(self, ParamRRI); self.write_reg( tg, (self.read_reg(a0).sint().cmp(&Value::from(imm).sint()) as i64).into(), ); } CMPUI => { let ParamRRI(tg, a0, imm) = param!(self, ParamRRI); self.write_reg(tg, (self.read_reg(a0).int().cmp(&imm) as i64).into()); } CP => { let param = param!(self, ParamRR); self.write_reg(param.0, self.read_reg(param.1)); } LI => { let param = param!(self, ParamRI); self.write_reg(param.0, param.1.into()); } LB => load!(self, ma_size::Byte), LD => load!(self, ma_size::Doublet), LQ => load!(self, ma_size::Quadlet), LO => load!(self, ma_size::Octlet), SB => store!(self, ma_size::Byte), SD => store!(self, ma_size::Doublet), SQ => store!(self, ma_size::Quadlet), SO => store!(self, ma_size::Octlet), JMP => { let ParamRI(reg, offset) = param!(self, ParamRI); self.pc = (self.read_reg(reg).int() + offset) as usize; } JEQ => cond_jump!(self, int, Equal), JNE => { let ParamRRI(a0, a1, jt) = param!(self, ParamRRI); if self.read_reg(a0) != self.read_reg(a1) { self.pc = jt as usize; } } JLT => cond_jump!(self, int, Less), JGT => cond_jump!(self, int, Greater), JLTU => cond_jump!(self, sint, Less), JGTU => cond_jump!(self, sint, Greater), ECALL => { param!(self, ()); return HaltReason::Ecall; } _ => core::hint::unreachable_unchecked(), } } } } #[inline] unsafe fn read_reg(&self, n: u8) -> Value { if n == 0 { 0_u64.into() } else { *self.registers.get_unchecked(n as usize) } } #[inline] unsafe fn write_reg(&mut self, n: u8, value: Value) { if n != 0 { *self.registers.get_unchecked_mut(n as usize) = value; } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum HaltReason { ProgramEnd, Ecall, LoadAccessEx, StoreAccessEx, }