holey-bytes/hbvm/src/vm/mod.rs

257 lines
9.1 KiB
Rust
Raw Normal View History

2023-06-09 14:58:59 -05:00
//! 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)
2023-06-10 09:46:04 -05:00
// - Instructions have to be valid as specified (values and sizes)
2023-06-09 14:58:59 -05:00
// - Mapped pages should be at least 8 KiB
2023-06-10 09:46:04 -05:00
// - 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.
2023-06-09 14:58:59 -05:00
mod mem;
mod value;
use {
2023-06-09 14:58:59 -05:00
crate::validate,
core::ops,
2023-06-07 17:25:38 -05:00
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);
2023-06-09 11:25:37 -05:00
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);
2023-06-08 16:23:23 -05:00
$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(),
);
}};
}
2023-06-07 17:25:38 -05:00
macro_rules! load {
($self:expr, $size:ty) => {{
let ParamRRI(tg, a0, offset) = param!($self, ParamRRI);
2023-06-07 17:52:24 -05:00
$self.write_reg(
tg,
match $self
2023-06-08 16:23:23 -05:00
.memory
2023-06-07 17:52:24 -05:00
.load::<$size>($self.read_reg(a0).int() + offset)
{
Some(x) => x,
None => return HaltReason::LoadAccessEx,
},
2023-06-07 17:52:24 -05:00
);
2023-06-07 17:25:38 -05:00
}};
}
macro_rules! store {
($self:expr, $size:ty) => {{
let ParamRRI(src, a0, offset) = param!($self, ParamRRI);
if let Err(()) = $self
2023-06-07 17:25:38 -05:00
.memory
2023-06-07 17:52:24 -05:00
.store::<$size>($self.read_reg(a0).int() + offset, $self.read_reg(src))
{
return HaltReason::StoreAccessEx;
}
2023-06-07 17:25:38 -05:00
}};
}
2023-06-08 18:55:09 -05:00
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],
2023-06-07 17:25:38 -05:00
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],
2023-06-07 17:25:38 -05:00
memory: Default::default(),
pc: 0,
program,
}
}
2023-06-06 16:08:26 -05:00
pub fn new_validated(program: &'a [u8]) -> Result<Self, validate::Error> {
validate::validate(program)?;
Ok(unsafe { Self::new_unchecked(program) })
}
pub fn run(&mut self) -> HaltReason {
2023-06-07 08:07:37 -05:00
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),
2023-06-09 11:25:37 -05:00
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);
2023-06-07 17:52:24 -05:00
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),
2023-06-09 11:25:37 -05:00
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());
}
2023-06-06 17:02:27 -05:00
CP => {
let param = param!(self, ParamRR);
2023-06-07 17:52:24 -05:00
self.write_reg(param.0, self.read_reg(param.1));
2023-06-06 17:02:27 -05:00
}
LI => {
let param = param!(self, ParamRI);
2023-06-07 17:52:24 -05:00
self.write_reg(param.0, param.1.into());
}
2023-06-07 17:25:38 -05:00
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 => {
2023-06-07 17:52:24 -05:00
let ParamRI(reg, offset) = param!(self, ParamRI);
self.pc = (self.read_reg(reg).int() + offset) as usize;
}
2023-06-08 18:55:09 -05:00
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]
2023-06-07 17:52:24 -05:00
unsafe fn read_reg(&self, n: u8) -> Value {
if n == 0 {
0_u64.into()
2023-06-07 17:52:24 -05:00
} else {
*self.registers.get_unchecked(n as usize)
}
}
#[inline]
2023-06-07 17:52:24 -05:00
unsafe fn write_reg(&mut self, n: u8, value: Value) {
if n != 0 {
*self.registers.get_unchecked_mut(n as usize) = value;
}
}
}
2023-06-07 17:25:38 -05:00
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum HaltReason {
ProgramEnd,
Ecall,
LoadAccessEx,
StoreAccessEx,
2023-06-07 17:25:38 -05:00
}