Runtime abstraction + browser REPL
Decouple ForthVM from wasmtime via a Runtime trait so the same outer interpreter, compiler, and 200+ word definitions work on both native (wasmtime) and browser (js-sys WebAssembly API) backends. Runtime trait (runtime.rs): - HostAccess trait for memory/global ops inside host function closures - HostFn type: Box<dyn Fn(&mut dyn HostAccess) -> Result<()>> - Runtime trait: memory, globals, table, instantiate, call, register NativeRuntime (runtime_native.rs): - Wraps wasmtime Engine/Store/Memory/Table/Global/Func - CallerHostAccess bridges HostAccess to wasmtime Caller API - Feature-gated behind "native" (default) outer.rs refactor: - ForthVM<R: Runtime> — generic over execution backend - All 87 host functions converted from Func::new closures to HostFn - All memory access via rt.mem_read/write_*, global access via rt.get/set_* - Zero logic changes — pure API conversion wafer-core feature gates: - default = ["native"] includes wasmtime + all native modules - Without "native": pure Rust only (outer, codegen, optimizer, dictionary) Browser REPL (crates/web): - WebRuntime: js-sys WebAssembly.Memory/Table/Global/Module/Instance - WaferRepl: wasm-bindgen entry point (evaluate, data_stack, reset) - WebAssembly.Function with Safari fallback (wrapper module) - Frontend: dark terminal UI, word panel, init code editor, history - Build: wasm-pack build --target web All 452 tests pass (431 unit + 1 benchmark + 9 comparison + 11 compliance).
This commit is contained in:
@@ -3059,7 +3059,7 @@ fn compile_multi_word_module(
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, feature = "native"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dictionary::WordId;
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::codegen::{ExportSections, compile_exportable_module};
|
||||
use crate::dictionary::WordId;
|
||||
use crate::ir::IrOp;
|
||||
use crate::outer::ForthVM;
|
||||
use crate::runtime::Runtime;
|
||||
|
||||
/// Configuration for `wafer build`.
|
||||
pub struct ExportConfig {
|
||||
@@ -39,14 +40,14 @@ pub struct ExportMetadata {
|
||||
///
|
||||
/// Returns the raw `.wasm` bytes ready to write to a file, plus the metadata.
|
||||
pub fn export_module(
|
||||
vm: &mut ForthVM,
|
||||
vm: &mut ForthVM<impl Runtime>,
|
||||
config: &ExportConfig,
|
||||
) -> anyhow::Result<(Vec<u8>, ExportMetadata)> {
|
||||
let mut words = vm.ir_words();
|
||||
|
||||
// Determine the entry point.
|
||||
// Priority: --entry flag > MAIN word > recorded top-level execution.
|
||||
let toplevel = vm.toplevel_ir();
|
||||
let toplevel = vm.toplevel_ir().to_vec();
|
||||
let entry_word_id = if let Some(ref name) = config.entry_word {
|
||||
Some(
|
||||
vm.resolve_word(name)
|
||||
@@ -58,7 +59,7 @@ pub fn export_module(
|
||||
// Synthesize a _start word from recorded top-level execution.
|
||||
// Pick a WordId that won't collide (one past the current table size).
|
||||
let start_id = WordId(vm.current_table_size());
|
||||
words.push((start_id, toplevel.to_vec()));
|
||||
words.push((start_id, toplevel.clone()));
|
||||
Some(start_id)
|
||||
} else {
|
||||
None
|
||||
@@ -361,8 +362,9 @@ mod tests {
|
||||
fn roundtrip(source: &str) -> String {
|
||||
use crate::outer::ForthVM;
|
||||
use crate::runner::run_wasm_bytes;
|
||||
use crate::runtime_native::NativeRuntime;
|
||||
|
||||
let mut vm = ForthVM::new().unwrap();
|
||||
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||
vm.set_recording(true);
|
||||
vm.evaluate(source).unwrap();
|
||||
|
||||
|
||||
+15
-3
@@ -16,13 +16,25 @@
|
||||
|
||||
pub mod codegen;
|
||||
pub mod config;
|
||||
pub mod consolidate;
|
||||
pub mod dictionary;
|
||||
pub mod error;
|
||||
pub mod export;
|
||||
pub mod ir;
|
||||
pub mod js_loader;
|
||||
pub mod memory;
|
||||
pub mod optimizer;
|
||||
pub mod runtime;
|
||||
|
||||
// Outer interpreter: runtime-agnostic, works with any Runtime impl
|
||||
#[allow(trivial_numeric_casts, clippy::unnecessary_cast)]
|
||||
pub mod outer;
|
||||
|
||||
// Modules requiring the native wasmtime runtime
|
||||
#[cfg(feature = "native")]
|
||||
pub mod consolidate;
|
||||
#[cfg(feature = "native")]
|
||||
pub mod export;
|
||||
#[cfg(feature = "native")]
|
||||
pub mod js_loader;
|
||||
#[cfg(feature = "native")]
|
||||
pub mod runner;
|
||||
#[cfg(feature = "native")]
|
||||
pub mod runtime_native;
|
||||
|
||||
+1531
-2646
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
//! Runtime abstraction for WASM execution.
|
||||
//!
|
||||
//! The [`Runtime`] trait decouples the Forth VM from any specific WASM engine.
|
||||
//! Two implementations exist:
|
||||
//! - `NativeRuntime` (wasmtime) — for CLI and native tests
|
||||
//! - `WebRuntime` (`js_sys`) — for browser REPL
|
||||
//!
|
||||
//! The [`HostAccess`] trait provides memory and global access to host function
|
||||
//! callbacks, abstracting over wasmtime's `Caller` and browser's `js_sys` APIs.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Access to WASM memory and globals from within a host function callback.
|
||||
///
|
||||
/// Both wasmtime (via `Caller`) and browser (via `js_sys`) implement this trait,
|
||||
/// allowing host function logic to be shared across runtimes.
|
||||
pub trait HostAccess {
|
||||
/// Read a 32-bit integer from linear memory at `addr` (little-endian).
|
||||
fn mem_read_i32(&mut self, addr: u32) -> i32;
|
||||
|
||||
/// Write a 32-bit integer to linear memory at `addr` (little-endian).
|
||||
fn mem_write_i32(&mut self, addr: u32, val: i32);
|
||||
|
||||
/// Read a single byte from linear memory.
|
||||
fn mem_read_u8(&mut self, addr: u32) -> u8;
|
||||
|
||||
/// Write a single byte to linear memory.
|
||||
fn mem_write_u8(&mut self, addr: u32, val: u8);
|
||||
|
||||
/// Read a slice of bytes from linear memory.
|
||||
fn mem_read_slice(&mut self, addr: u32, len: usize) -> Vec<u8>;
|
||||
|
||||
/// Write a slice of bytes to linear memory.
|
||||
fn mem_write_slice(&mut self, addr: u32, data: &[u8]);
|
||||
|
||||
/// Total size of linear memory in bytes.
|
||||
fn mem_len(&mut self) -> usize;
|
||||
|
||||
/// Read the data stack pointer global.
|
||||
fn get_dsp(&mut self) -> u32;
|
||||
|
||||
/// Write the data stack pointer global.
|
||||
fn set_dsp(&mut self, val: u32);
|
||||
|
||||
/// Read the return stack pointer global.
|
||||
fn get_rsp(&mut self) -> u32;
|
||||
|
||||
/// Write the return stack pointer global.
|
||||
fn set_rsp(&mut self, val: u32);
|
||||
|
||||
/// Read the float stack pointer global.
|
||||
fn get_fsp(&mut self) -> u32;
|
||||
|
||||
/// Write the float stack pointer global.
|
||||
fn set_fsp(&mut self, val: u32);
|
||||
|
||||
/// Call a function in the shared table by index.
|
||||
/// Needed by CATCH to invoke the xt it receives.
|
||||
fn call_func(&mut self, fn_index: u32) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
/// Host function callback type.
|
||||
///
|
||||
/// A boxed closure that receives mutable [`HostAccess`] for memory/global ops.
|
||||
/// Captures shared state (e.g. output buffer) via `Arc<Mutex<...>>`.
|
||||
pub type HostFn = Box<dyn Fn(&mut dyn HostAccess) -> anyhow::Result<()> + Send + Sync>;
|
||||
|
||||
/// Abstraction over a WASM execution runtime.
|
||||
///
|
||||
/// Provides memory access, global management, module instantiation,
|
||||
/// function execution, and host function registration.
|
||||
pub trait Runtime: Sized {
|
||||
/// Create a new runtime with shared linear memory, function table,
|
||||
/// stack pointer globals, and an `emit` host function wired to `output`.
|
||||
fn new(
|
||||
memory_pages: u32,
|
||||
table_size: u32,
|
||||
dsp_init: u32,
|
||||
rsp_init: u32,
|
||||
fsp_init: u32,
|
||||
output: Arc<Mutex<String>>,
|
||||
) -> anyhow::Result<Self>;
|
||||
|
||||
// -- Linear memory access --
|
||||
|
||||
/// Read a 32-bit integer from linear memory at `addr` (little-endian).
|
||||
fn mem_read_i32(&mut self, addr: u32) -> i32;
|
||||
|
||||
/// Write a 32-bit integer to linear memory at `addr` (little-endian).
|
||||
fn mem_write_i32(&mut self, addr: u32, val: i32);
|
||||
|
||||
/// Read a single byte from linear memory.
|
||||
fn mem_read_u8(&mut self, addr: u32) -> u8;
|
||||
|
||||
/// Write a single byte to linear memory.
|
||||
fn mem_write_u8(&mut self, addr: u32, val: u8);
|
||||
|
||||
/// Read a slice of bytes from linear memory.
|
||||
fn mem_read_slice(&mut self, addr: u32, len: usize) -> Vec<u8>;
|
||||
|
||||
/// Write a slice of bytes to linear memory.
|
||||
fn mem_write_slice(&mut self, addr: u32, data: &[u8]);
|
||||
|
||||
/// Total size of linear memory in bytes.
|
||||
fn mem_len(&mut self) -> usize;
|
||||
|
||||
// -- Globals --
|
||||
|
||||
/// Read the data stack pointer global.
|
||||
fn get_dsp(&mut self) -> u32;
|
||||
|
||||
/// Write the data stack pointer global.
|
||||
fn set_dsp(&mut self, val: u32);
|
||||
|
||||
/// Read the return stack pointer global.
|
||||
fn get_rsp(&mut self) -> u32;
|
||||
|
||||
/// Write the return stack pointer global.
|
||||
fn set_rsp(&mut self, val: u32);
|
||||
|
||||
/// Read the float stack pointer global.
|
||||
fn get_fsp(&mut self) -> u32;
|
||||
|
||||
/// Write the float stack pointer global.
|
||||
fn set_fsp(&mut self, val: u32);
|
||||
|
||||
// -- Function table --
|
||||
|
||||
/// Current number of entries in the function table.
|
||||
fn table_size(&mut self) -> u32;
|
||||
|
||||
/// Grow the table if needed so that index `needed` is valid.
|
||||
fn ensure_table_size(&mut self, needed: u32) -> anyhow::Result<()>;
|
||||
|
||||
// -- Compilation and execution --
|
||||
|
||||
/// Compile WASM bytes into a module, instantiate it with shared imports
|
||||
/// (memory, table, globals, emit), and install the exported function
|
||||
/// at `fn_index` in the shared table.
|
||||
fn instantiate_and_install(&mut self, wasm_bytes: &[u8], fn_index: u32) -> anyhow::Result<()>;
|
||||
|
||||
/// Call the function at `fn_index` in the shared table.
|
||||
fn call_func(&mut self, fn_index: u32) -> anyhow::Result<()>;
|
||||
|
||||
// -- Host functions --
|
||||
|
||||
/// Register a void→void host function at `fn_index` in the shared table.
|
||||
///
|
||||
/// The callback receives a [`HostAccess`] for memory and global operations.
|
||||
/// It may also capture shared state via `Arc<Mutex<...>>`.
|
||||
fn register_host_func(&mut self, fn_index: u32, f: HostFn) -> anyhow::Result<()>;
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
//! Native runtime implementation using wasmtime.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use wasmtime::{
|
||||
Engine, Func, FuncType, Global, Instance, Memory, Module, Mutability, Ref, RefType, Store,
|
||||
Table, Val, ValType,
|
||||
};
|
||||
|
||||
use crate::runtime::{HostAccess, HostFn, Runtime};
|
||||
|
||||
/// Host-side state accessible from WASM callbacks.
|
||||
struct NativeVmHost {
|
||||
#[allow(dead_code)]
|
||||
output: Arc<Mutex<String>>,
|
||||
}
|
||||
|
||||
/// [`HostAccess`] implementation for wasmtime, wrapping a `Caller`.
|
||||
struct CallerHostAccess<'a, 'b> {
|
||||
caller: &'a mut wasmtime::Caller<'b, NativeVmHost>,
|
||||
memory: Memory,
|
||||
table: Table,
|
||||
dsp: Global,
|
||||
rsp: Global,
|
||||
fsp: Global,
|
||||
}
|
||||
|
||||
impl HostAccess for CallerHostAccess<'_, '_> {
|
||||
fn mem_read_i32(&mut self, addr: u32) -> i32 {
|
||||
let data = self.memory.data(&self.caller);
|
||||
let a = addr as usize;
|
||||
i32::from_le_bytes(data[a..a + 4].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn mem_write_i32(&mut self, addr: u32, val: i32) {
|
||||
let a = addr as usize;
|
||||
let bytes = val.to_le_bytes();
|
||||
self.memory.data_mut(&mut *self.caller)[a..a + 4].copy_from_slice(&bytes);
|
||||
}
|
||||
|
||||
fn mem_read_u8(&mut self, addr: u32) -> u8 {
|
||||
self.memory.data(&self.caller)[addr as usize]
|
||||
}
|
||||
|
||||
fn mem_write_u8(&mut self, addr: u32, val: u8) {
|
||||
self.memory.data_mut(&mut *self.caller)[addr as usize] = val;
|
||||
}
|
||||
|
||||
fn mem_read_slice(&mut self, addr: u32, len: usize) -> Vec<u8> {
|
||||
let a = addr as usize;
|
||||
self.memory.data(&self.caller)[a..a + len].to_vec()
|
||||
}
|
||||
|
||||
fn mem_write_slice(&mut self, addr: u32, data: &[u8]) {
|
||||
let a = addr as usize;
|
||||
self.memory.data_mut(&mut *self.caller)[a..a + data.len()].copy_from_slice(data);
|
||||
}
|
||||
|
||||
fn mem_len(&mut self) -> usize {
|
||||
self.memory.data(&self.caller).len()
|
||||
}
|
||||
|
||||
fn get_dsp(&mut self) -> u32 {
|
||||
self.dsp.get(&mut *self.caller).unwrap_i32() as u32
|
||||
}
|
||||
|
||||
fn set_dsp(&mut self, val: u32) {
|
||||
self.dsp
|
||||
.set(&mut *self.caller, Val::I32(val as i32))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn get_rsp(&mut self) -> u32 {
|
||||
self.rsp.get(&mut *self.caller).unwrap_i32() as u32
|
||||
}
|
||||
|
||||
fn set_rsp(&mut self, val: u32) {
|
||||
self.rsp
|
||||
.set(&mut *self.caller, Val::I32(val as i32))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn get_fsp(&mut self) -> u32 {
|
||||
self.fsp.get(&mut *self.caller).unwrap_i32() as u32
|
||||
}
|
||||
|
||||
fn set_fsp(&mut self, val: u32) {
|
||||
self.fsp
|
||||
.set(&mut *self.caller, Val::I32(val as i32))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn call_func(&mut self, fn_index: u32) -> anyhow::Result<()> {
|
||||
let func_ref = self
|
||||
.table
|
||||
.get(&mut *self.caller, fn_index as u64)
|
||||
.ok_or_else(|| anyhow::anyhow!("call_func: invalid index {fn_index}"))?;
|
||||
let func = *func_ref
|
||||
.unwrap_func()
|
||||
.ok_or_else(|| anyhow::anyhow!("call_func: null funcref {fn_index}"))?;
|
||||
func.call(&mut *self.caller, &[], &mut [])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wasmtime-based native runtime.
|
||||
pub struct NativeRuntime {
|
||||
engine: Engine,
|
||||
store: Store<NativeVmHost>,
|
||||
memory: Memory,
|
||||
table: Table,
|
||||
dsp: Global,
|
||||
rsp: Global,
|
||||
fsp: Global,
|
||||
emit_func: Func,
|
||||
}
|
||||
|
||||
impl Runtime for NativeRuntime {
|
||||
fn new(
|
||||
memory_pages: u32,
|
||||
table_size: u32,
|
||||
dsp_init: u32,
|
||||
rsp_init: u32,
|
||||
fsp_init: u32,
|
||||
output: Arc<Mutex<String>>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut config = wasmtime::Config::new();
|
||||
config.cranelift_nan_canonicalization(false);
|
||||
let engine = Engine::new(&config)?;
|
||||
|
||||
let host = NativeVmHost {
|
||||
output: Arc::clone(&output),
|
||||
};
|
||||
let mut store = Store::new(&engine, host);
|
||||
|
||||
let memory = Memory::new(&mut store, wasmtime::MemoryType::new(memory_pages, None))?;
|
||||
|
||||
let dsp = Global::new(
|
||||
&mut store,
|
||||
wasmtime::GlobalType::new(ValType::I32, Mutability::Var),
|
||||
Val::I32(dsp_init as i32),
|
||||
)?;
|
||||
let rsp = Global::new(
|
||||
&mut store,
|
||||
wasmtime::GlobalType::new(ValType::I32, Mutability::Var),
|
||||
Val::I32(rsp_init as i32),
|
||||
)?;
|
||||
let fsp = Global::new(
|
||||
&mut store,
|
||||
wasmtime::GlobalType::new(ValType::I32, Mutability::Var),
|
||||
Val::I32(fsp_init as i32),
|
||||
)?;
|
||||
|
||||
let table = Table::new(
|
||||
&mut store,
|
||||
wasmtime::TableType::new(RefType::FUNCREF, table_size, None),
|
||||
Ref::Func(None),
|
||||
)?;
|
||||
|
||||
let out_ref = Arc::clone(&output);
|
||||
let emit_func = Func::new(
|
||||
&mut store,
|
||||
FuncType::new(&engine, [ValType::I32], []),
|
||||
move |_caller, params, _results| {
|
||||
let ch = params[0].unwrap_i32() as u8 as char;
|
||||
out_ref.lock().unwrap().push(ch);
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
Ok(NativeRuntime {
|
||||
engine,
|
||||
store,
|
||||
memory,
|
||||
table,
|
||||
dsp,
|
||||
rsp,
|
||||
fsp,
|
||||
emit_func,
|
||||
})
|
||||
}
|
||||
|
||||
// -- Memory --
|
||||
|
||||
fn mem_read_i32(&mut self, addr: u32) -> i32 {
|
||||
let a = addr as usize;
|
||||
let data = self.memory.data(&self.store);
|
||||
i32::from_le_bytes(data[a..a + 4].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn mem_write_i32(&mut self, addr: u32, val: i32) {
|
||||
let a = addr as usize;
|
||||
let bytes = val.to_le_bytes();
|
||||
self.memory.data_mut(&mut self.store)[a..a + 4].copy_from_slice(&bytes);
|
||||
}
|
||||
|
||||
fn mem_read_u8(&mut self, addr: u32) -> u8 {
|
||||
self.memory.data(&self.store)[addr as usize]
|
||||
}
|
||||
|
||||
fn mem_write_u8(&mut self, addr: u32, val: u8) {
|
||||
self.memory.data_mut(&mut self.store)[addr as usize] = val;
|
||||
}
|
||||
|
||||
fn mem_read_slice(&mut self, addr: u32, len: usize) -> Vec<u8> {
|
||||
let a = addr as usize;
|
||||
self.memory.data(&self.store)[a..a + len].to_vec()
|
||||
}
|
||||
|
||||
fn mem_write_slice(&mut self, addr: u32, data: &[u8]) {
|
||||
let a = addr as usize;
|
||||
self.memory.data_mut(&mut self.store)[a..a + data.len()].copy_from_slice(data);
|
||||
}
|
||||
|
||||
fn mem_len(&mut self) -> usize {
|
||||
self.memory.data(&self.store).len()
|
||||
}
|
||||
|
||||
// -- Globals --
|
||||
|
||||
fn get_dsp(&mut self) -> u32 {
|
||||
self.dsp.get(&mut self.store).unwrap_i32() as u32
|
||||
}
|
||||
|
||||
fn set_dsp(&mut self, val: u32) {
|
||||
self.dsp.set(&mut self.store, Val::I32(val as i32)).unwrap();
|
||||
}
|
||||
|
||||
fn get_rsp(&mut self) -> u32 {
|
||||
self.rsp.get(&mut self.store).unwrap_i32() as u32
|
||||
}
|
||||
|
||||
fn set_rsp(&mut self, val: u32) {
|
||||
self.rsp.set(&mut self.store, Val::I32(val as i32)).unwrap();
|
||||
}
|
||||
|
||||
fn get_fsp(&mut self) -> u32 {
|
||||
self.fsp.get(&mut self.store).unwrap_i32() as u32
|
||||
}
|
||||
|
||||
fn set_fsp(&mut self, val: u32) {
|
||||
self.fsp.set(&mut self.store, Val::I32(val as i32)).unwrap();
|
||||
}
|
||||
|
||||
// -- Table --
|
||||
|
||||
fn table_size(&mut self) -> u32 {
|
||||
self.table.size(&self.store) as u32
|
||||
}
|
||||
|
||||
fn ensure_table_size(&mut self, needed: u32) -> anyhow::Result<()> {
|
||||
let current = self.table.size(&self.store) as u32;
|
||||
if needed >= current {
|
||||
let grow = (needed - current + 64) as u64;
|
||||
self.table.grow(&mut self.store, grow, Ref::Func(None))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// -- Compilation and execution --
|
||||
|
||||
fn instantiate_and_install(&mut self, wasm_bytes: &[u8], fn_index: u32) -> anyhow::Result<()> {
|
||||
self.ensure_table_size(fn_index)?;
|
||||
let module = Module::new(&self.engine, wasm_bytes)?;
|
||||
let instance = Instance::new(
|
||||
&mut self.store,
|
||||
&module,
|
||||
&[
|
||||
self.emit_func.into(),
|
||||
self.memory.into(),
|
||||
self.dsp.into(),
|
||||
self.rsp.into(),
|
||||
self.fsp.into(),
|
||||
self.table.into(),
|
||||
],
|
||||
)?;
|
||||
|
||||
// Single-word modules export "fn"; multi-word (consolidated/batch)
|
||||
// modules use the element section to place functions in the table.
|
||||
if let Some(func) = instance.get_func(&mut self.store, "fn") {
|
||||
self.table
|
||||
.set(&mut self.store, fn_index as u64, Ref::Func(Some(func)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn call_func(&mut self, fn_index: u32) -> anyhow::Result<()> {
|
||||
let r = self
|
||||
.table
|
||||
.get(&mut self.store, fn_index as u64)
|
||||
.ok_or_else(|| anyhow::anyhow!("word {fn_index} not in function table"))?;
|
||||
let func = *r
|
||||
.unwrap_func()
|
||||
.ok_or_else(|| anyhow::anyhow!("word {fn_index} is null funcref"))?;
|
||||
func.call(&mut self.store, &[], &mut [])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// -- Host functions --
|
||||
|
||||
fn register_host_func(&mut self, fn_index: u32, f: HostFn) -> anyhow::Result<()> {
|
||||
let mem = self.memory;
|
||||
let tbl = self.table;
|
||||
let dsp = self.dsp;
|
||||
let rsp = self.rsp;
|
||||
let fsp = self.fsp;
|
||||
let func = Func::new(
|
||||
&mut self.store,
|
||||
FuncType::new(&self.engine, [], []),
|
||||
move |mut caller, _params, _results| {
|
||||
let mut ctx = CallerHostAccess {
|
||||
caller: &mut caller,
|
||||
memory: mem,
|
||||
table: tbl,
|
||||
dsp,
|
||||
rsp,
|
||||
fsp,
|
||||
};
|
||||
f(&mut ctx).map_err(|e| wasmtime::Error::msg(e.to_string()))
|
||||
},
|
||||
);
|
||||
self.ensure_table_size(fn_index)?;
|
||||
self.table
|
||||
.set(&mut self.store, fn_index as u64, Ref::Func(Some(func)))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user