diff --git a/build.rs b/build.rs index 551c09a..547030e 100644 --- a/build.rs +++ b/build.rs @@ -31,7 +31,7 @@ fn main() -> Result<(), Box> { .define("VB_DIRECT_FRAME", "on_frame") .define("VB_DIRECT_READ", "on_read") .define("VB_DIRECT_WRITE", "on_write") - .define("VBU_REALLOC", "vbu_realloc_shim") + .define("VBU_MEMMGR", "VBU_MEMMGR") .file(Path::new("shrooms-vb-core/core/vb.c")) .file(Path::new("shrooms-vb-core/util/isx.c")) .file(Path::new("shrooms-vb-core/util/vbu.c")) diff --git a/shrooms-vb-core b/shrooms-vb-core index 31b7403..6e9b727 160000 --- a/shrooms-vb-core +++ b/shrooms-vb-core @@ -1 +1 @@ -Subproject commit 31b740325288d9a92debc024a26bf1d689b3ac58 +Subproject commit 6e9b727b319e9ef48fce3f7045afe7f493d093eb diff --git a/src/emulator/shrooms_vb_util.rs b/src/emulator/shrooms_vb_util.rs index 4724e30..5675324 100644 --- a/src/emulator/shrooms_vb_util.rs +++ b/src/emulator/shrooms_vb_util.rs @@ -1,13 +1,95 @@ -use std::ffi::c_void; +use std::{ffi::c_void, sync::{LazyLock, Mutex}}; + +#[derive(Default)] +struct Reallocator { + allocations: Vec, +} + +unsafe impl Send for Reallocator {} + +struct Allocation { + ptr: *mut u8, + size: usize, +} +impl Allocation { + fn new(size: usize) -> Self { + let layout = Self::layout(size); + // SAFETY: our layout is not zero, not too big, and properly aligned + let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + if ptr.is_null() { + std::alloc::handle_alloc_error(layout); + } + Self { ptr, size } + } + + fn layout(size: usize) -> std::alloc::Layout { + assert!(size != 0); + std::alloc::Layout::from_size_align(size, 8).expect("far too large an allocation") + } + + fn as_slice(&self) -> &[u8] { + // SAFETY: we allocated this ourselves, it's safe + unsafe { std::slice::from_raw_parts(self.ptr, self.size) } + } + + fn as_mut_slice(&mut self) -> &mut [u8] { + // SAFETY: we allocated this ourselves, it's safe + unsafe { std::slice::from_raw_parts_mut(self.ptr, self.size) } + } +} + +impl Drop for Allocation { + fn drop(&mut self) { + // SAFETY: we allocated this ourselves with this layout, and it hasn't been freed + unsafe { std::alloc::dealloc(self.ptr, Self::layout(self.size)) }; + } +} + +impl Reallocator { + fn realloc(&mut self, ptr: *mut c_void, new_size: usize) -> *mut c_void { + if let Some(old_data) = self.load_allocation(ptr) { + if new_size == 0 { + // free + std::ptr::null_mut() + } else { + // actually reallocate and copy the data over + let mut new_data = Allocation::new(new_size); + let bytes_to_copy = old_data.size.min(new_size); + new_data.as_mut_slice()[..bytes_to_copy].copy_from_slice(&old_data.as_slice()[..bytes_to_copy]); + self.store_allocation(new_data) + } + } else if new_size > 0 { + // fresh allocation + let allocation = Allocation::new(new_size); + self.store_allocation(allocation) + } else { + std::ptr::null_mut() + } + } + + fn load_allocation(&mut self, ptr: *mut c_void) -> Option { + if ptr.is_null() { + return None; + } + let index = self.allocations.iter().enumerate().find_map(|(i, alloc)| std::ptr::addr_eq(alloc.ptr, ptr).then_some(i))?; + let allocation = self.allocations.remove(index); + Some(allocation) + } + + fn store_allocation(&mut self, allocation: Allocation) -> *mut c_void { + let ptr = allocation.ptr; + self.allocations.push(allocation); + ptr.cast() + } +} + +static VBU_ALLOC: LazyLock> = LazyLock::new(|| Mutex::new(Reallocator::default())); #[unsafe(no_mangle)] -unsafe fn vbu_realloc_shim(ptr: *mut c_void, new_size: usize) -> *mut c_void { - if !ptr.is_null() { - // not supporting proper realloc because it needs bookkeeping and it's unnecessary - return std::ptr::null_mut(); - } - let allocation = vec![0u8; new_size].into_boxed_slice(); - Box::into_raw(allocation).cast() +#[allow(nonstandard_style)] +unsafe fn VBU_MEMMGR(ptr: *mut c_void, new_size: usize) -> *mut c_void { + let mut allocator = VBU_ALLOC.lock().unwrap(); + allocator.realloc(ptr, new_size) } #[link(name = "vb")] @@ -25,7 +107,7 @@ pub fn rom_from_isx(bytes: &[u8]) -> Option> { if raw_rom.is_null() { return None; } - // SAFETY: the rom was allocated by vbu_realloc_shim, which created it from a Vec. - let rom = unsafe { Vec::from_raw_parts(raw_rom.cast(), rom_length, rom_length) }; - Some(rom) + let bytes = VBU_ALLOC.lock().unwrap().load_allocation(raw_rom)?; + assert_eq!(bytes.size, rom_length); + Some(bytes.as_slice().to_vec()) }