diff --git a/Cargo.lock b/Cargo.lock index 0ca3e825..4cd6d8ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,7 @@ version = "0.1.0" dependencies = [ "hashbrown", "log", + "static_assertions", ] [[package]] @@ -55,6 +56,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "version_check" version = "0.9.4" diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index bac4d36e..5c0f1dc0 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -3,6 +3,10 @@ name = "hbvm" version = "0.1.0" edition = "2021" +[profile.release] +lto = true + [dependencies] log = "*" hashbrown = "0.13.2" +static_assertions = "1.0" diff --git a/hbvm/src/bytecode.rs b/hbvm/src/bytecode.rs new file mode 100644 index 00000000..12129b36 --- /dev/null +++ b/hbvm/src/bytecode.rs @@ -0,0 +1,56 @@ +pub const REG_COUNT: usize = 60; + +macro_rules! constmod { + ($vis:vis $mname:ident($repr:ty) { $($cname:ident = $val:expr),* $(,)? }) => { + $vis mod $mname { + $(pub const $cname: $repr = $val;)* + } + }; +} + +constmod!(pub opcode(u8) { + NOP = 0, // _ + ADD = 1, // RRR + SUB = 2, // RRR + MUL = 3, // RRR + DIV = 4, // RRR + REM = 5, // RRR + AND = 6, // RRR + OR = 7, // RRR + XOR = 8, // RRR + NOT = 9, // RR + + // TODO: Add instruction for integer and float + // reg ← reg + imm instructions + + ADDF = 10, // RRR + SUBF = 11, // RRR + MULF = 12, // RRR + DIVF = 13, // RRR + + LI = 14, // RI + LD = 15, // RI + ST = 16, // RI + + MAPPAGE = 17, // ? + UNMAPPAGE = 18, // ? + + JMP = 100, // I + JMPCOND = 101, // I + RET = 103, // _ + ECALL = 255, // _ +}); + +#[repr(packed)] pub struct ParamRRR(pub u8, pub u8, pub u8); +#[repr(packed)] pub struct ParamRR(pub u8, pub u8); + +#[repr(packed)] pub struct ParamRI(pub u8, pub u64); + +/// # Safety +/// TODO. +pub unsafe trait OpParam {} +unsafe impl OpParam for ParamRRR {} +unsafe impl OpParam for ParamRR {} +unsafe impl OpParam for ParamRI {} +unsafe impl OpParam for u64 {} +unsafe impl OpParam for () {} diff --git a/hbvm/src/bytecode/mod.rs b/hbvm/src/bytecode/mod.rs deleted file mode 100644 index 73d06871..00000000 --- a/hbvm/src/bytecode/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -mod validate; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[repr(u8)] -pub enum Instruction { - Nop = 0, - Add(Register, Register, Register), - Sub(Register, Register, Register) = 2, - Mul(Register, Register, Register) = 3, - Div(Register, Register, Register) = 4, - Mod(Register, Register, Register) = 5, - - And(Register, Register, Register) = 6, - Or(Register, Register, Register) = 7, - Xor(Register, Register, Register) = 8, - Not(Register, Register) = 9, - - Li(Register, u64) = 10, - Ld(Register, u64) = 15, - St(Register, u64) = 16, - - MapPage = 17, - UnmapPage = 18, - - Jmp = 100, - JumpCond = 101, - Ret = 103, - Ecall = 255, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[repr(u8)] -#[rustfmt::skip] -pub enum Register { - X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, - X21, X22, X23, X24, X25, X26, X27, X28, X29, X30, X31, X32, X33, X34, X35, X36, X37, X38, X39, - X40, X41, X42, X43, X44, X45, X46, X47, X48, X49, X50, X51, X52, X53, X54, X55, X56, X57, X58, - X59, -} diff --git a/hbvm/src/engine/call_stack.rs b/hbvm/src/engine/call_stack.rs deleted file mode 100644 index 39fee04e..00000000 --- a/hbvm/src/engine/call_stack.rs +++ /dev/null @@ -1,6 +0,0 @@ -use alloc::vec::Vec; - -pub type CallStack = Vec; -pub struct FnCall { - pub ret: usize, -} diff --git a/hbvm/src/engine/config.rs b/hbvm/src/engine/config.rs deleted file mode 100644 index 504d6631..00000000 --- a/hbvm/src/engine/config.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub struct EngineConfig { - pub call_stack_depth: usize, - pub quantum: u32, -} - -impl EngineConfig { - pub fn default() -> Self { - Self { - call_stack_depth: 32, - quantum: 0, - } - } -} diff --git a/hbvm/src/engine/enviroment_calls.rs b/hbvm/src/engine/enviroment_calls.rs deleted file mode 100644 index 27289d6b..00000000 --- a/hbvm/src/engine/enviroment_calls.rs +++ /dev/null @@ -1,3 +0,0 @@ -use super::Engine; - -pub type EnviromentCall = fn(&mut Engine) -> Result<&mut Engine, u64>; diff --git a/hbvm/src/engine/mod.rs b/hbvm/src/engine/mod.rs deleted file mode 100644 index 4370689d..00000000 --- a/hbvm/src/engine/mod.rs +++ /dev/null @@ -1,102 +0,0 @@ -use log::info; - -pub mod call_stack; -pub mod config; -pub mod enviroment_calls; -pub mod regs; -#[cfg(test)] -pub mod tests; - -use { - self::call_stack::CallStack, - crate::{engine::enviroment_calls::EnviromentCall, memory, HaltStatus, RuntimeErrors}, - alloc::vec::Vec, - config::EngineConfig, - log::trace, - regs::Registers, -}; - -// pub const PAGE_SIZE: usize = 8192; - -pub struct RealPage { - pub ptr: *mut u8, -} - -#[derive(Debug, Clone, Copy)] -pub struct VMPage { - pub data: [u8; 8192], -} -impl Default for VMPage { - fn default() -> Self { - Self { - data: [0; 4096 * 2], - } - } -} - -pub enum Page { - VMPage(VMPage), - RealPage(RealPage), -} -impl Page { - pub fn data(&self) -> [u8; 4096 * 2] { - match self { - Page::VMPage(vmpage) => vmpage.data, - Page::RealPage(_) => { - unimplemented!("Memmapped hw page not yet supported") - } - } - } -} - -pub fn empty_enviroment_call(engine: &mut Engine) -> Result<&mut Engine, u64> { - trace!("Registers {:?}", engine.registers); - Err(0) -} - -pub struct Engine { - pub pc: usize, - pub program: Vec, - pub registers: Registers, - pub config: EngineConfig, - - /// BUG: This DOES NOT account for overflowing - pub last_timer_count: u32, - pub timer_callback: Option u32>, - pub memory: memory::Memory, - pub enviroment_call_table: [Option; 256], - pub call_stack: CallStack, -} - -impl Engine { - pub fn set_timer_callback(&mut self, func: fn() -> u32) { - self.timer_callback = Some(func); - } -} - -impl Engine { - pub fn new(program: Vec) -> Self { - let mut mem = memory::Memory::new(); - for (addr, byte) in program.clone().into_iter().enumerate() { - let _ = mem.set_addr8(addr as u64, byte); - } - trace!("{:?}", mem.read_addr8(0)); - let ecall_table: [Option; 256] = [None; 256]; - Self { - pc: 0, - program, - registers: Registers::default(), - config: EngineConfig::default(), - last_timer_count: 0, - timer_callback: None, - enviroment_call_table: ecall_table, - memory: mem, - call_stack: Vec::new(), - } - } - - pub fn dump(&self) {} - pub fn run(&mut self) -> Result { - Ok(HaltStatus::Running) - } -} diff --git a/hbvm/src/engine/tests/mod.rs b/hbvm/src/engine/tests/mod.rs deleted file mode 100644 index aa74d70b..00000000 --- a/hbvm/src/engine/tests/mod.rs +++ /dev/null @@ -1,125 +0,0 @@ -use { - super::Engine, - crate::{HaltStatus, RuntimeErrors}, - alloc::vec, - RuntimeErrors::*, -}; - -#[test] -fn invalid_program() { - let prog = vec![1, 0]; - let mut eng = Engine::new(prog); - let ret = eng.run(); - assert_eq!(ret, Err(InvalidOpcodePair(1, 0))); -} - -#[test] -fn empty_program() { - let prog = vec![]; - let mut eng = Engine::new(prog); - let ret = eng.run(); - assert_eq!(ret, Ok(HaltStatus::Halted)); -} - -#[test] -fn max_quantum_reached() { - let prog = vec![0, 0, 0, 0]; - let mut eng = Engine::new(prog); - eng.set_timer_callback(|| { - return 1; - }); - eng.config.quantum = 1; - let ret = eng.run(); - assert_eq!(ret, Ok(HaltStatus::Running)); -} - -#[test] -fn jump_out_of_bounds() { - use crate::bytecode::ops::Operations::JUMP; - let prog = vec![JUMP as u8, 0, 0, 0, 0, 0, 0, 1, 0]; - let mut eng = Engine::new(prog); - let ret = eng.run(); - assert_eq!(ret, Err(InvalidJumpAddress(256))); -} - -#[test] -fn invalid_system_call() { - let prog = vec![255, 0]; - let mut eng = Engine::new(prog); - let ret = eng.run(); - assert_eq!(ret, Err(InvalidSystemCall(0))); -} - -#[test] -fn add_u8() { - use crate::bytecode::ops::{MathOpSides::ConstantConstant, Operations::ADD}; - - let prog = vec![ADD as u8, ConstantConstant as u8, 100, 98, 0xA0]; - let mut eng = Engine::new(prog); - let _ = eng.run(); - assert_eq!(eng.registers.a0, 2); -} - -#[test] -fn sub_u8() { - use crate::bytecode::ops::Operations::SUB; - - let prog = vec![SUB as u8]; - let mut eng = Engine::new(prog); - let _ = eng.run(); - assert_eq!(eng.registers.a0, 1); -} -#[test] -fn mul_u8() { - use crate::bytecode::ops::{MathOpSides::ConstantConstant, Operations::MUL}; - - let prog = vec![MUL as u8, ConstantConstant as u8, 1, 2, 0xA0]; - let mut eng = Engine::new(prog); - let _ = eng.run(); - assert_eq!(eng.registers.a0, 2); -} - -#[test] -fn div_u8() { - use crate::bytecode::ops::Operations::DIV; - - let prog = vec![DIV as u8]; - let mut eng = Engine::new(prog); - let _ = eng.run(); - assert_eq!(eng.registers.a0, 2); -} - -#[test] -fn set_register() { - let prog = alloc::vec![]; - let mut eng = Engine::new(prog); - eng.set_register(0xA0, 1); - assert_eq!(eng.registers.a0, 1); -} - -#[test] -fn load_u8() { - use crate::bytecode::ops::{Operations::LOAD, RWSubTypes::AddrToReg}; - - let prog = vec![LOAD as u8, AddrToReg as u8, 0, 0, 0, 0, 0, 0, 1, 0, 0xA0]; - let mut eng = Engine::new(prog); - let ret = eng.memory.set_addr8(256, 1); - assert_eq!(ret, Ok(())); - let _ = eng.run(); - assert_eq!(eng.registers.a0, 1); -} -#[test] -fn set_memory_8() { - let prog = vec![]; - let mut eng = Engine::new(prog); - let ret = eng.memory.set_addr8(256, 1); - assert_eq!(ret, Ok(())); -} - -#[test] -fn set_memory_64() { - let prog = vec![]; - let mut eng = Engine::new(prog); - let ret = eng.memory.set_addr64(256, 1); - assert_eq!(ret, Ok(())); -} diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 6541de80..ffdbdbc3 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -2,8 +2,8 @@ extern crate alloc; pub mod bytecode; -pub mod engine; -pub mod memory; +pub mod validate; +pub mod vm; #[derive(Debug, PartialEq)] pub enum RuntimeErrors { diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index a5462df6..27895f39 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -1,30 +1,17 @@ -use hbvm::{ - bytecode::Instruction, - engine::Engine, - RuntimeErrors, HaltStatus, -}; +use hbvm::{vm::Vm, RuntimeErrors}; fn main() -> Result<(), RuntimeErrors> { // TODO: Grab program from cmdline #[rustfmt::skip] - let prog: Vec = vec![ - ]; - - let mut eng = Engine::new(prog); - // eng.set_timer_callback(time); - eng.enviroment_call_table[10] = Some(print_fn); - while eng.run()? != HaltStatus::Halted {} - eng.dump(); - println!("{:#?}", eng.registers); + let prog = &[]; + unsafe { + let mut vm = Vm::new_unchecked(prog); + vm.run(); + } Ok(()) } pub fn time() -> u32 { 9 } - -pub fn print_fn(engine: &mut Engine) -> Result<&mut Engine, u64> { - println!("hello"); - Ok(engine) -} diff --git a/hbvm/src/memory.rs b/hbvm/src/memory.rs deleted file mode 100644 index 1b1ff8af..00000000 --- a/hbvm/src/memory.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::engine::VMPage; - -use { - crate::{engine::Page, RuntimeErrors}, - alloc::vec::Vec, - hashbrown::HashMap, - log::trace, -}; - -pub struct Memory { - inner: HashMap, -} - -impl Memory { - pub fn new() -> Self { - Self { - inner: HashMap::new(), - } - // - } - - pub fn map_vec(&mut self, address: u64, vec: Vec) { - panic!("Mapping vectors into pages is not supported yet"); - } -} - -impl Memory { - pub fn read_addr8(&mut self, address: u64) -> Result { - let (page, offset) = addr_to_page(address); - trace!("page {} offset {}", page, offset); - match self.inner.get(&page) { - Some(page) => { - let val = page.data()[offset as usize]; - trace!("Value {}", val); - Ok(val) - } - None => { - trace!("page not mapped"); - Err(RuntimeErrors::PageNotMapped(page)) - } - } - } - pub fn read_addr64(&mut self, address: u64) -> u64 { - unimplemented!() - } - - pub fn set_addr8(&mut self, address: u64, value: u8) -> Result<(), RuntimeErrors> { - let (page, offset) = addr_to_page(address); - let ret: Option<(&u64, &mut Page)> = self.inner.get_key_value_mut(&page); - match ret { - Some((_, page)) => { - page.data()[offset as usize] = value; - } - None => { - let mut pg = VMPage::default(); - pg.data[offset as usize] = value; - self.inner.insert(page, Page::VMPage(pg)); - trace!("Mapped page {}", page); - } - } - Ok(()) - } - pub fn set_addr64(&mut self, address: u64, value: u64) -> Result<(), RuntimeErrors> { - unimplemented!() - } -} - -fn addr_to_page(addr: u64) -> (u64, u64) { - (addr / 8192, addr % 8192) -} diff --git a/hbvm/src/bytecode/validate.rs b/hbvm/src/validate.rs similarity index 100% rename from hbvm/src/bytecode/validate.rs rename to hbvm/src/validate.rs diff --git a/hbvm/src/vm/mod.rs b/hbvm/src/vm/mod.rs new file mode 100644 index 00000000..c6e8c93f --- /dev/null +++ b/hbvm/src/vm/mod.rs @@ -0,0 +1,95 @@ +mod value; + +use { + crate::bytecode::{OpParam, ParamRI, ParamRR, ParamRRR}, + core::ops, + 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.reg_mut(tg) = $handler(Value::$ty($self.reg(a0)), Value::$ty($self.reg(a1))).into(); + }}; +} + +pub struct Vm<'a> { + pub registers: [Value; 60], + 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); 60], + pc: 0, + program, + } + } + + pub fn run(&mut self) { + use crate::bytecode::opcode::*; + loop { + let Some(&opcode) = self.program.get(self.pc) + else { return }; + + 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), + NOT => { + let param = param!(self, ParamRR); + *self.reg_mut(param.0) = (!self.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), + LI => { + let param = param!(self, ParamRI); + *self.reg_mut(param.0) = param.1.into(); + } + // TODO: LD, ST + JMP => { + self.pc = + self.program.as_ptr().add(self.pc + 1).cast::().read() as usize; + } + _ => core::hint::unreachable_unchecked(), + } + } + } + } + + #[inline] + unsafe fn reg(&self, n: u8) -> &Value { + self.registers.get_unchecked(n as usize) + } + + #[inline] + unsafe fn reg_mut(&mut self, n: u8) -> &mut Value { + self.registers.get_unchecked_mut(n as usize) + } +} diff --git a/hbvm/src/engine/regs.rs b/hbvm/src/vm/value.rs similarity index 64% rename from hbvm/src/engine/regs.rs rename to hbvm/src/vm/value.rs index 60e7d428..3544dd4e 100644 --- a/hbvm/src/engine/regs.rs +++ b/hbvm/src/vm/value.rs @@ -3,31 +3,6 @@ use core::{ ops::{Index, IndexMut}, }; -#[derive(Debug, Clone, Copy)] -pub struct Registers([Value; 60]); - -impl Index for Registers { - type Output = Value; - - #[inline] - fn index(&self, index: u8) -> &Self::Output { - &self.0[index as usize] - } -} - -impl IndexMut for Registers { - #[inline] - fn index_mut(&mut self, index: u8) -> &mut Self::Output { - &mut self.0[index as usize] - } -} - -impl Default for Registers { - fn default() -> Self { - Self([Value { i: 0 }; 60]) - } -} - /// # Safety /// The macro invoker shall make sure that byte reinterpret-cast /// won't cause undefined behaviour.