diff --git a/.gitignore b/.gitignore index 4cb4dcef..40673047 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .build .fix fix/wasm/coupon-collector.wat +.vscode +.DS_Store diff --git a/fix/postprocessor/src/lib.rs b/fix/postprocessor/src/lib.rs index d48f12c0..26e26765 100644 --- a/fix/postprocessor/src/lib.rs +++ b/fix/postprocessor/src/lib.rs @@ -7,32 +7,53 @@ use wasmparser::{Parser, Payload}; pub fn process(wasm: &[u8]) -> Result> { let mut module = Module::new(); + let mut num_memories: u32 = 0; + let mut memory_section: Option = None; + // Sections that come after memory_section + let mut trailing_sections = Vec::new(); + for payload in Parser::new(0).parse_all(wasm) { match payload? { Payload::MemorySection(section) => { - let mut memory_section = MemorySection::new(); - RoundtripReencoder.parse_memory_section(&mut memory_section, section)?; - // Inject one hardcoded memory - memory_section.memory(MemoryType { - minimum: 1, - maximum: None, - memory64: false, - shared: false, - page_size_log2: None, - }); - module.section(&memory_section); + let mut memories = MemorySection::new(); + RoundtripReencoder.parse_memory_section(&mut memories, section)?; + memory_section = Some(memories); + } + Payload::CustomSection(section) if section.name() == "num_fix_memories" => { + num_memories = u32::from_le_bytes(section.data().try_into()?); } // Don't change other sections payload => { if let Some((id, range)) = payload.as_section() { - module.section(&RawSection { + let section = RawSection { id, data: &wasm[range], - }); + }; + if memory_section.is_some() { + trailing_sections.push(section); + } else { + module.section(§ion); + } } } } } + if let Some(mut mem_section) = memory_section { + for _ in 0..num_memories { + mem_section.memory(MemoryType { + minimum: 1, + maximum: None, + memory64: false, + shared: false, + page_size_log2: None, + }); + } + module.section(&mem_section); + } + for section in trailing_sections { + module.section(§ion); + } + Ok(module.finish()) } diff --git a/fix/procedure/src/lib.rs b/fix/procedure/src/lib.rs index 0992608a..9ad2a7c4 100644 --- a/fix/procedure/src/lib.rs +++ b/fix/procedure/src/lib.rs @@ -1,25 +1,28 @@ -#![cfg_attr(target_arch = "wasm32", no_std)] +#![cfg_attr(target_arch = "wasm32", no_std, feature(asm_experimental_arch))] use dlmalloc::GlobalDlmalloc; #[global_allocator] static ALLOCATOR: GlobalDlmalloc = GlobalDlmalloc; use fixutils::*; -extern crate alloc; -use alloc::vec::Vec; +num_memories!(1); +num_tables!(1); const HELLO: &[u8] = b"hello"; -const WORLD: &[u8] = b" world"; #[fix_entrypoint] -pub fn _fixpoint_apply(_combination: RustHandle) -> RustHandle { - unsafe { - let mut blob: Vec = HELLO.to_vec(); - memory_1_write(blob.as_ptr() as u32, blob.len()); - let handle = create_blob(1, blob.len()); - attach_blob(1, &handle); - memory_1_read(blob.as_mut_ptr() as u32, len(&handle)); - blob.append(&mut WORLD.to_vec()); - memory_1_write(blob.as_ptr() as u32, blob.len()); - create_blob(1, blob.len()) - } +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), + )) } diff --git a/fix/shell/src/fixpoint.rs b/fix/shell/src/fixpoint.rs index 1bca0429..c62f859f 100644 --- a/fix/shell/src/fixpoint.rs +++ b/fix/shell/src/fixpoint.rs @@ -33,7 +33,7 @@ pub unsafe extern "C" fn w2c_fixpoint_attach_tree( table_idx: u32, handle: wasm_rt_externref_t, ) { - assert!(table_idx < 63); + assert!(table_idx < 32); unsafe { let table = crate::rt::TABLES[table_idx as usize]; if (table.is_null()) { @@ -50,15 +50,15 @@ pub unsafe extern "C" fn w2c_fixpoint_attach_tree( pub unsafe extern "C" fn w2c_fixpoint_create_tree( fixpoint: *mut w2c_fixpoint, table_idx: u32, + length: u32, ) -> wasm_rt_externref_t { - assert!(table_idx < 63); + assert!(table_idx < 32); unsafe { let table = crate::rt::TABLES[table_idx as usize]; - let addr = (1usize << 32) * (64 + table_idx as usize); wasm_rt_externref_t { bytes: shell::fixpoint_create_tree(core::slice::from_raw_parts( - addr as *const u8, - (*table).size as usize, + (*table).data.cast::(), + length as usize * 32, )), } } @@ -68,15 +68,15 @@ pub unsafe extern "C" fn w2c_fixpoint_create_tree( pub unsafe extern "C" fn w2c_fixpoint_create_tag( fixpoint: *mut w2c_fixpoint, table_idx: u32, + length: u32, ) -> wasm_rt_externref_t { - assert!(table_idx < 63); + assert!(table_idx < 32); unsafe { let table = crate::rt::TABLES[table_idx as usize]; - let addr = (1usize << 32) * (64 + table_idx as usize); wasm_rt_externref_t { bytes: shell::fixpoint_create_tag(core::slice::from_raw_parts( - addr as *const u8, - (*table).size as usize, + (*table).data.cast::(), + length as usize * 32, )), } } @@ -161,6 +161,26 @@ pub unsafe extern "C" fn w2c_fixpoint_is_equal( shell::fixpoint_is_equal(lhs.bytes, rhs.bytes) as i32 } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_ref( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_ref(handle.bytes), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_identification_thunk( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_identification_thunk(handle.bytes), + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn w2c_fixpoint_create_application_thunk( fixpoint: *mut w2c_fixpoint, @@ -171,6 +191,16 @@ pub unsafe extern "C" fn w2c_fixpoint_create_application_thunk( } } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_selection_thunk( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_selection_thunk(handle.bytes), + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn w2c_fixpoint_create_strict_encode( fixpoint: *mut w2c_fixpoint, @@ -181,6 +211,16 @@ pub unsafe extern "C" fn w2c_fixpoint_create_strict_encode( } } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_shallow_encode( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_shallow_encode(handle.bytes), + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn w2c_fixpoint_len( fixpoint: *mut w2c_fixpoint, diff --git a/fix/shell/src/rt.rs b/fix/shell/src/rt.rs index 9dbcfc05..b0441125 100644 --- a/fix/shell/src/rt.rs +++ b/fix/shell/src/rt.rs @@ -2,7 +2,10 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::{ + slice::from_raw_parts_mut, + sync::atomic::{AtomicUsize, Ordering}, +}; use arcane::{__MODE_read_write, arca_compat_mmap}; use user::error; @@ -16,7 +19,7 @@ unsafe extern "C" { } pub static mut MEMORY_IDX: usize = 0; -pub static mut TABLE_IDX: usize = 0; +pub static mut TABLE_IDX: usize = 1; pub static mut FUNCREF_TABLE_IDX: usize = 0; pub static mut MEMORIES: [*mut wasm_rt_memory_t; 64] = [core::ptr::null_mut(); 64]; @@ -113,7 +116,11 @@ pub extern "C" fn wasm_rt_allocate_externref_table( max_elements = 1 << (32 - 5); } let data = ((1 << 32) * (64 + idx)) as *mut u8; - arca_compat_mmap(data as *mut _, (elements * 32) as usize, __MODE_read_write); + arca_compat_mmap( + data as *mut _, + (elements * 32).next_multiple_of(PAGE_SIZE) as usize, + __MODE_read_write, + ); table.write(wasm_rt_externref_table_t { data: data as *mut _, size: elements, @@ -134,10 +141,17 @@ pub extern "C" fn wasm_rt_grow_externref_table( return u32::MAX; } - let start = unsafe { table.data.byte_add(current as usize * 32) }; - let size = delta * 32; + let mapped = (current * 32).next_multiple_of(PAGE_SIZE) as usize; + let required = ((current + delta) * 32).next_multiple_of(PAGE_SIZE) as usize; unsafe { - arca_compat_mmap(start as *mut _, size as usize, __MODE_read_write); + if required > mapped { + arca_compat_mmap( + table.data.byte_add(mapped) as *mut _, + required - mapped, + __MODE_read_write, + ); + } + from_raw_parts_mut(table.data.add(current as usize), delta as usize).fill(init); table.size += delta; } current diff --git a/fix/shell/src/shell.rs b/fix/shell/src/shell.rs index 8d05ff12..22bd372a 100644 --- a/fix/shell/src/shell.rs +++ b/fix/shell/src/shell.rs @@ -12,7 +12,6 @@ use core::ffi::c_void; use fixhandle::*; use user::ArcaError; -use user::Ref; use user::Runtime; use user::error::log as arca_log; use user::error::log_int as arca_log_int; @@ -242,19 +241,56 @@ pub fn fixpoint_is_equal(lhs: [u8; 32], rhs: [u8; 32]) -> bool { result == 1 } +pub fn fixpoint_create_ref(handle: [u8; 32]) -> [u8; 32] { + Handle::Ref(match Handle::unpack(handle) { + Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), + Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), + _ => { + arca_log("create_ref: handle does not refer to an Object"); + panic!() + } + }) + .pack() +} + +pub fn fixpoint_create_identification_thunk(handle: [u8; 32]) -> [u8; 32] { + let reference = match Handle::unpack(handle) { + Handle::Ref(reference) => reference, + Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), + Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), + _ => { + arca_log("create_identification_thunk: handle does not refer to an Object or Ref"); + panic!() + } + }; + let thunk: Handle = Thunk::Identification(reference).into(); + thunk.pack() +} + pub fn fixpoint_create_application_thunk(handle: [u8; 32]) -> [u8; 32] { let handle = Handle::unpack(handle); - // TODO: handle refs let thunk: Handle = Thunk::Application(handle.unwrap_object().unwrap_tree()).into(); thunk.pack() } +pub fn fixpoint_create_selection_thunk(handle: [u8; 32]) -> [u8; 32] { + let handle = Handle::unpack(handle); + let thunk: Handle = Thunk::Selection(handle.unwrap_object().unwrap_tree()).into(); + thunk.pack() +} + pub fn fixpoint_create_strict_encode(handle: [u8; 32]) -> [u8; 32] { let handle = Handle::unpack(handle); let encode: Handle = Encode::Strict(handle.unwrap_thunk()).into(); encode.pack() } +pub fn fixpoint_create_shallow_encode(handle: [u8; 32]) -> [u8; 32] { + let handle = Handle::unpack(handle); + let encode: Handle = Encode::Shallow(handle.unwrap_thunk()).into(); + encode.pack() +} + pub fn fixpoint_len(handle: [u8; 32]) -> usize { let handle = Handle::unpack(handle); handle.len() diff --git a/fix/src/evaluator.rs b/fix/src/evaluator.rs index 0a62aaa8..3eb48d67 100644 --- a/fix/src/evaluator.rs +++ b/fix/src/evaluator.rs @@ -72,7 +72,8 @@ impl Evaluator { match thought { Handle::Object(_) => thought, Handle::Ref(_) => self.lift(thought), - Handle::Thunk(_) | Handle::Encode(_) => todo!(), + Handle::Thunk(thunk) => self.force(thunk), + Handle::Encode(encode) => self.lift(self.encode(encode)), } } diff --git a/fix/utils/build.rs b/fix/utils/build.rs index f74cce1a..2576ac6d 100644 --- a/fix/utils/build.rs +++ b/fix/utils/build.rs @@ -1,8 +1,10 @@ fn main() { + println!("cargo::rerun-if-changed=src/fixpoint.h"); println!("cargo::rerun-if-changed=src/fixpoint.c"); cc::Build::new() .file("src/fixpoint.c") + .include("src") .flag("-mreference-types") .opt_level(2) .compile("fixpoint"); diff --git a/fix/utils/src/fixpoint.c b/fix/utils/src/fixpoint.c index 36980f2c..97467274 100644 --- a/fix/utils/src/fixpoint.c +++ b/fix/utils/src/fixpoint.c @@ -1,62 +1,62 @@ -#include +#include "fixpoint.h" -typedef __externref_t externref; static externref __attribute__((address_space(1))) combination_global; -enum producer { - COMBINATION = 0, - TABLE_GET = 1, - CREATE_BLOB = 2, - CREATE_TREE = 3, -}; - -struct RustHandle { - uint8_t name[24]; - union { - uint64_t body; - struct { - uint32_t entry; - uint8_t producer; - uint8_t index; - uint16_t meta; - }; - }; -}; +static externref create_thunk(uint16_t meta, externref value) { + switch (THUNK_TAG(meta)) { + case IDENTIFICATION: return fixpoint_create_identification_thunk(value); + case APPLICATION: return fixpoint_create_application_thunk(value); + case SELECTION: return fixpoint_create_selection_thunk(value); + default: __builtin_unreachable(); + } +} -// Imports -__attribute__((import_module("fixpoint"), import_name("create_blob"))) -extern externref fixpoint_create_blob(uint32_t memory_index, uint32_t length); +static externref create_encode(uint16_t meta, externref value) { + switch (ENCODE_TAG(meta)) { + case STRICT: return fixpoint_create_strict_encode(value); + case SHALLOW: return fixpoint_create_shallow_encode(value); + default: __builtin_unreachable(); + } +} -__attribute__((import_module("fixpoint"), import_name("attach_blob"))) -extern void fixpoint_attach_blob(uint32_t memory_index, externref handle); +static externref resolve(const struct RustHandle* handle) { + externref value; -__attribute__((import_module("fixpoint"), import_name("len"))) -extern uint32_t fixpoint_len(externref handle); + switch (PRODUCER_TAG(handle->meta)) { + case COMBINATION: value = combination_global; break; + case TABLE_GET: value = fixpoint_table_get(handle->index, handle->entry); break; + case CREATE_BLOB: value = fixpoint_create_blob(handle->index, handle->entry); break; + case CREATE_TREE: value = fixpoint_create_tree(handle->index, handle->entry); break; + default: __builtin_unreachable(); + } -static externref resolve(const struct RustHandle *handle) { - switch (handle->producer) { - case COMBINATION: - return combination_global; - case CREATE_BLOB: - return fixpoint_create_blob(handle->index, handle->entry); - default: - __builtin_unreachable(); + switch (HANDLE_TAG(handle->meta)) { + case OBJECT: return value; + case REF: return fixpoint_create_ref(value); + case THUNK: return create_thunk(handle->meta, value); + case ENCODE: return create_encode(handle->meta, create_thunk(handle->meta, value)); + default: __builtin_unreachable(); } } -void attach_blob(uint32_t memory_index, const struct RustHandle *handle) { +void fix_attach_blob(uint32_t memory_index, const struct RustHandle* handle) { fixpoint_attach_blob(memory_index, resolve(handle)); } -uint32_t len(const struct RustHandle *handle) { +void fix_attach_tree(uint32_t table_index, const struct RustHandle* handle) { + fixpoint_attach_tree(table_index, resolve(handle)); +} + +uint32_t fix_len(const struct RustHandle* handle) { return fixpoint_len(resolve(handle)); } -extern struct RustHandle _fixpoint_apply_inner(struct RustHandle combination_global); +void fix_table_set(uint32_t table_index, uint32_t entry_index, const struct RustHandle* handle) { + fixpoint_table_set(table_index, entry_index, resolve(handle)); +} static const struct RustHandle combination = { - .producer = COMBINATION, - .meta = 0x0440, // meta bits for Handle::Object(Object::Tree(Tree::Tree(_))) + .meta = (COMBINATION << 12) | (OBJECT << 10) | (1 << 6), }; __attribute__((export_name("_fixpoint_apply"))) diff --git a/fix/utils/src/fixpoint.h b/fix/utils/src/fixpoint.h new file mode 100644 index 00000000..b96f0374 --- /dev/null +++ b/fix/utils/src/fixpoint.h @@ -0,0 +1,84 @@ +#ifndef FIX_UTILS +#define FIX_UTILS +#include + +typedef __externref_t externref; + +enum producer { + COMBINATION = 0, + TABLE_GET = 1, + CREATE_BLOB = 2, + CREATE_TREE = 3, +}; + +enum handle { + REF = 0, + OBJECT = 1, + THUNK = 2, + ENCODE = 3 +}; +enum thunk { + IDENTIFICATION = 0, + APPLICATION = 1, + SELECTION = 2 +}; +enum encode { + STRICT = 0, + SHALLOW = 1 +}; + +#define PRODUCER_TAG(meta) (((meta) >> 12) & 0x3) +#define HANDLE_TAG(meta) (((meta) >> 10) & 0x3) +#define ENCODE_TAG(meta) (((meta) >> 9) & 0x1) +#define THUNK_TAG(meta) (((meta) >> 7) & 0x3) + +struct RustHandle { + uint8_t name[24]; + union { + uint64_t body; + struct { + uint32_t entry; + uint16_t index; + uint16_t meta; + }; + }; +}; + +__attribute__((import_module("fixpoint"), import_name("create_blob"))) +extern externref fixpoint_create_blob(uint32_t memory_index, uint32_t length); + +__attribute__((import_module("fixpoint"), import_name("create_tree"))) +extern externref fixpoint_create_tree(uint32_t table_index, uint32_t length); + +__attribute__((import_module("fixpoint"), import_name("create_ref"))) +extern externref fixpoint_create_ref(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_identification_thunk"))) +extern externref fixpoint_create_identification_thunk(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_application_thunk"))) +extern externref fixpoint_create_application_thunk(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_selection_thunk"))) +extern externref fixpoint_create_selection_thunk(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_strict_encode"))) +extern externref fixpoint_create_strict_encode(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_shallow_encode"))) +extern externref fixpoint_create_shallow_encode(externref handle); + +__attribute__((import_module("fixpoint"), import_name("attach_blob"))) +extern void fixpoint_attach_blob(uint32_t memory_index, externref handle); + +__attribute__((import_module("fixpoint"), import_name("attach_tree"))) +extern void fixpoint_attach_tree(uint32_t table_index, externref handle); + +__attribute__((import_module("fixpoint"), import_name("len"))) +extern uint32_t fixpoint_len(externref handle); + +extern externref fixpoint_table_get(uint32_t table_index, uint32_t entry_index); +extern void fixpoint_table_set(uint32_t table_index, uint32_t entry_index, externref value); +extern struct RustHandle _fixpoint_apply_inner(struct RustHandle combination); + +#endif \ No newline at end of file diff --git a/fix/utils/src/lib.rs b/fix/utils/src/lib.rs index eda7d548..f882a198 100644 --- a/fix/utils/src/lib.rs +++ b/fix/utils/src/lib.rs @@ -1,78 +1,218 @@ -#![cfg_attr(target_arch = "wasm32", no_std, feature(asm_experimental_arch))] +#![cfg_attr(target_arch = "wasm32", no_std)] +#[cfg(target_arch = "wasm32")] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { core::arch::wasm32::unreachable() } -pub use fixhandle::*; -pub use macros::fix_entrypoint; -#[repr(u8)] +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}; + +#[repr(u16)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Producer { + // Combination = 0, used on C side + // table_index, entry_index + TableGet = 1, // memory_index, length CreateBlob = 2, + // table_index, length + CreateTree = 3, } -fn encode_args(producer: Producer, index: u32, entry: u32) -> RawName { - debug_assert!(index < 256); +fn encode_args(producer: Producer, index: u16, entry: usize) -> RawName { let mut bytes = [0; 32]; // 4 bytes for entry/length argument - bytes[24..28].copy_from_slice(&entry.to_le_bytes()); - // One byte for producer type - bytes[28] = producer as u8; - // One byte for table/memory index - bytes[29] = index as u8; + bytes[24..28].copy_from_slice(&(entry as u32).to_le_bytes()); + // two bytes for table/memory index + bytes[28..30].copy_from_slice(&index.to_le_bytes()); + // two bits for producer type + bytes[30..32].copy_from_slice(&((producer as u16) << 12).to_le_bytes()); RawName::forge(bytes) } #[repr(C, align(8))] -pub struct RustHandle([u8; 32]); +#[derive(Clone, Copy)] +pub struct RustHandle<'a> { + pub raw_handle: [u8; 32], + source: PhantomData<&'a ()>, // Lifetimes for memories/tables to prevent overwriting +} -impl RustHandle { +impl<'a> RustHandle<'a> { fn new(handle: Handle) -> Self { - Self(handle.pack()) + Self { + raw_handle: handle.pack(), + source: PhantomData, + } + } + + fn unpack(self) -> Handle { + Handle::unpack(self.raw_handle) + } + + pub fn len(&self) -> usize { + unsafe { fix_len(&self.raw_handle) } } -} -core::arch::global_asm!( - r#" - .globl memory_1_read -memory_1_read: - .functype memory_1_read (i32, i32) -> () - local.get 0 - i32.const 0 - local.get 1 - memory.copy 0, 1 - end_function - - .globl memory_1_write -memory_1_write: - .functype memory_1_write (i32, i32) -> () - i32.const 0 - local.get 0 - local.get 1 - memory.copy 1, 0 - end_function - "# -); + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} unsafe extern "C" { - // Copies length bytes from memory 1 to destination in program memory 0 - pub fn memory_1_read(destination: u32, length: usize); + 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); - // Copies length bytes from source in program memory 0 to memory 1 - pub fn memory_1_write(source: u32, length: usize); +impl Table { + #[doc(hidden)] + pub const EMPTY: Self = Self(0); - pub fn attach_blob(memory_index: u32, handle: *const RustHandle); - pub fn len(handle: *const RustHandle) -> usize; + 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 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 attach_tree(&mut self, handle: RustHandle<'_>) { + unsafe { fix_attach_tree(self.0 as u32, &handle.raw_handle) } + } +} + +pub fn create_ref<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Handle::Ref(match handle.unpack() { + Handle::Ref(reference) => reference, + Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), + Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), + _ => panic!("create_ref: handle does not refer to an Object"), + })) +} + +pub fn create_identification_thunk<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + let reference = match handle.unpack() { + Handle::Ref(reference) => reference, + Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), + Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), + _ => panic!("create_identification_thunk: handle does not refer to an Object or Ref"), + }; + RustHandle::new(Thunk::Identification(reference).into()) +} + +pub fn create_application_thunk<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Thunk::Application(handle.unpack().unwrap_object().unwrap_tree()).into()) +} + +pub fn create_selection_thunk<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Thunk::Selection(handle.unpack().unwrap_object().unwrap_tree()).into()) } -pub fn create_blob(memory_index: u32, length: usize) -> RustHandle { - RustHandle::new(Handle::Object(Object::Blob(Blob::Blob(unsafe { - BlobName::new(encode_args( - Producer::CreateBlob, - memory_index, - length as u32, - )) - })))) +pub fn create_strict_encode<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Encode::Strict(handle.unpack().unwrap_thunk()).into()) +} + +pub fn create_shallow_encode<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Encode::Shallow(handle.unpack().unwrap_thunk()).into()) +} + +unsafe extern "C" { + pub fn fix_memory_read(memory_index: u32, destination: u32, length: usize); + pub fn fix_memory_write(memory_index: u32, source: u32, length: usize); + pub fn fix_memory_size(memory_index: u32) -> usize; + pub fn fix_memory_grow(memory_index: u32, num_pages: usize) -> usize; + + pub fn fix_table_size(table_index: u32) -> usize; + pub fn fix_table_grow(table_index: u32, entries: usize) -> usize; + + pub fn fix_attach_blob(memory_index: u32, handle: *const [u8; 32]); + pub fn fix_attach_tree(table_index: u32, handle: *const [u8; 32]); + pub fn fix_len(handle: *const [u8; 32]) -> usize; + pub fn fix_table_set(table_index: u32, entry_index: usize, handle: *const [u8; 32]); } diff --git a/fix/wasm/fixprocedure.wasm b/fix/wasm/fixprocedure.wasm index ad0fb731..f8c35b7b 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 d9afe3f9..21f61031 100644 --- a/macros/src/fix_utils.rs +++ b/macros/src/fix_utils.rs @@ -1,18 +1,139 @@ use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, ItemFn}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, ItemFn, LitInt}; pub fn entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as ItemFn); - let ident = &item.sig.ident; + let _fixpoint_apply = &item.sig.ident; quote! { #item #[unsafe(export_name = "_fixpoint_apply_inner")] - pub extern "C" fn _fixpoint_apply_inner(combination: ::fixutils::RustHandle) -> ::fixutils::RustHandle { - let _fixpoint_apply: fn(::fixutils::RustHandle) -> ::fixutils::RustHandle = #ident; - _fixpoint_apply(combination) + pub extern "C" fn _fixpoint_apply_inner(combination: ::fixutils::RustHandle<'static>) -> ::fixutils::RustHandle<'static> { + #_fixpoint_apply(combination) } } .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 [ + ( + "fix_memory_read", + "(i32, i32, i32) -> ()", + "local.get 1\ni32.const 0\nlocal.get 2\nmemory.copy 0, {}", + ), + ( + "fix_memory_write", + "(i32, i32, i32) -> ()", + "i32.const 0\nlocal.get 1\nlocal.get 2\nmemory.copy {}, 0", + ), + ("fix_memory_size", "(i32) -> (i32)", "memory.size {}"), + ( + "fix_memory_grow", + "(i32, i32) -> (i32)", + "local.get 1\nmemory.grow {}", + ), + ] { + asm += &format!( + ".section .text.{name},\"\",@\n.globl {name}\n{name}:\n.functype {name} {signature}\n" + ); + // Match statement + for index in 1..=count { + let body = body.replace("{}", &index.to_string()); + asm += &format!("local.get 0\ni32.const {index}\ni32.eq\nif\n{body}\nreturn\nend_if\n"); + } + asm += "unreachable\nend_function\n"; + } + // Number of memories encoded in custom section + asm + &format!(".section .custom_section.num_fix_memories,\"\",@\n.int32 {count}\n") +} + +fn table_asm(count: usize) -> String { + let mut asm = String::new(); + // Tables + for index in 1..=count { + asm += &format!( + ".section .text.fix_table_{index},\"\",@\n.globl fix_table_{index}\n.tabletype fix_table_{index}, externref\nfix_table_{index}:\n" + ); + } + for (name, signature, body) in [ + ( + "fixpoint_table_get", + "(i32, i32) -> (externref)", + "local.get 1\ntable.get fix_table_{}", + ), + ( + "fixpoint_table_set", + "(i32, i32, externref) -> ()", + "local.get 1\nlocal.get 2\ntable.set fix_table_{}", + ), + ( + "fix_table_size", + "(i32) -> (i32)", + "table.size fix_table_{}", + ), + ( + "fix_table_grow", + "(i32, i32) -> (i32)", + "ref.null_extern\nlocal.get 1\ntable.grow fix_table_{}", + ), + ] { + asm += &format!( + ".section .text.{name},\"\",@\n.globl {name}\n{name}:\n.functype {name} {signature}\n" + ); + // Match statement + for index in 1..=count { + let body = body.replace("{}", &index.to_string()); + asm += &format!("local.get 0\ni32.const {index}\ni32.eq\nif\n{body}\nreturn\nend_if\n"); + } + asm += "unreachable\nend_function\n"; + } + asm +} + +pub fn num_memories(input: TokenStream) -> TokenStream { + let count: usize = match parse_macro_input!(input as LitInt).base10_parse() { + 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() +} + +pub fn num_tables(input: TokenStream) -> TokenStream { + let count: usize = match parse_macro_input!(input as LitInt).base10_parse() { + 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() +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c36ce0b1..9e458edc 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -45,3 +45,13 @@ pub fn bitpack(input: TokenStream) -> TokenStream { pub fn fix_entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { fix_utils::entrypoint(attr, item) } + +#[proc_macro] +pub fn num_memories(input: TokenStream) -> TokenStream { + fix_utils::num_memories(input) +} + +#[proc_macro] +pub fn num_tables(input: TokenStream) -> TokenStream { + fix_utils::num_tables(input) +}