diff --git a/Cargo.lock b/Cargo.lock index 9ba3ea2..9657079 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,36 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "compiler" version = "0.1.0" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "delegate" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d358e0ec5c59a5e1603b933def447096886121660fc680dc1e64a0753981fe3c" +dependencies = [ + "proc-macro2", + "quote", + "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]] name = "fnv" version = "1.0.7" @@ -62,6 +92,8 @@ version = "0.1.0" name = "hbvm" version = "0.1.0" dependencies = [ + "delegate", + "derive_more", "hashbrown", "hbbytecode", "log", @@ -106,7 +138,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn", + "syn 2.0.18", ] [[package]] @@ -154,12 +186,38 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.18" diff --git a/hbvm/Cargo.toml b/hbvm/Cargo.toml index bbb2a26..48e932f 100644 --- a/hbvm/Cargo.toml +++ b/hbvm/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" lto = true [dependencies] +delegate = "0.9" +derive_more = "0.99" hashbrown = "0.13" hbbytecode.path = "../hbbytecode" log = "0.4" diff --git a/hbvm/src/vm/mem.rs b/hbvm/src/vm/mem.rs deleted file mode 100644 index 759ea1a..0000000 --- a/hbvm/src/vm/mem.rs +++ /dev/null @@ -1,109 +0,0 @@ -// HACK: This is temporary implementation so we can have memory instructions working - -use { - crate::vm::value::Value, alloc::boxed::Box, core::mem::MaybeUninit, hashbrown::HashMap, - ma_size::MemAccessSize, -}; - -pub const PAGE_SIZE: usize = 8192; - -#[derive(Clone, Debug, Default)] -pub struct Memory { - pages: HashMap>, -} - -impl Memory { - // HACK: Just for allocation testing, will be removed when proper memory interfaces - // implemented. - pub fn insert_test_page(&mut self) { - self.pages.insert(0, unsafe { - use alloc::alloc::{alloc_zeroed, handle_alloc_error, Layout}; - let layout = Layout::new::<[u8; PAGE_SIZE]>(); - let ptr = alloc_zeroed(layout); - if ptr.is_null() { - handle_alloc_error(layout); - } - Box::from_raw(ptr.cast()) - }); - } - - /// Load value from an address - pub fn load(&self, addr: u64) -> Option { - let (page, offset) = split_addr(addr); - - // Check if copy won't get over page boundary (TODO: make it go over) - if offset + S::BYTES <= PAGE_SIZE - 1 { - let mut value = MaybeUninit::::zeroed(); - unsafe { - core::ptr::copy_nonoverlapping( - self.pages.get(&page)?.as_ptr().add(offset), - value.as_mut_ptr().cast(), - S::BYTES, - ); - - // Even zeroed [`Value`] if holding valid variants as defined is valid, - // this is always valid. - Some(value.assume_init()) - } - } else { - None - } - } - - - /// Store value to an address - pub fn store(&mut self, addr: u64, value: Value) -> Result<(), ()> { - let (page, offset) = split_addr(addr); - - // Check if copy won't get over page boundary (TODO: make it go over) - if offset + S::BYTES <= PAGE_SIZE - 1 { - unsafe { - core::ptr::copy_nonoverlapping( - (&value as *const Value).cast::(), - self.pages - .get_mut(&page) - .ok_or(())? - .as_mut_ptr() - .add(offset), - S::BYTES, - ) - }; - Ok(()) - } else { - Err(()) - } - } -} - -/// Split address into page number and in-page offset -#[inline] -pub const fn split_addr(addr: u64) -> (u64, usize) { - (addr >> PAGE_SIZE.count_ones(), (addr as usize & PAGE_SIZE)) -} - -macro_rules! size_markers { - ($($name:ident = $size:expr),* $(,)?) => { - pub mod ma_size { - /// # Safety - /// Implementor has to assure that [`MemAccessSize::BYTES`] won't be larger than - /// size of [`Value`] - pub unsafe trait MemAccessSize { - const BYTES: usize; - } - - $( - pub struct $name; - unsafe impl MemAccessSize for $name { - const BYTES: usize = $size; - } - )* - } - }; -} - -size_markers! { - Byte = 1, - Doublet = 2, - Quadlet = 4, - Octlet = 8, -} diff --git a/hbvm/src/vm/mem/mod.rs b/hbvm/src/vm/mem/mod.rs new file mode 100644 index 0000000..afb3d71 --- /dev/null +++ b/hbvm/src/vm/mem/mod.rs @@ -0,0 +1,129 @@ +// HACK: This is temporary implementation so we can have memory instructions working + +mod paging; + +use self::paging::{PageTable, Permission, PtEntry}; +use alloc::boxed::Box; +use core::mem::MaybeUninit; +use {crate::vm::value::Value, ma_size::MemAccessSize}; + +#[derive(Clone, Debug)] +pub struct Memory { + root_pt: *mut PageTable, +} + +impl Default for Memory { + fn default() -> Self { + Self { + root_pt: Box::into_raw(Box::default()), + } + } +} + +impl Drop for Memory { + fn drop(&mut self) { + let _ = unsafe { Box::from_raw(self.root_pt) }; + } +} + +impl Memory { + // HACK: Just for allocation testing, will be removed when proper memory interfaces + // implemented. + pub fn insert_test_page(&mut self) { + unsafe { + let mut entry = PtEntry::new( + { + let layout = alloc::alloc::Layout::from_size_align_unchecked(4096, 4096); + let ptr = alloc::alloc::alloc(layout); + if ptr.is_null() { + alloc::alloc::handle_alloc_error(layout); + } + + core::ptr::write_bytes(ptr, 69, 10); + ptr.cast() + }, + Permission::Write, + ); + + for _ in 0..4 { + let mut pt = Box::::default(); + pt[0] = entry; + entry = PtEntry::new(Box::into_raw(pt) as _, Permission::Node); + } + + self.root_pt_mut()[0] = entry; + } + } + + /// Load value from an address + pub fn load(&self, addr: u64) -> Option { + let mut current_pt = self.root_pt; + for lvl in (0..5).rev() { + unsafe { + let entry = (*current_pt).get_unchecked( + usize::try_from((addr >> (lvl * 9 + 12)) & ((1 << 9) - 1)) + .expect("?conradluget a better CPU"), + ); + + let ptr = entry.ptr(); + match entry.permission() { + Permission::Empty => return None, + Permission::Node => current_pt = ptr as _, + Permission::Readonly | Permission::Write | Permission::Exec => { + let mut value = MaybeUninit::::zeroed(); + core::ptr::copy_nonoverlapping::( + entry.ptr() as _, + value.as_mut_ptr().cast(), + 1, + ); + return Some(value.assume_init()); + } + } + } + } + + None + } + + /// Store value to an address + pub fn store(&mut self, addr: u64, value: Value) -> Result<(), ()> { + Err(()) + } + + #[inline] + pub fn root_pt(&self) -> &PageTable { + unsafe { &*self.root_pt } + } + + #[inline] + pub fn root_pt_mut(&mut self) -> &mut PageTable { + unsafe { &mut *self.root_pt } + } +} + +macro_rules! size_markers { + ($($name:ident = $size:expr),* $(,)?) => { + pub mod ma_size { + /// # Safety + /// Implementor has to assure that [`MemAccessSize::BYTES`] won't be larger than + /// size of [`Value`] + pub unsafe trait MemAccessSize { + const BYTES: usize; + } + + $( + pub struct $name; + unsafe impl MemAccessSize for $name { + const BYTES: usize = $size; + } + )* + } + }; +} + +size_markers! { + Byte = 1, + Doublet = 2, + Quadlet = 4, + Octlet = 8, +} diff --git a/hbvm/src/vm/mem/paging.rs b/hbvm/src/vm/mem/paging.rs new file mode 100644 index 0000000..05fffce --- /dev/null +++ b/hbvm/src/vm/mem/paging.rs @@ -0,0 +1,107 @@ +use core::{ + fmt::Debug, + mem::MaybeUninit, + ops::{Index, IndexMut}, + slice::SliceIndex, +}; +use delegate::delegate; +use derive_more::{Deref, DerefMut}; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[repr(u8)] +pub enum Permission { + #[default] + Empty, + Node, + Readonly, + Write, + Exec, +} + +#[derive(Clone, Copy, Default, PartialEq, Eq)] +pub struct PtEntry(u64); +impl PtEntry { + #[inline] + pub unsafe fn new(ptr: *mut PtPointedData, permission: Permission) -> Self { + Self(ptr as u64 | permission as u64) + } + + #[inline] + pub fn permission(&self) -> Permission { + unsafe { core::mem::transmute(self.0 as u8 & 0b111) } + } + + #[inline] + pub fn ptr(&self) -> *mut PtPointedData { + (self.0 & !((1 << 12) - 1)) as _ + } +} + +impl Debug for PtEntry { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("PtEntry") + .field("ptr", &self.ptr()) + .field("permission", &self.permission()) + .finish() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(align(4096))] +pub struct PageTable([PtEntry; 512]); + +impl PageTable { + delegate!(to self.0 { + pub unsafe fn get(&self, ix: I) -> Option<&I::Output> + where I: SliceIndex<[PtEntry]>; + + pub unsafe fn get_mut(&mut self, ix: I) -> Option<&mut I::Output> + where I: SliceIndex<[PtEntry]>; + + pub unsafe fn get_unchecked(&self, index: I) -> &I::Output + where I: SliceIndex<[PtEntry]>; + + pub unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut I::Output + where I: SliceIndex<[PtEntry]>; + }); +} + +impl Index for PageTable +where + Idx: SliceIndex<[PtEntry]>, +{ + type Output = Idx::Output; + + #[inline(always)] + fn index(&self, index: Idx) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for PageTable +where + Idx: SliceIndex<[PtEntry]>, +{ + #[inline(always)] + fn index_mut(&mut self, index: Idx) -> &mut Self::Output { + &mut self.0[index] + } +} + +impl Default for PageTable { + fn default() -> Self { + Self(unsafe { MaybeUninit::zeroed().assume_init() }) + } +} + +/// This byte is special. It has a whole page reserved just for it. +#[derive(Clone, Copy, PartialEq, Eq, Deref, DerefMut)] +#[repr(align(4096))] +pub struct SpecialByte(u8); + +#[derive(Clone, Copy)] +#[repr(C)] +pub union PtPointedData { + pub pt: PageTable, + pub page: SpecialByte, +}