diff --git a/fix/procedure/src/lib.rs b/fix/procedure/src/lib.rs index 9ad2a7c..a289684 100644 --- a/fix/procedure/src/lib.rs +++ b/fix/procedure/src/lib.rs @@ -5,24 +5,14 @@ static ALLOCATOR: GlobalDlmalloc = GlobalDlmalloc; use fixutils::*; num_memories!(1); -num_tables!(1); - -const HELLO: &[u8] = b"hello"; +num_tables!(2); #[fix_entrypoint] -pub fn _fixpoint_apply(combination: RustHandle<'static>) -> RustHandle<'static> { - let memory_1 = Memory::new(1).expect("expected 1 memory"); - let table_1 = Table::new(1).expect("expected 1 table"); - - let num_entries = combination.len(); - table_1.attach_tree(combination); - table_1.grow(1); - - memory_1.write(HELLO); - let blob = memory_1.create_blob(HELLO.len()); - table_1.set(num_entries, blob); - - create_strict_encode(create_identification_thunk( - table_1.create_tree(num_entries + 1), - )) +pub fn _fixpoint_apply(combination: RustHandle<'static>) -> Result, FixError> { + let blob_handle = RustHandle::from_bytes(b"hello")?; + let mut entries = combination.to_entries()?; + entries.push(blob_handle); + Ok(create_strict_encode(create_identification_thunk( + RustHandle::from_entries(&entries)?, + ))) } diff --git a/fix/utils/src/lib.rs b/fix/utils/src/lib.rs index f882a19..9928133 100644 --- a/fix/utils/src/lib.rs +++ b/fix/utils/src/lib.rs @@ -1,16 +1,32 @@ #![cfg_attr(target_arch = "wasm32", no_std)] +extern crate alloc; #[cfg(target_arch = "wasm32")] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { core::arch::wasm32::unreachable() } +use alloc::vec::Vec; use core::marker::PhantomData; use fixhandle::{ BitPack, Blob, BlobName, Encode, Handle, Object, RawName, Ref, Thunk, Tree, TreeName, }; pub use macros::{fix_entrypoint, num_memories, num_tables}; +pub mod memory; +pub mod table; + +pub use memory::*; +pub use table::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FixError { + AllOcuppied, // All memories/tables occupied + Unavailable, // Resource unavilable + GrowFailed, // Memory/Table growth failed + OutOfBounds, // Memory/Table access out of bounds +} + #[repr(u16)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Producer { @@ -60,110 +76,21 @@ impl<'a> RustHandle<'a> { pub fn is_empty(&self) -> bool { self.len() == 0 } -} - -unsafe extern "C" { - fn fix_memory_slot(index: u16) -> *mut Memory; - fn fix_table_slot(index: u16) -> *mut Table; -} - -#[repr(transparent)] -pub struct Memory(u16); - -impl Memory { - #[doc(hidden)] - pub const EMPTY: Self = Self(0); - - pub fn new(index: u16) -> Option<&'static mut Self> { - let slot = unsafe { fix_memory_slot(index) }; - if !slot.is_null() { - let memory = unsafe { &mut *slot }; - memory.0 = index; - return Some(memory); - } - None - } - - // Borrows the memory until the handle is consumed - pub fn create_blob(&self, length: usize) -> RustHandle<'_> { - RustHandle::new(Handle::Object(Object::Blob(Blob::Blob(unsafe { - BlobName::new(encode_args(Producer::CreateBlob, self.0, length)) - })))) - } - - pub fn read(&self, destination: &mut [u8]) { - unsafe { - fix_memory_read( - self.0 as u32, - destination.as_mut_ptr() as u32, - destination.len(), - ) - } - } - - pub fn write(&mut self, source: &[u8]) { - unsafe { fix_memory_write(self.0 as u32, source.as_ptr() as u32, source.len()) } - } - - pub fn size(&self) -> usize { - unsafe { fix_memory_size(self.0 as u32) } - } - - pub fn grow(&mut self, num_pages: usize) -> usize { - unsafe { fix_memory_grow(self.0 as u32, num_pages) } - } - - pub fn attach_blob(&mut self, handle: RustHandle<'_>) { - unsafe { fix_attach_blob(self.0 as u32, &handle.raw_handle) } - } -} - -#[repr(transparent)] -pub struct Table(u16); - -impl Table { - #[doc(hidden)] - pub const EMPTY: Self = Self(0); - - pub fn new(index: u16) -> Option<&'static mut Self> { - let slot = unsafe { fix_table_slot(index) }; - if !slot.is_null() { - let table = unsafe { &mut *slot }; - table.0 = index; - return Some(table); - } - None - } - - // Borrows the table until the handle is consumed - pub fn create_tree(&self, length: usize) -> RustHandle<'_> { - RustHandle::new(Handle::Object(Object::Tree(Tree::Tree(unsafe { - TreeName::new(encode_args(Producer::CreateTree, self.0, length)) - })))) - } - - pub fn get(&self, entry: usize) -> RustHandle<'_> { - assert!(entry < self.size()); - RustHandle::new(Handle::Object(Object::Tree(Tree::Tree(unsafe { - TreeName::new(encode_args(Producer::TableGet, self.0, entry)) - })))) - } - pub fn set(&mut self, entry: usize, handle: RustHandle<'_>) { - assert!(entry < self.size()); - unsafe { fix_table_set(self.0 as u32, entry, &handle.raw_handle) } + pub fn from_bytes(bytes: &[u8]) -> Result { + Memory::from_bytes(bytes)?.to_blob(bytes.len()) } - pub fn size(&self) -> usize { - unsafe { fix_table_size(self.0 as u32) } + pub fn from_entries(entries: &[RustHandle<'_>]) -> Result { + Table::from_entries(entries)?.to_tree(entries.len()) } - pub fn grow(&mut self, entries: usize) -> usize { - unsafe { fix_table_grow(self.0 as u32, entries) } + pub fn to_bytes(&self) -> Result, FixError> { + Memory::from_blob(*self)?.to_bytes(self.len()) } - pub fn attach_tree(&mut self, handle: RustHandle<'_>) { - unsafe { fix_attach_tree(self.0 as u32, &handle.raw_handle) } + pub fn to_entries(&self) -> Result>, FixError> { + Table::from_tree(*self)?.to_entries(self.len()) } } diff --git a/fix/utils/src/memory.rs b/fix/utils/src/memory.rs new file mode 100644 index 0000000..a672e33 --- /dev/null +++ b/fix/utils/src/memory.rs @@ -0,0 +1,129 @@ +use crate::*; + +unsafe extern "C" { + fn fix_allocate_memory(index: u16) -> *mut Memory; + static FIX_NUM_MEMORIES: u16; +} +static mut POSITION: u16 = 0; +const PAGE_SIZE: usize = 65536; + +pub fn fix_next_memory() -> Result<&'static mut Memory, FixError> { + unsafe { + while POSITION < FIX_NUM_MEMORIES { + POSITION += 1; + if let Ok(memory) = Memory::new(POSITION) { + return Ok(memory); + } + } + } + Err(FixError::AllOcuppied) +} + +#[repr(transparent)] +pub struct Memory(u16); + +impl Memory { + #[doc(hidden)] + pub const EMPTY: Self = Self(0); + + pub fn new(index: u16) -> Result<&'static mut Self, FixError> { + let slot = unsafe { fix_allocate_memory(index) }; + if slot.is_null() { + return Err(FixError::Unavailable); + } + let memory = unsafe { &mut *slot }; + memory.0 = index; + Ok(memory) + } + + /// Calls the fixshell's create_blob function when resolved. + /// Borrows the memory until the handle is consumed. + /// + /// # Safety + /// + /// `length` must be <= size() * PAGE_SIZE + pub unsafe fn create_blob(&self, length: usize) -> RustHandle<'_> { + RustHandle::new(Handle::Object(Object::Blob(Blob::Blob(unsafe { + BlobName::new(encode_args(Producer::CreateBlob, self.0, length)) + })))) + } + + /// Fills the destination slice by copying bytes from the memory + /// + /// # Safety + /// + /// The `destination` slice's length must be <= size() * PAGE_SIZE + pub unsafe fn read(&self, destination: &mut [u8]) { + unsafe { + fix_memory_read( + self.0 as u32, + destination.as_mut_ptr() as u32, + destination.len(), + ) + } + } + + /// Copies the bytes from the source slice into the memory + /// + /// # Safety + /// + /// The `source` slice's length must be <= size() * PAGE_SIZE + pub unsafe fn write(&mut self, source: &[u8]) { + unsafe { fix_memory_write(self.0 as u32, source.as_ptr() as u32, source.len()) } + } + + /// Calls the fixshell's attach_blob after resolving the provided `handle` + /// + /// # Safety + /// + /// `handle` must refer to a blob + pub unsafe fn attach_blob(&mut self, handle: RustHandle<'_>) { + unsafe { fix_attach_blob(self.0 as u32, &handle.raw_handle) } + } + + pub fn size(&self) -> usize { + unsafe { fix_memory_size(self.0 as u32) } + } + + pub fn grow(&mut self, num_pages: usize) -> usize { + unsafe { fix_memory_grow(self.0 as u32, num_pages) } + } + + pub fn from_bytes(bytes: &[u8]) -> Result<&'static mut Self, FixError> { + let memory = fix_next_memory()?; + let mapped = memory.size(); + let required = bytes.len().div_ceil(PAGE_SIZE); + if required > mapped && memory.grow(required - mapped) == usize::MAX { + return Err(FixError::GrowFailed); + } + unsafe { memory.write(bytes) }; + Ok(memory) + } + + pub fn from_blob(handle: RustHandle<'_>) -> Result<&'static mut Self, FixError> { + let memory = fix_next_memory()?; + let mapped = memory.size(); + let required = handle.len().div_ceil(PAGE_SIZE); + if required > mapped && memory.grow(required - mapped) == usize::MAX { + return Err(FixError::GrowFailed); + } + unsafe { memory.attach_blob(handle) }; + Ok(memory) + } + + pub fn to_bytes(&self, length: usize) -> Result, FixError> { + if length > self.size() * PAGE_SIZE { + return Err(FixError::OutOfBounds); + } + let mut bytes = alloc::vec![0; length]; + unsafe { self.read(&mut bytes) }; + Ok(bytes) + } + + pub fn to_blob(&self, length: usize) -> Result, FixError> { + if length > self.size() * PAGE_SIZE { + return Err(FixError::OutOfBounds); + } + Ok(unsafe { self.create_blob(length) }) + } +} diff --git a/fix/utils/src/table.rs b/fix/utils/src/table.rs new file mode 100644 index 0000000..61bb7e6 --- /dev/null +++ b/fix/utils/src/table.rs @@ -0,0 +1,128 @@ +use crate::*; + +unsafe extern "C" { + fn fix_allocate_table(index: u16) -> *mut Table; + static FIX_NUM_TABLES: u16; +} +static mut POSITION: u16 = 0; + +pub fn fix_next_table() -> Result<&'static mut Table, FixError> { + unsafe { + while POSITION < FIX_NUM_TABLES { + POSITION += 1; + if let Ok(table) = Table::new(POSITION) { + return Ok(table); + } + } + } + Err(FixError::AllOcuppied) +} + +#[repr(transparent)] +pub struct Table(u16); + +impl Table { + #[doc(hidden)] + pub const EMPTY: Self = Self(0); + + pub fn new(index: u16) -> Result<&'static mut Self, FixError> { + let slot = unsafe { fix_allocate_table(index) }; + if slot.is_null() { + return Err(FixError::Unavailable); + } + let table = unsafe { &mut *slot }; + table.0 = index; + Ok(table) + } + + /// Calls the fixshell's create_tree function when resolved. + /// Borrows the table until the handle is consumed + /// + /// # Safety + /// + /// `length` must be <= size() + pub unsafe fn create_tree(&self, length: usize) -> RustHandle<'_> { + RustHandle::new(Handle::Object(Object::Tree(Tree::Tree(unsafe { + TreeName::new(encode_args(Producer::CreateTree, self.0, length)) + })))) + } + + /// Gets the externref with index `entry` from the table when resolved + /// + /// # Safety + /// + /// `entry` must be < size() + pub unsafe fn get(&self, entry: usize) -> RustHandle<'_> { + RustHandle::new(Handle::Object(Object::Tree(Tree::Tree(unsafe { + TreeName::new(encode_args(Producer::TableGet, self.0, entry)) + })))) + } + + /// Sets index `entry` in the table with the externref resolved from `handle` + /// + /// # Safety + /// + /// `entry` must be < size() + pub unsafe fn set(&mut self, entry: usize, handle: RustHandle<'_>) { + unsafe { fix_table_set(self.0 as u32, entry, &handle.raw_handle) } + } + + /// Calls the fixshell's attach_tree after resolving the provided `handle` + /// + /// # Safety + /// + /// `handle` must refer to a tree + pub unsafe fn attach_tree(&mut self, handle: RustHandle<'_>) { + unsafe { fix_attach_tree(self.0 as u32, &handle.raw_handle) } + } + + pub fn size(&self) -> usize { + unsafe { fix_table_size(self.0 as u32) } + } + + pub fn grow(&mut self, entries: usize) -> usize { + unsafe { fix_table_grow(self.0 as u32, entries) } + } + + pub fn from_entries(entries: &[RustHandle<'_>]) -> Result<&'static mut Self, FixError> { + let table = fix_next_table()?; + let mapped = table.size(); + let required = entries.len(); + if required > mapped && table.grow(required - mapped) == usize::MAX { + return Err(FixError::GrowFailed); + } + for (entry, handle) in entries.iter().enumerate() { + unsafe { table.set(entry, *handle) }; + } + Ok(table) + } + + pub fn from_tree(handle: RustHandle<'_>) -> Result<&'static mut Self, FixError> { + let table = fix_next_table()?; + let mapped = table.size(); + let required = handle.len(); + if required > mapped && table.grow(required - mapped) == usize::MAX { + return Err(FixError::GrowFailed); + } + unsafe { table.attach_tree(handle) }; + Ok(table) + } + + pub fn to_entries(&self, length: usize) -> Result>, FixError> { + if self.size() < length { + return Err(FixError::OutOfBounds); + } + let mut entries = Vec::with_capacity(length); + for entry in 0..length { + entries.push(unsafe { self.get(entry) }); + } + Ok(entries) + } + + pub fn to_tree(&self, length: usize) -> Result, FixError> { + if self.size() < length { + return Err(FixError::OutOfBounds); + } + Ok(unsafe { self.create_tree(length) }) + } +} diff --git a/fix/wasm/fixprocedure.wasm b/fix/wasm/fixprocedure.wasm index f8c35b7..ee0425e 100644 Binary files a/fix/wasm/fixprocedure.wasm and b/fix/wasm/fixprocedure.wasm differ diff --git a/macros/src/fix_utils.rs b/macros/src/fix_utils.rs index 21f6103..d4e7518 100644 --- a/macros/src/fix_utils.rs +++ b/macros/src/fix_utils.rs @@ -1,6 +1,5 @@ use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote}; +use quote::quote; use syn::{parse_macro_input, ItemFn, LitInt}; pub fn entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { @@ -11,36 +10,12 @@ pub fn entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { #item #[unsafe(export_name = "_fixpoint_apply_inner")] pub extern "C" fn _fixpoint_apply_inner(combination: ::fixutils::RustHandle<'static>) -> ::fixutils::RustHandle<'static> { - #_fixpoint_apply(combination) + #_fixpoint_apply(combination).expect("expected _fixpoint_apply to succeed") } } .into() } -fn registry(count: usize, kind: &str, lookup: &str) -> TokenStream2 { - let kind = format_ident!("{kind}"); - let lookup = format_ident!("{lookup}"); - - quote! { - #[doc(hidden)] - #[unsafe(no_mangle)] - pub extern "C" fn #lookup(index: u16) -> *mut ::fixutils::#kind { - use ::core::sync::atomic::{AtomicBool, Ordering}; - - const COUNT: usize = #count; - static mut SLOTS: [::fixutils::#kind; COUNT] = [const { ::fixutils::#kind::EMPTY }; COUNT]; - static OCCUPIED: [AtomicBool; COUNT] = [const { AtomicBool::new(false) }; COUNT]; - let slot_index = index as usize - 1; - - // can't get memory 0, memory above count, or already occupied memory - if index == 0 || index as usize > COUNT || OCCUPIED[slot_index].swap(true, Ordering::Relaxed) { - return ::core::ptr::null_mut(); - } - unsafe { (&raw mut SLOTS).cast::<::fixutils::#kind>().add(slot_index) } - } - } -} - fn memory_asm(count: usize) -> String { let mut asm = String::new(); for (name, signature, body) in [ @@ -123,9 +98,32 @@ pub fn num_memories(input: TokenStream) -> TokenStream { Ok(count) => count, Err(error) => return error.to_compile_error().into(), }; - let registry = registry(count, "Memory", "fix_memory_slot"); let asm = memory_asm(count); - quote! { #registry ::core::arch::global_asm!(#asm); }.into() + quote! { + #[doc(hidden)] + #[unsafe(no_mangle)] + pub static FIX_NUM_MEMORIES: u16 = #count as u16; + + #[doc(hidden)] + #[unsafe(no_mangle)] + pub extern "C" fn fix_allocate_memory(index: u16) -> *mut ::fixutils::Memory { + use ::core::sync::atomic::{AtomicBool, Ordering}; + + const COUNT: usize = #count; + static mut SLOTS: [::fixutils::Memory; COUNT] = [const { ::fixutils::Memory::EMPTY }; COUNT]; + static OCCUPIED: [AtomicBool; COUNT] = [const { AtomicBool::new(false) }; COUNT]; + let slot_index = index as usize - 1; + + // can't get memory 0, memory above count, or already occupied memory + if index == 0 || index as usize > COUNT || OCCUPIED[slot_index].swap(true, Ordering::Relaxed) { + return ::core::ptr::null_mut(); + } + unsafe { (&raw mut SLOTS).cast::<::fixutils::Memory>().add(index as usize - 1) } + } + + ::core::arch::global_asm!(#asm); + } + .into() } pub fn num_tables(input: TokenStream) -> TokenStream { @@ -133,7 +131,30 @@ pub fn num_tables(input: TokenStream) -> TokenStream { Ok(count) => count, Err(error) => return error.to_compile_error().into(), }; - let registry = registry(count, "Table", "fix_table_slot"); let asm = table_asm(count); - quote! { #registry ::core::arch::global_asm!(#asm); }.into() + quote! { + #[doc(hidden)] + #[unsafe(no_mangle)] + pub static FIX_NUM_TABLES: u16 = #count as u16; + + #[doc(hidden)] + #[unsafe(no_mangle)] + pub extern "C" fn fix_allocate_table(index: u16) -> *mut ::fixutils::Table { + use ::core::sync::atomic::{AtomicBool, Ordering}; + + const COUNT: usize = #count; + static mut SLOTS: [::fixutils::Table; COUNT] = [const { ::fixutils::Table::EMPTY }; COUNT]; + static OCCUPIED: [AtomicBool; COUNT] = [const { AtomicBool::new(false) }; COUNT]; + let slot_index = index as usize - 1; + + // can't get table 0, table above count, or already occupied table + if index == 0 || index as usize > COUNT || OCCUPIED[slot_index].swap(true, Ordering::Relaxed) { + return ::core::ptr::null_mut(); + } + unsafe { (&raw mut SLOTS).cast::<::fixutils::Table>().add(index as usize - 1) } + } + + ::core::arch::global_asm!(#asm); + } + .into() }