Implemented some portion of VM, missing validation

This commit is contained in:
Erin 2023-06-06 22:03:37 +02:00 committed by ondra05
parent a4b22e2053
commit e67d512f89
15 changed files with 170 additions and 404 deletions

7
Cargo.lock generated
View file

@ -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"

View file

@ -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"

56
hbvm/src/bytecode.rs Normal file
View file

@ -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 () {}

View file

@ -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,
}

View file

@ -1,6 +0,0 @@
use alloc::vec::Vec;
pub type CallStack = Vec<FnCall>;
pub struct FnCall {
pub ret: usize,
}

View file

@ -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,
}
}
}

View file

@ -1,3 +0,0 @@
use super::Engine;
pub type EnviromentCall = fn(&mut Engine) -> Result<&mut Engine, u64>;

View file

@ -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<u8>,
pub registers: Registers,
pub config: EngineConfig,
/// BUG: This DOES NOT account for overflowing
pub last_timer_count: u32,
pub timer_callback: Option<fn() -> u32>,
pub memory: memory::Memory,
pub enviroment_call_table: [Option<EnviromentCall>; 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<u8>) -> 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<EnviromentCall>; 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<HaltStatus, RuntimeErrors> {
Ok(HaltStatus::Running)
}
}

View file

@ -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(()));
}

View file

@ -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 {

View file

@ -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<u8> = 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)
}

View file

@ -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<u64, Page>,
}
impl Memory {
pub fn new() -> Self {
Self {
inner: HashMap::new(),
}
//
}
pub fn map_vec(&mut self, address: u64, vec: Vec<u8>) {
panic!("Mapping vectors into pages is not supported yet");
}
}
impl Memory {
pub fn read_addr8(&mut self, address: u64) -> Result<u8, RuntimeErrors> {
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)
}

95
hbvm/src/vm/mod.rs Normal file
View file

@ -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::<u64>().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)
}
}

View file

@ -3,31 +3,6 @@ use core::{
ops::{Index, IndexMut},
};
#[derive(Debug, Clone, Copy)]
pub struct Registers([Value; 60]);
impl Index<u8> for Registers {
type Output = Value;
#[inline]
fn index(&self, index: u8) -> &Self::Output {
&self.0[index as usize]
}
}
impl IndexMut<u8> 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.