forked from AbleOS/holey-bytes
Implemented traps
This commit is contained in:
parent
87ec6ded54
commit
a7cf5e4847
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -25,6 +25,12 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "delegate"
|
name = "delegate"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -36,6 +42,19 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "0.99.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustc_version",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -70,6 +89,7 @@ name = "hbvm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"delegate",
|
"delegate",
|
||||||
|
"derive_more",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"hbbytecode",
|
"hbbytecode",
|
||||||
"log",
|
"log",
|
||||||
|
@ -163,6 +183,21 @@ version = "0.6.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||||
|
dependencies = [
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
|
@ -8,6 +8,7 @@ lto = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
delegate = "0.9"
|
delegate = "0.9"
|
||||||
|
derive_more = "0.99"
|
||||||
hashbrown = "0.13"
|
hashbrown = "0.13"
|
||||||
hbbytecode.path = "../hbbytecode"
|
hbbytecode.path = "../hbbytecode"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -3,20 +3,3 @@ extern crate alloc;
|
||||||
|
|
||||||
pub mod validate;
|
pub mod validate;
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum RuntimeErrors {
|
|
||||||
InvalidOpcodePair(u8, u8),
|
|
||||||
RegisterTooSmall,
|
|
||||||
HostError(u64),
|
|
||||||
PageNotMapped(u64),
|
|
||||||
InvalidJumpAddress(u64),
|
|
||||||
InvalidSystemCall(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you solve the halting problem feel free to remove this
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub enum HaltStatus {
|
|
||||||
Halted,
|
|
||||||
Running,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
use hbvm::vm::{
|
||||||
|
mem::{Memory, MemoryAccessReason, PageSize},
|
||||||
|
trap::HandleTrap,
|
||||||
|
value::Value,
|
||||||
|
};
|
||||||
|
|
||||||
use {
|
use {
|
||||||
hbvm::{validate::validate, vm::Vm},
|
hbvm::{validate::validate, vm::Vm},
|
||||||
std::io::{stdin, Read},
|
std::io::{stdin, Read},
|
||||||
|
@ -6,13 +12,13 @@ use {
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut prog = vec![];
|
let mut prog = vec![];
|
||||||
stdin().read_to_end(&mut prog)?;
|
stdin().read_to_end(&mut prog)?;
|
||||||
|
|
||||||
if let Err(e) = validate(&prog) {
|
if let Err(e) = validate(&prog) {
|
||||||
eprintln!("Program validation error: {e:?}");
|
eprintln!("Program validation error: {e:?}");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut vm = Vm::new_unchecked(&prog);
|
let mut vm = Vm::new_unchecked(&prog, TestTrapHandler);
|
||||||
vm.memory.insert_test_page();
|
vm.memory.insert_test_page();
|
||||||
println!("Program interrupt: {:?}", vm.run());
|
println!("Program interrupt: {:?}", vm.run());
|
||||||
println!("{:?}", vm.registers);
|
println!("{:?}", vm.registers);
|
||||||
|
@ -24,3 +30,30 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
pub fn time() -> u32 {
|
pub fn time() -> u32 {
|
||||||
9
|
9
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TestTrapHandler;
|
||||||
|
impl HandleTrap for TestTrapHandler {
|
||||||
|
fn page_fault(
|
||||||
|
&mut self,
|
||||||
|
_: MemoryAccessReason,
|
||||||
|
_: &mut Memory,
|
||||||
|
_: u64,
|
||||||
|
_: PageSize,
|
||||||
|
_: *mut u8,
|
||||||
|
) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_op(&mut self, _: &mut [Value; 256], _: &mut usize, _: &mut Memory, _: u8) -> bool
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ecall(&mut self, _: &mut [Value; 256], _: &mut usize, _: &mut Memory)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,21 +1,31 @@
|
||||||
|
/// Program validation error kind
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
|
/// Unknown opcode
|
||||||
InvalidInstruction,
|
InvalidInstruction,
|
||||||
|
/// VM doesn't implement this valid opcode
|
||||||
Unimplemented,
|
Unimplemented,
|
||||||
|
/// Attempted to copy over register boundary
|
||||||
RegisterArrayOverflow,
|
RegisterArrayOverflow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
|
/// Kind
|
||||||
pub kind: ErrorKind,
|
pub kind: ErrorKind,
|
||||||
|
/// Location in bytecode
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform bytecode validation. If it passes, the program should be
|
||||||
|
/// sound to execute.
|
||||||
pub fn validate(mut program: &[u8]) -> Result<(), Error> {
|
pub fn validate(mut program: &[u8]) -> Result<(), Error> {
|
||||||
use hbbytecode::opcode::*;
|
use hbbytecode::opcode::*;
|
||||||
|
|
||||||
let start = program;
|
let start = program;
|
||||||
loop {
|
loop {
|
||||||
|
// Match on instruction types and perform necessary checks
|
||||||
program = match program {
|
program = match program {
|
||||||
[] => return Ok(()),
|
[] => return Ok(()),
|
||||||
[LD..=ST, reg, _, _, _, _, _, _, _, _, _, count, ..]
|
[LD..=ST, reg, _, _, _, _, _, _, _, _, _, count, ..]
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
mod paging;
|
mod paging;
|
||||||
|
|
||||||
use self::paging::{PageTable, Permission, PtEntry};
|
use core::mem::MaybeUninit;
|
||||||
use alloc::boxed::Box;
|
|
||||||
|
|
||||||
|
use self::paging::{PageTable, Permission, PtEntry};
|
||||||
|
use super::{trap::HandleTrap, VmRunError};
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use derive_more::Display;
|
||||||
|
|
||||||
|
/// HoleyBytes virtual memory
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
|
/// Root page table
|
||||||
root_pt: *mut PageTable,
|
root_pt: *mut PageTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,13 +53,23 @@ impl Memory {
|
||||||
entry = PtEntry::new(Box::into_raw(pt) as _, Permission::Node);
|
entry = PtEntry::new(Box::into_raw(pt) as _, Permission::Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.root_pt_mut()[0] = entry;
|
(*self.root_pt)[0] = entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load value from an address
|
/// Load value from an address
|
||||||
pub unsafe fn load(&self, addr: u64, target: *mut u8, count: usize) -> Result<(), ()> {
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Applies same conditions as for [`core::ptr::copy_nonoverlapping`]
|
||||||
|
pub unsafe fn load(
|
||||||
|
&mut self,
|
||||||
|
addr: u64,
|
||||||
|
target: *mut u8,
|
||||||
|
count: usize,
|
||||||
|
traph: &mut impl HandleTrap,
|
||||||
|
) -> Result<(), LoadError> {
|
||||||
self.memory_access(
|
self.memory_access(
|
||||||
|
MemoryAccessReason::Load,
|
||||||
addr,
|
addr,
|
||||||
target,
|
target,
|
||||||
count,
|
count,
|
||||||
|
@ -64,96 +80,193 @@ impl Memory {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count),
|
|src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count),
|
||||||
|
traph,
|
||||||
)
|
)
|
||||||
|
.map_err(|_| LoadError)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store value to an address
|
/// Store value to an address
|
||||||
pub unsafe fn store(&mut self, addr: u64, source: *const u8, count: usize) -> Result<(), ()> {
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Applies same conditions as for [`core::ptr::copy_nonoverlapping`]
|
||||||
|
pub unsafe fn store(
|
||||||
|
&mut self,
|
||||||
|
addr: u64,
|
||||||
|
source: *const u8,
|
||||||
|
count: usize,
|
||||||
|
traph: &mut impl HandleTrap,
|
||||||
|
) -> Result<(), StoreError> {
|
||||||
self.memory_access(
|
self.memory_access(
|
||||||
|
MemoryAccessReason::Store,
|
||||||
addr,
|
addr,
|
||||||
source.cast_mut(),
|
source.cast_mut(),
|
||||||
count,
|
count,
|
||||||
|perm| perm == Permission::Write,
|
|perm| perm == Permission::Write,
|
||||||
|dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count),
|
|dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count),
|
||||||
|
traph,
|
||||||
)
|
)
|
||||||
|
.map_err(|_| StoreError)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy a block of memory
|
/// Copy a block of memory
|
||||||
pub unsafe fn block_copy(&mut self, src: u64, dst: u64, count: u64) -> Result<(), ()> {
|
///
|
||||||
let count = usize::try_from(count).expect("?conradluget a better CPU");
|
/// # Safety
|
||||||
|
/// - Same as for [`Self::load`] and [`Self::store`]
|
||||||
|
/// - Your faith in the gods of UB
|
||||||
|
/// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus:
|
||||||
|
pub unsafe fn block_copy(
|
||||||
|
&mut self,
|
||||||
|
src: u64,
|
||||||
|
dst: u64,
|
||||||
|
count: usize,
|
||||||
|
traph: &mut impl HandleTrap,
|
||||||
|
) -> Result<(), MemoryAccessReason> {
|
||||||
|
// Yea, i know it is possible to do this more efficiently, but I am too lazy.
|
||||||
|
|
||||||
let mut srcs = PageSplitter::new(src, count, self.root_pt);
|
const STACK_BUFFER_SIZE: usize = 512;
|
||||||
let mut dsts = PageSplitter::new(dst, count, self.root_pt);
|
|
||||||
let mut c_src = srcs.next().ok_or(())?;
|
|
||||||
let mut c_dst = dsts.next().ok_or(())?;
|
|
||||||
|
|
||||||
loop {
|
// Decide if to use stack-allocated buffer or to heap allocate
|
||||||
let min_size = c_src.size.min(c_dst.size);
|
// Deallocation is again decided on size at the end of the function
|
||||||
|
let mut buf = MaybeUninit::<[u8; STACK_BUFFER_SIZE]>::uninit();
|
||||||
|
let buf = if count <= STACK_BUFFER_SIZE {
|
||||||
|
buf.as_mut_ptr().cast()
|
||||||
|
} else {
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::copy(c_src.ptr, c_dst.ptr, min_size);
|
let layout = core::alloc::Layout::from_size_align_unchecked(count, 1);
|
||||||
}
|
let ptr = alloc::alloc::alloc(layout);
|
||||||
|
if ptr.is_null() {
|
||||||
|
alloc::alloc::handle_alloc_error(layout);
|
||||||
|
}
|
||||||
|
|
||||||
match (
|
ptr
|
||||||
match c_src.size.saturating_sub(min_size) {
|
|
||||||
0 => srcs.next(),
|
|
||||||
size => Some(PageSplitResult { size, ..c_src }),
|
|
||||||
},
|
|
||||||
match c_dst.size.saturating_sub(min_size) {
|
|
||||||
0 => dsts.next(),
|
|
||||||
size => Some(PageSplitResult { size, ..c_dst }),
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
(None, None) => return Ok(()),
|
|
||||||
(Some(src), Some(dst)) => (c_src, c_dst) = (src, dst),
|
|
||||||
_ => return Err(()),
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform memory block transfer
|
||||||
|
let status = (|| {
|
||||||
|
// Load to buffer
|
||||||
|
self.memory_access(
|
||||||
|
MemoryAccessReason::Load,
|
||||||
|
src,
|
||||||
|
buf,
|
||||||
|
count,
|
||||||
|
|perm| {
|
||||||
|
matches!(
|
||||||
|
perm,
|
||||||
|
Permission::Readonly | Permission::Write | Permission::Exec
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|src, dst, count| core::ptr::copy(src, dst, count),
|
||||||
|
traph,
|
||||||
|
)
|
||||||
|
.map_err(|_| MemoryAccessReason::Load)?;
|
||||||
|
|
||||||
|
// Store from buffer
|
||||||
|
self.memory_access(
|
||||||
|
MemoryAccessReason::Store,
|
||||||
|
dst,
|
||||||
|
buf,
|
||||||
|
count,
|
||||||
|
|perm| perm == Permission::Write,
|
||||||
|
|dst, src, count| core::ptr::copy(src, dst, count),
|
||||||
|
traph,
|
||||||
|
)
|
||||||
|
.map_err(|_| MemoryAccessReason::Store)?;
|
||||||
|
|
||||||
|
Ok::<_, MemoryAccessReason>(())
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Deallocate if used heap-allocated array
|
||||||
|
if count > STACK_BUFFER_SIZE {
|
||||||
|
alloc::alloc::dealloc(
|
||||||
|
buf,
|
||||||
|
core::alloc::Layout::from_size_align_unchecked(count, 1),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
/// Split address to pages, check their permissions and feed pointers with offset
|
||||||
pub fn root_pt(&self) -> &PageTable {
|
/// to a specified function.
|
||||||
unsafe { &*self.root_pt }
|
///
|
||||||
}
|
/// If page is not found, execute page fault trap handler.
|
||||||
|
#[allow(clippy::too_many_arguments)] // Silence peasant
|
||||||
#[inline]
|
|
||||||
pub fn root_pt_mut(&mut self) -> &mut PageTable {
|
|
||||||
unsafe { &mut *self.root_pt }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn memory_access(
|
fn memory_access(
|
||||||
&self,
|
&mut self,
|
||||||
|
reason: MemoryAccessReason,
|
||||||
src: u64,
|
src: u64,
|
||||||
mut dst: *mut u8,
|
mut dst: *mut u8,
|
||||||
len: usize,
|
len: usize,
|
||||||
permission_check: impl Fn(Permission) -> bool,
|
permission_check: fn(Permission) -> bool,
|
||||||
action: impl Fn(*mut u8, *mut u8, usize),
|
action: fn(*mut u8, *mut u8, usize),
|
||||||
|
traph: &mut impl HandleTrap,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
for PageSplitResult { ptr, size, perm } in PageSplitter::new(src, len, self.root_pt) {
|
let mut pspl = AddrSplitter::new(src, len, self.root_pt);
|
||||||
if !permission_check(perm) {
|
loop {
|
||||||
return Err(());
|
match pspl.next() {
|
||||||
|
// Page found
|
||||||
|
Some(Ok(AddrSplitOk { ptr, size, perm })) => {
|
||||||
|
if !permission_check(perm) {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform memory action and bump dst pointer
|
||||||
|
action(ptr, dst, size);
|
||||||
|
dst = unsafe { dst.add(size) };
|
||||||
|
}
|
||||||
|
Some(Err(AddrSplitError { addr, size })) => {
|
||||||
|
// Execute page fault handler
|
||||||
|
if traph.page_fault(reason, self, addr, size, dst) {
|
||||||
|
// Shift the splitter address
|
||||||
|
pspl.bump(size);
|
||||||
|
|
||||||
|
// Bump dst pointer
|
||||||
|
dst = unsafe { dst.add(size as _) };
|
||||||
|
} else {
|
||||||
|
return Err(()); // Unhandleable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return Ok(()),
|
||||||
}
|
}
|
||||||
|
|
||||||
action(ptr, dst, size);
|
|
||||||
dst = unsafe { dst.add(size) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PageSplitResult {
|
/// Result from address split
|
||||||
|
struct AddrSplitOk {
|
||||||
|
/// Pointer to the start for perform operation
|
||||||
ptr: *mut u8,
|
ptr: *mut u8,
|
||||||
|
|
||||||
|
/// Size to the end of page / end of desired size
|
||||||
size: usize,
|
size: usize,
|
||||||
|
|
||||||
|
/// Page permission
|
||||||
perm: Permission,
|
perm: Permission,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PageSplitter {
|
struct AddrSplitError {
|
||||||
|
/// Address of failure
|
||||||
addr: u64,
|
addr: u64,
|
||||||
|
|
||||||
|
/// Requested page size
|
||||||
|
size: PageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Address splitter into pages
|
||||||
|
struct AddrSplitter {
|
||||||
|
/// Current address
|
||||||
|
addr: u64,
|
||||||
|
|
||||||
|
/// Size left
|
||||||
size: usize,
|
size: usize,
|
||||||
|
|
||||||
|
/// Page table
|
||||||
pagetable: *const PageTable,
|
pagetable: *const PageTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageSplitter {
|
impl AddrSplitter {
|
||||||
|
/// Create a new page splitter
|
||||||
pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self {
|
pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self {
|
||||||
Self {
|
Self {
|
||||||
addr,
|
addr,
|
||||||
|
@ -161,19 +274,29 @@ impl PageSplitter {
|
||||||
pagetable,
|
pagetable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bump address by size X
|
||||||
|
fn bump(&mut self, page_size: PageSize) {
|
||||||
|
self.addr += page_size as u64;
|
||||||
|
self.size = self.size.saturating_sub(page_size as _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for PageSplitter {
|
impl Iterator for AddrSplitter {
|
||||||
type Item = PageSplitResult;
|
type Item = Result<AddrSplitOk, AddrSplitError>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// The end, everything is fine
|
||||||
if self.size == 0 {
|
if self.size == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (base, perm, size, offset) = 'a: {
|
let (base, perm, size, offset) = 'a: {
|
||||||
let mut current_pt = self.pagetable;
|
let mut current_pt = self.pagetable;
|
||||||
|
|
||||||
|
// Walk the page table
|
||||||
for lvl in (0..5).rev() {
|
for lvl in (0..5).rev() {
|
||||||
|
// Get an entry
|
||||||
unsafe {
|
unsafe {
|
||||||
let entry = (*current_pt).get_unchecked(
|
let entry = (*current_pt).get_unchecked(
|
||||||
usize::try_from((self.addr >> (lvl * 9 + 12)) & ((1 << 9) - 1))
|
usize::try_from((self.addr >> (lvl * 9 + 12)) & ((1 << 9) - 1))
|
||||||
|
@ -182,34 +305,102 @@ impl Iterator for PageSplitter {
|
||||||
|
|
||||||
let ptr = entry.ptr();
|
let ptr = entry.ptr();
|
||||||
match entry.permission() {
|
match entry.permission() {
|
||||||
Permission::Empty => return None,
|
// No page → page fault
|
||||||
|
Permission::Empty => {
|
||||||
|
return Some(Err(AddrSplitError {
|
||||||
|
addr: self.addr,
|
||||||
|
size: PageSize::from_lvl(lvl)?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node → proceed waking
|
||||||
Permission::Node => current_pt = ptr as _,
|
Permission::Node => current_pt = ptr as _,
|
||||||
|
|
||||||
|
// Leaft → return relevant data
|
||||||
perm => {
|
perm => {
|
||||||
break 'a (
|
break 'a (
|
||||||
|
// Pointer in host memory
|
||||||
ptr as *mut u8,
|
ptr as *mut u8,
|
||||||
perm,
|
perm,
|
||||||
match lvl {
|
PageSize::from_lvl(lvl)?,
|
||||||
0 => 4096,
|
// In-page offset
|
||||||
1 => 1024_usize.pow(2) * 2,
|
|
||||||
2 => 1024_usize.pow(3),
|
|
||||||
_ => return None,
|
|
||||||
},
|
|
||||||
self.addr as usize & ((1 << (lvl * 9 + 12)) - 1),
|
self.addr as usize & ((1 << (lvl * 9 + 12)) - 1),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return None;
|
return None; // Reached the end (should not happen)
|
||||||
};
|
};
|
||||||
|
|
||||||
let avail = (size - offset).clamp(0, self.size);
|
// Get available byte count in the selected page with offset
|
||||||
self.addr += size as u64;
|
let avail = (size as usize - offset).clamp(0, self.size);
|
||||||
self.size = self.size.saturating_sub(size);
|
self.bump(size);
|
||||||
Some(PageSplitResult {
|
|
||||||
ptr: unsafe { base.add(offset) },
|
Some(Ok(AddrSplitOk {
|
||||||
|
ptr: unsafe { base.add(offset) }, // Return pointer to the start of region
|
||||||
size: avail,
|
size: avail,
|
||||||
perm,
|
perm,
|
||||||
})
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Page size
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum PageSize {
|
||||||
|
/// 4 KiB page (on level 0)
|
||||||
|
Size4K = 4096,
|
||||||
|
|
||||||
|
/// 2 MiB page (on level 1)
|
||||||
|
Size2M = 1024 * 1024 * 2,
|
||||||
|
|
||||||
|
/// 1 GiB page (on level 2)
|
||||||
|
Size1G = 1024 * 1024 * 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PageSize {
|
||||||
|
/// Convert page table level to size of page
|
||||||
|
fn from_lvl(lvl: u8) -> Option<Self> {
|
||||||
|
match lvl {
|
||||||
|
0 => Some(PageSize::Size4K),
|
||||||
|
1 => Some(PageSize::Size2M),
|
||||||
|
2 => Some(PageSize::Size1G),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unhandled load access trap
|
||||||
|
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
|
||||||
|
pub struct LoadError;
|
||||||
|
|
||||||
|
/// Unhandled store access trap
|
||||||
|
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
|
||||||
|
pub struct StoreError;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
|
||||||
|
pub enum MemoryAccessReason {
|
||||||
|
Load,
|
||||||
|
Store,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MemoryAccessReason> for VmRunError {
|
||||||
|
fn from(value: MemoryAccessReason) -> Self {
|
||||||
|
match value {
|
||||||
|
MemoryAccessReason::Load => Self::LoadAccessEx,
|
||||||
|
MemoryAccessReason::Store => Self::StoreAccessEx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LoadError> for VmRunError {
|
||||||
|
fn from(_: LoadError) -> Self {
|
||||||
|
Self::LoadAccessEx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StoreError> for VmRunError {
|
||||||
|
fn from(_: StoreError) -> Self {
|
||||||
|
Self::StoreAccessEx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,30 +6,40 @@ use core::{
|
||||||
};
|
};
|
||||||
use delegate::delegate;
|
use delegate::delegate;
|
||||||
|
|
||||||
|
/// Page entry permission
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Permission {
|
pub enum Permission {
|
||||||
|
/// No page present
|
||||||
#[default]
|
#[default]
|
||||||
Empty,
|
Empty,
|
||||||
|
/// Points to another pagetable
|
||||||
Node,
|
Node,
|
||||||
|
/// Page is read only
|
||||||
Readonly,
|
Readonly,
|
||||||
|
/// Page is readable and writable
|
||||||
Write,
|
Write,
|
||||||
|
/// Page is readable and executable
|
||||||
Exec,
|
Exec,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Page table entry
|
||||||
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
||||||
pub struct PtEntry(u64);
|
pub struct PtEntry(u64);
|
||||||
impl PtEntry {
|
impl PtEntry {
|
||||||
|
/// Create new
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn new(ptr: *mut PtPointedData, permission: Permission) -> Self {
|
pub unsafe fn new(ptr: *mut PtPointedData, permission: Permission) -> Self {
|
||||||
Self(ptr as u64 | permission as u64)
|
Self(ptr as u64 | permission as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get permission
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn permission(&self) -> Permission {
|
pub fn permission(&self) -> Permission {
|
||||||
unsafe { core::mem::transmute(self.0 as u8 & 0b111) }
|
unsafe { core::mem::transmute(self.0 as u8 & 0b111) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get pointer to the data (leaf) or next page table (node)
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ptr(&self) -> *mut PtPointedData {
|
pub fn ptr(&self) -> *mut PtPointedData {
|
||||||
(self.0 & !((1 << 12) - 1)) as _
|
(self.0 & !((1 << 12) - 1)) as _
|
||||||
|
@ -45,6 +55,8 @@ impl Debug for PtEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Page table
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[repr(align(4096))]
|
#[repr(align(4096))]
|
||||||
pub struct PageTable([PtEntry; 512]);
|
pub struct PageTable([PtEntry; 512]);
|
||||||
|
@ -89,13 +101,17 @@ where
|
||||||
|
|
||||||
impl Default for PageTable {
|
impl Default for PageTable {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
// SAFETY: It's fine, zeroed page table entry is valid (= empty)
|
||||||
Self(unsafe { MaybeUninit::zeroed().assume_init() })
|
Self(unsafe { MaybeUninit::zeroed().assume_init() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data page table entry can possibly point to
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(C, align(4096))]
|
#[repr(C, align(4096))]
|
||||||
pub union PtPointedData {
|
pub union PtPointedData {
|
||||||
|
/// Node - next page table
|
||||||
pub pt: PageTable,
|
pub pt: PageTable,
|
||||||
|
/// Leaf
|
||||||
pub page: u8,
|
pub page: u8,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,11 @@
|
||||||
// program size. If you are (rightfully) worried about the UB, for now just
|
// program size. If you are (rightfully) worried about the UB, for now just
|
||||||
// append your program with 11 zeroes.
|
// append your program with 11 zeroes.
|
||||||
|
|
||||||
mod mem;
|
use self::trap::HandleTrap;
|
||||||
mod value;
|
|
||||||
|
pub mod mem;
|
||||||
|
pub mod trap;
|
||||||
|
pub mod value;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::validate,
|
crate::validate,
|
||||||
|
@ -23,6 +26,7 @@ use {
|
||||||
value::Value,
|
value::Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Extract a parameter from program
|
||||||
macro_rules! param {
|
macro_rules! param {
|
||||||
($self:expr, $ty:ty) => {{
|
($self:expr, $ty:ty) => {{
|
||||||
assert_impl_one!($ty: OpParam);
|
assert_impl_one!($ty: OpParam);
|
||||||
|
@ -37,6 +41,7 @@ macro_rules! param {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform binary operation `#0 ← #1 OP #2`
|
||||||
macro_rules! binary_op {
|
macro_rules! binary_op {
|
||||||
($self:expr, $ty:ident, $handler:expr) => {{
|
($self:expr, $ty:ident, $handler:expr) => {{
|
||||||
let ParamBBB(tg, a0, a1) = param!($self, ParamBBB);
|
let ParamBBB(tg, a0, a1) = param!($self, ParamBBB);
|
||||||
|
@ -51,6 +56,7 @@ macro_rules! binary_op {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform binary operation with immediate `#0 ← #1 OP imm #2`
|
||||||
macro_rules! binary_op_imm {
|
macro_rules! binary_op_imm {
|
||||||
($self:expr, $ty:ident, $handler:expr) => {{
|
($self:expr, $ty:ident, $handler:expr) => {{
|
||||||
let ParamBBD(tg, a0, imm) = param!($self, ParamBBD);
|
let ParamBBD(tg, a0, imm) = param!($self, ParamBBD);
|
||||||
|
@ -61,6 +67,7 @@ macro_rules! binary_op_imm {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected
|
||||||
macro_rules! cond_jump {
|
macro_rules! cond_jump {
|
||||||
($self:expr, $ty:ident, $expected:ident) => {{
|
($self:expr, $ty:ident, $expected:ident) => {{
|
||||||
let ParamBBD(a0, a1, jt) = param!($self, ParamBBD);
|
let ParamBBD(a0, a1, jt) = param!($self, ParamBBD);
|
||||||
|
@ -72,36 +79,59 @@ macro_rules! cond_jump {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Vm<'a> {
|
/// HoleyBytes Virtual Machine
|
||||||
|
pub struct Vm<'a, T> {
|
||||||
|
/// Holds 256 registers
|
||||||
|
///
|
||||||
|
/// Writing to register 0 is considered undefined behaviour
|
||||||
|
/// in terms of HoleyBytes program execution
|
||||||
pub registers: [Value; 256],
|
pub registers: [Value; 256],
|
||||||
|
|
||||||
|
/// Memory implementation
|
||||||
pub memory: Memory,
|
pub memory: Memory,
|
||||||
|
|
||||||
|
/// Trap handler
|
||||||
|
pub traph: T,
|
||||||
|
|
||||||
|
// Program counter
|
||||||
pc: usize,
|
pc: usize,
|
||||||
|
|
||||||
|
/// Program
|
||||||
program: &'a [u8],
|
program: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Vm<'a> {
|
impl<'a, T: HandleTrap> Vm<'a, T> {
|
||||||
|
/// Create a new VM with program and trap handler
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Program code has to be validated
|
/// Program code has to be validated
|
||||||
pub unsafe fn new_unchecked(program: &'a [u8]) -> Self {
|
pub unsafe fn new_unchecked(program: &'a [u8], traph: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
registers: [Value::from(0_u64); 256],
|
registers: [Value::from(0_u64); 256],
|
||||||
memory: Default::default(),
|
memory: Default::default(),
|
||||||
|
traph,
|
||||||
pc: 0,
|
pc: 0,
|
||||||
program,
|
program,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_validated(program: &'a [u8]) -> Result<Self, validate::Error> {
|
/// Create a new VM with program and trap handler only if it passes validation
|
||||||
|
pub fn new_validated(program: &'a [u8], traph: T) -> Result<Self, validate::Error> {
|
||||||
validate::validate(program)?;
|
validate::validate(program)?;
|
||||||
Ok(unsafe { Self::new_unchecked(program) })
|
Ok(unsafe { Self::new_unchecked(program, traph) })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) -> HaltReason {
|
/// Execute program
|
||||||
|
///
|
||||||
|
/// Program can return [`VmRunError`] if a trap handling failed
|
||||||
|
pub fn run(&mut self) -> Result<(), VmRunError> {
|
||||||
use hbbytecode::opcode::*;
|
use hbbytecode::opcode::*;
|
||||||
loop {
|
loop {
|
||||||
|
// Fetch instruction
|
||||||
let Some(&opcode) = self.program.get(self.pc)
|
let Some(&opcode) = self.program.get(self.pc)
|
||||||
else { return HaltReason::ProgramEnd };
|
else { return Ok(()) };
|
||||||
|
|
||||||
|
// Big match
|
||||||
unsafe {
|
unsafe {
|
||||||
match opcode {
|
match opcode {
|
||||||
NOP => param!(self, ()),
|
NOP => param!(self, ()),
|
||||||
|
@ -196,45 +226,30 @@ impl<'a> Vm<'a> {
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
if self
|
self.memory.load(
|
||||||
.memory
|
self.read_reg(base).as_u64() + off + n as u64,
|
||||||
.load(
|
self.registers.as_mut_ptr().add(usize::from(dst) + n).cast(),
|
||||||
self.read_reg(base).as_u64() + off + n as u64,
|
usize::from(count).saturating_sub(n),
|
||||||
self.registers.as_mut_ptr().add(usize::from(dst) + n).cast(),
|
&mut self.traph,
|
||||||
usize::from(count).saturating_sub(n),
|
)?;
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return HaltReason::LoadAccessEx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ST => {
|
ST => {
|
||||||
let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH);
|
let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH);
|
||||||
if self
|
self.memory.store(
|
||||||
.memory
|
self.read_reg(base).as_u64() + off,
|
||||||
.store(
|
self.registers.as_ptr().add(usize::from(dst)).cast(),
|
||||||
self.read_reg(base).as_u64() + off,
|
count.into(),
|
||||||
self.registers.as_ptr().add(usize::from(dst)).cast(),
|
&mut self.traph,
|
||||||
count.into(),
|
)?;
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return HaltReason::LoadAccessEx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
BMC => {
|
BMC => {
|
||||||
let ParamBBD(src, dst, count) = param!(self, ParamBBD);
|
let ParamBBD(src, dst, count) = param!(self, ParamBBD);
|
||||||
if self
|
self.memory.block_copy(
|
||||||
.memory
|
self.read_reg(src).as_u64(),
|
||||||
.block_copy(
|
self.read_reg(dst).as_u64(),
|
||||||
self.read_reg(src).as_u64(),
|
count as _,
|
||||||
self.read_reg(dst).as_u64(),
|
&mut self.traph,
|
||||||
count,
|
)?;
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return HaltReason::LoadAccessEx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
BRC => {
|
BRC => {
|
||||||
let ParamBBB(src, dst, count) = param!(self, ParamBBB);
|
let ParamBBB(src, dst, count) = param!(self, ParamBBB);
|
||||||
|
@ -261,7 +276,8 @@ impl<'a> Vm<'a> {
|
||||||
JGTU => cond_jump!(self, sint, Greater),
|
JGTU => cond_jump!(self, sint, Greater),
|
||||||
ECALL => {
|
ECALL => {
|
||||||
param!(self, ());
|
param!(self, ());
|
||||||
return HaltReason::Ecall;
|
self.traph
|
||||||
|
.ecall(&mut self.registers, &mut self.pc, &mut self.memory);
|
||||||
}
|
}
|
||||||
ADDF => binary_op!(self, as_f64, ops::Add::add),
|
ADDF => binary_op!(self, as_f64, ops::Add::add),
|
||||||
MULF => binary_op!(self, as_f64, ops::Mul::mul),
|
MULF => binary_op!(self, as_f64, ops::Mul::mul),
|
||||||
|
@ -274,21 +290,29 @@ impl<'a> Vm<'a> {
|
||||||
}
|
}
|
||||||
ADDFI => binary_op_imm!(self, as_f64, ops::Add::add),
|
ADDFI => binary_op_imm!(self, as_f64, ops::Add::add),
|
||||||
MULFI => binary_op_imm!(self, as_f64, ops::Mul::mul),
|
MULFI => binary_op_imm!(self, as_f64, ops::Mul::mul),
|
||||||
_ => return HaltReason::InvalidOpcode,
|
op => {
|
||||||
|
if !self.traph.invalid_op(
|
||||||
|
&mut self.registers,
|
||||||
|
&mut self.pc,
|
||||||
|
&mut self.memory,
|
||||||
|
op,
|
||||||
|
) {
|
||||||
|
return Err(VmRunError::InvalidOpcodeEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read register
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn read_reg(&self, n: u8) -> Value {
|
unsafe fn read_reg(&self, n: u8) -> Value {
|
||||||
if n == 0 {
|
*self.registers.get_unchecked(n as usize)
|
||||||
0_u64.into()
|
|
||||||
} else {
|
|
||||||
*self.registers.get_unchecked(n as usize)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a register.
|
||||||
|
/// Writing to register 0 is no-op.
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn write_reg(&mut self, n: u8, value: Value) {
|
unsafe fn write_reg(&mut self, n: u8, value: Value) {
|
||||||
if n != 0 {
|
if n != 0 {
|
||||||
|
@ -297,12 +321,16 @@ impl<'a> Vm<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Virtual machine halt error
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum HaltReason {
|
pub enum VmRunError {
|
||||||
ProgramEnd,
|
/// Unhandled invalid opcode exceptions
|
||||||
Ecall,
|
InvalidOpcodeEx,
|
||||||
InvalidOpcode,
|
|
||||||
|
/// Unhandled load access exception
|
||||||
LoadAccessEx,
|
LoadAccessEx,
|
||||||
|
|
||||||
|
/// Unhandled store access exception
|
||||||
StoreAccessEx,
|
StoreAccessEx,
|
||||||
}
|
}
|
||||||
|
|
33
hbvm/src/vm/trap.rs
Normal file
33
hbvm/src/vm/trap.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use super::{
|
||||||
|
mem::{Memory, MemoryAccessReason, PageSize},
|
||||||
|
value::Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Handle VM traps
|
||||||
|
pub trait HandleTrap {
|
||||||
|
/// Handle page fault
|
||||||
|
fn page_fault(
|
||||||
|
&mut self,
|
||||||
|
reason: MemoryAccessReason,
|
||||||
|
memory: &mut Memory,
|
||||||
|
vaddr: u64,
|
||||||
|
size: PageSize,
|
||||||
|
dataptr: *mut u8,
|
||||||
|
) -> bool;
|
||||||
|
|
||||||
|
/// Handle invalid opcode exception
|
||||||
|
fn invalid_op(
|
||||||
|
&mut self,
|
||||||
|
regs: &mut [Value; 256],
|
||||||
|
pc: &mut usize,
|
||||||
|
memory: &mut Memory,
|
||||||
|
op: u8,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
/// Handle environment calls
|
||||||
|
fn ecall(&mut self, regs: &mut [Value; 256], pc: &mut usize, memory: &mut Memory)
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
|
@ -1,7 +1,13 @@
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
|
||||||
|
/// Define [`Value`] union
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Union variants have to be sound to byte-reinterpretate
|
||||||
|
/// between each other. Otherwise the behaviour is undefined.
|
||||||
macro_rules! value_def {
|
macro_rules! value_def {
|
||||||
($($ty:ident),* $(,)?) => {
|
($($ty:ident),* $(,)?) => {
|
||||||
|
/// HBVM register value
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
pub union Value {
|
pub union Value {
|
||||||
|
@ -10,6 +16,7 @@ macro_rules! value_def {
|
||||||
|
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
impl Value {$(
|
impl Value {$(
|
||||||
|
#[doc = "Byte-reinterpret [`Value`] as [`" $ty "`]"]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn [<as_ $ty>](&self) -> $ty {
|
pub fn [<as_ $ty>](&self) -> $ty {
|
||||||
unsafe { self.$ty }
|
unsafe { self.$ty }
|
||||||
|
@ -29,9 +36,11 @@ macro_rules! value_def {
|
||||||
}
|
}
|
||||||
|
|
||||||
value_def!(u64, i64, f64);
|
value_def!(u64, i64, f64);
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Value>(), 8);
|
||||||
|
|
||||||
impl Debug for Value {
|
impl Debug for Value {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
self.as_u64().fmt(f)
|
// Print formatted as hexadecimal, unsigned integer
|
||||||
|
write!(f, "{:x}", self.as_u64())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue