mod composition;
mod host;
pub mod interceptor;
mod interface_check;
pub use composition::{BuiltComposition, CompositionBuilder, HostFn};
pub use host::{
AsyncCtx, Ctx, DefaultHostProvider, ErrorHandler, HostFunctionError, HostFunctionErrorKind,
HostFunctionProvider, HostLinkerBuilder, InterfaceBuilder, LinkerError, INPUT_BUFFER_OFFSET,
OUTPUT_BUFFER_CAPACITY, OUTPUT_BUFFER_OFFSET, RESULT_LEN_OFFSET, RESULT_PTR_OFFSET,
};
pub use interceptor::CallInterceptor;
pub use interface_check::{
validate_instance_implements_interface, ExpectedSignature, InterfaceError,
};
use crate::abi::{decode, encode, Value};
use crate::parser::{decode_with_schema, encode_with_schema, Interface};
use crate::types::{Type, TypeDef};
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use thiserror::Error;
use wasmtime::{Config, Engine, Instance as WasmtimeInstance, Linker, Memory, Module, Store};
#[derive(Error, Debug)]
pub enum RuntimeError {
#[error("Module not found: {0}")]
ModuleNotFound(String),
#[error("Function not found: {0}")]
FunctionNotFound(String),
#[error("Type mismatch: {0}")]
TypeMismatch(String),
#[error("WASM execution error: {0}")]
WasmError(String),
#[error("Schema validation error: {0}")]
SchemaError(String),
#[error("ABI error: {0}")]
AbiError(String),
#[error("Memory error: {0}")]
MemoryError(String),
}
#[derive(Clone)]
pub struct HostState {
pub log_messages: Arc<Mutex<Vec<String>>>,
alloc_offset: Arc<Mutex<usize>>,
}
impl Default for HostState {
fn default() -> Self {
Self {
log_messages: Arc::new(Mutex::new(Vec::new())),
alloc_offset: Arc::new(Mutex::new(48 * 1024)),
}
}
}
impl HostState {
pub fn new() -> Self {
Self::default()
}
pub fn get_logs(&self) -> Vec<String> {
self.log_messages.lock().unwrap().clone()
}
pub fn clear_logs(&self) {
self.log_messages.lock().unwrap().clear();
}
}
pub struct HostImports {
state: HostState,
}
impl HostImports {
pub fn new() -> Self {
Self {
state: HostState::new(),
}
}
pub fn state(&self) -> &HostState {
&self.state
}
}
impl Default for HostImports {
fn default() -> Self {
Self::new()
}
}
pub struct Runtime {
engine: Engine,
}
impl Runtime {
pub fn new() -> Self {
Self {
engine: Engine::default(),
}
}
pub fn load_module(&self, wasm_bytes: &[u8]) -> Result<CompiledModule<'_>, RuntimeError> {
let module = Module::new(&self.engine, wasm_bytes)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
Ok(CompiledModule {
module,
engine: &self.engine,
})
}
pub fn decode_arg(
&self,
types: &[TypeDef],
bytes: &[u8],
ty: &Type,
) -> Result<Value, RuntimeError> {
decode_with_schema(types, bytes, ty, None)
.map_err(|err| RuntimeError::SchemaError(err.to_string()))
}
pub fn encode_result(&self, value: &Value) -> Result<Vec<u8>, RuntimeError> {
encode(value).map_err(|err| RuntimeError::AbiError(err.to_string()))
}
pub fn encode_result_with_schema(
&self,
types: &[TypeDef],
value: &Value,
ty: &Type,
) -> Result<Vec<u8>, RuntimeError> {
encode_with_schema(types, value, ty)
.map_err(|err| RuntimeError::SchemaError(err.to_string()))
}
}
impl Default for Runtime {
fn default() -> Self {
Self::new()
}
}
pub struct AsyncRuntime {
engine: Engine,
}
impl AsyncRuntime {
pub fn new() -> Self {
let mut config = Config::new();
config.async_support(true);
config.wasm_multi_memory(true);
let engine = Engine::new(&config).expect("failed to create async engine");
Self { engine }
}
pub fn load_module(&self, wasm_bytes: &[u8]) -> Result<AsyncCompiledModule<'_>, RuntimeError> {
let module = Module::new(&self.engine, wasm_bytes)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
Ok(AsyncCompiledModule {
module,
engine: &self.engine,
})
}
pub fn engine(&self) -> &Engine {
&self.engine
}
}
impl Default for AsyncRuntime {
fn default() -> Self {
Self::new()
}
}
pub struct AsyncCompiledModule<'a> {
module: Module,
engine: &'a Engine,
}
impl AsyncCompiledModule<'_> {
pub async fn instantiate_async(&self) -> Result<AsyncInstance<()>, RuntimeError> {
let mut store = Store::new(self.engine, ());
let linker = Linker::<()>::new(self.engine);
let instance = linker
.instantiate_async(&mut store, &self.module)
.await
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
Ok(AsyncInstance {
store,
instance,
interceptor: None,
})
}
pub async fn instantiate_with_host_async<T, F>(
&self,
state: T,
configure: F,
) -> Result<AsyncInstance<T>, RuntimeError>
where
T: Send + 'static,
F: FnOnce(&mut HostLinkerBuilder<'_, T>) -> Result<(), LinkerError>,
{
self.instantiate_with_host_and_interceptor_async(state, None, configure)
.await
}
pub async fn instantiate_with_host_and_interceptor_async<T, F>(
&self,
state: T,
interceptor: Option<Arc<dyn CallInterceptor>>,
configure: F,
) -> Result<AsyncInstance<T>, RuntimeError>
where
T: Send + 'static,
F: FnOnce(&mut HostLinkerBuilder<'_, T>) -> Result<(), LinkerError>,
{
let mut linker = Linker::new(self.engine);
let mut builder = HostLinkerBuilder::new(self.engine, &mut linker);
if let Some(ref interceptor) = interceptor {
builder.set_interceptor(interceptor.clone());
}
configure(&mut builder).map_err(|e| RuntimeError::WasmError(e.to_string()))?;
let mut store = Store::new(self.engine, state);
let instance = linker
.instantiate_async(&mut store, &self.module)
.await
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
Ok(AsyncInstance {
store,
instance,
interceptor,
})
}
pub fn engine(&self) -> &Engine {
self.engine
}
}
pub struct AsyncInstance<T> {
store: Store<T>,
instance: WasmtimeInstance,
interceptor: Option<Arc<dyn CallInterceptor>>,
}
impl<T: Send> AsyncInstance<T> {
pub fn validate_interface(&mut self, interface: &Interface) -> Result<(), InterfaceError> {
validate_instance_implements_interface(&mut self.store, &self.instance, interface)
}
fn get_memory(&mut self) -> Result<Memory, RuntimeError> {
self.instance
.get_memory(&mut self.store, "memory")
.ok_or_else(|| RuntimeError::MemoryError("no exported memory named 'memory'".into()))
}
pub fn write_memory(&mut self, offset: usize, data: &[u8]) -> Result<(), RuntimeError> {
let memory = self.get_memory()?;
memory
.write(&mut self.store, offset, data)
.map_err(|e| RuntimeError::MemoryError(e.to_string()))
}
pub fn read_memory(&mut self, offset: usize, len: usize) -> Result<Vec<u8>, RuntimeError> {
let memory = self.get_memory()?;
let mut buffer = vec![0u8; len];
memory
.read(&self.store, offset, &mut buffer)
.map_err(|e| RuntimeError::MemoryError(e.to_string()))?;
Ok(buffer)
}
pub fn memory_size(&mut self) -> Result<usize, RuntimeError> {
let memory = self.get_memory()?;
Ok(memory.data_size(&self.store))
}
pub fn write_value(&mut self, offset: usize, value: &Value) -> Result<usize, RuntimeError> {
let bytes = encode(value).map_err(|e| RuntimeError::AbiError(e.to_string()))?;
self.write_memory(offset, &bytes)?;
Ok(bytes.len())
}
pub fn read_value(&mut self, offset: usize, len: usize) -> Result<Value, RuntimeError> {
let bytes = self.read_memory(offset, len)?;
decode(&bytes).map_err(|e| RuntimeError::AbiError(e.to_string()))
}
pub fn set_interceptor(&mut self, interceptor: Arc<dyn CallInterceptor>) {
self.interceptor = Some(interceptor);
}
pub fn interceptor(&self) -> Option<&Arc<dyn CallInterceptor>> {
self.interceptor.as_ref()
}
pub async fn call_with_value_async(
&mut self,
name: &str,
input: &Value,
) -> Result<Value, RuntimeError> {
if let Some(ref interceptor) = self.interceptor {
if let Some(recorded_output) = interceptor.before_export(name, input).await {
interceptor
.after_export(name, input, &recorded_output)
.await;
return Ok(recorded_output);
}
}
let input_bytes = encode(input).map_err(|e| RuntimeError::AbiError(e.to_string()))?;
let (in_ptr, dynamic_input) = match self.call_pack_alloc_async(input_bytes.len()).await {
Ok(ptr) => (ptr, true),
Err(_) => (INPUT_BUFFER_OFFSET, false),
};
let memory = self.get_memory()?;
memory
.write(&mut self.store, in_ptr, &input_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to write input".into()))?;
let func = self
.instance
.get_typed_func::<(i32, i32, i32, i32), i32>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
let status = func
.call_async(
&mut self.store,
(
in_ptr as i32,
input_bytes.len() as i32,
RESULT_PTR_OFFSET as i32,
RESULT_LEN_OFFSET as i32,
),
)
.await
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
if dynamic_input {
self.call_pack_free_async(in_ptr, input_bytes.len())
.await
.ok();
}
let memory = self.get_memory()?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read result ptr".into()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read result len".into()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
if status != 0 {
let mut err_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut err_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read error".into()))?;
self.call_pack_free_async(out_ptr, out_len).await.ok();
let err_msg = String::from_utf8_lossy(&err_bytes).to_string();
return Err(RuntimeError::WasmError(format!(
"function '{}' returned error: {}",
name, err_msg
)));
}
let result = self.read_value(out_ptr, out_len)?;
self.call_pack_free_async(out_ptr, out_len).await.ok();
if let Some(ref interceptor) = self.interceptor {
interceptor.after_export(name, input, &result).await;
}
Ok(result)
}
async fn call_pack_alloc_async(&mut self, size: usize) -> Result<usize, RuntimeError> {
let alloc_func = self
.instance
.get_typed_func::<i32, i32>(&mut self.store, "__pack_alloc")
.map_err(|_| RuntimeError::FunctionNotFound("__pack_alloc not found".into()))?;
let ptr = alloc_func
.call_async(&mut self.store, size as i32)
.await
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
if ptr == 0 {
return Err(RuntimeError::MemoryError("Guest allocation failed".into()));
}
Ok(ptr as usize)
}
async fn call_pack_free_async(&mut self, ptr: usize, len: usize) -> Result<(), RuntimeError> {
if let Ok(free_func) = self
.instance
.get_typed_func::<(i32, i32), ()>(&mut self.store, "__pack_free")
{
free_func
.call_async(&mut self.store, (ptr as i32, len as i32))
.await
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
}
Ok(())
}
pub async fn call_i32_i32_to_i32_async(
&mut self,
name: &str,
a: i32,
b: i32,
) -> Result<i32, RuntimeError> {
let func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
func.call_async(&mut self.store, (a, b))
.await
.map_err(|e| RuntimeError::WasmError(e.to_string()))
}
pub async fn types(
&mut self,
) -> Result<crate::metadata::PackageMetadata, crate::metadata::MetadataError> {
let types_func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, "__pack_types")
.map_err(|_| crate::metadata::MetadataError::NotFound)?;
let status = types_func
.call_async(
&mut self.store,
(RESULT_PTR_OFFSET as i32, RESULT_LEN_OFFSET as i32),
)
.await
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
if status != 0 {
return Err(crate::metadata::MetadataError::CallFailed(
"non-zero status from __pack_types".into(),
));
}
let memory = self
.get_memory()
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
let mut metadata_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut metadata_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
crate::metadata::decode_metadata(&metadata_bytes)
}
pub async fn types_with_hashes(
&mut self,
) -> Result<crate::metadata::MetadataWithHashes, crate::metadata::MetadataError> {
let types_func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, "__pack_types")
.map_err(|_| crate::metadata::MetadataError::NotFound)?;
let status = types_func
.call_async(
&mut self.store,
(RESULT_PTR_OFFSET as i32, RESULT_LEN_OFFSET as i32),
)
.await
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
if status != 0 {
return Err(crate::metadata::MetadataError::CallFailed(
"non-zero status from __pack_types".into(),
));
}
let memory = self
.get_memory()
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
let mut metadata_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut metadata_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
crate::metadata::decode_metadata_with_hashes(&metadata_bytes)
}
}
pub type AsyncHostFnResult<R> = Pin<Box<dyn Future<Output = R> + Send + 'static>>;
pub struct CompiledModule<'a> {
module: Module,
engine: &'a Engine,
}
impl CompiledModule<'_> {
pub fn instantiate(&self) -> Result<Instance<()>, RuntimeError> {
let mut store = Store::new(self.engine, ());
let linker = Linker::<()>::new(self.engine);
let instance = linker
.instantiate(&mut store, &self.module)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
Ok(Instance { store, instance })
}
pub fn instantiate_with_imports(
&self,
imports: HostImports,
) -> Result<InstanceWithHost, RuntimeError> {
let state = imports.state.clone();
let mut linker = Linker::<HostState>::new(self.engine);
let mut builder = HostLinkerBuilder::new(self.engine, &mut linker);
DefaultHostProvider
.register(&mut builder)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
let mut store = Store::new(self.engine, state.clone());
let instance = linker
.instantiate(&mut store, &self.module)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
Ok(InstanceWithHost {
store,
instance,
state,
})
}
pub fn instantiate_with_linker<T: 'static>(
&self,
linker: Linker<T>,
state: T,
) -> Result<Instance<T>, RuntimeError> {
let mut store = Store::new(self.engine, state);
let instance = linker
.instantiate(&mut store, &self.module)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
Ok(Instance { store, instance })
}
pub fn instantiate_with_host<T, F>(
&self,
state: T,
configure: F,
) -> Result<Instance<T>, RuntimeError>
where
T: 'static,
F: FnOnce(&mut HostLinkerBuilder<'_, T>) -> Result<(), LinkerError>,
{
let mut linker = Linker::new(self.engine);
let mut builder = HostLinkerBuilder::new(self.engine, &mut linker);
configure(&mut builder).map_err(|e| RuntimeError::WasmError(e.to_string()))?;
self.instantiate_with_linker(linker, state)
}
pub fn engine(&self) -> &Engine {
self.engine
}
}
pub struct Instance<T> {
store: Store<T>,
instance: WasmtimeInstance,
}
pub struct InstanceWithHost {
store: Store<HostState>,
instance: WasmtimeInstance,
state: HostState,
}
impl InstanceWithHost {
pub fn validate_interface(&mut self, interface: &Interface) -> Result<(), InterfaceError> {
validate_instance_implements_interface(&mut self.store, &self.instance, interface)
}
pub fn host_state(&self) -> &HostState {
&self.state
}
pub fn get_logs(&self) -> Vec<String> {
self.state.get_logs()
}
pub fn clear_logs(&self) {
self.state.clear_logs()
}
fn get_memory(&mut self) -> Result<Memory, RuntimeError> {
self.instance
.get_memory(&mut self.store, "memory")
.ok_or_else(|| RuntimeError::MemoryError("no exported memory named 'memory'".into()))
}
pub fn write_memory(&mut self, offset: usize, data: &[u8]) -> Result<(), RuntimeError> {
let memory = self.get_memory()?;
memory
.write(&mut self.store, offset, data)
.map_err(|e| RuntimeError::MemoryError(e.to_string()))
}
pub fn read_memory(&mut self, offset: usize, len: usize) -> Result<Vec<u8>, RuntimeError> {
let memory = self.get_memory()?;
let mut buffer = vec![0u8; len];
memory
.read(&self.store, offset, &mut buffer)
.map_err(|e| RuntimeError::MemoryError(e.to_string()))?;
Ok(buffer)
}
pub fn memory_size(&mut self) -> Result<usize, RuntimeError> {
let memory = self.get_memory()?;
Ok(memory.data_size(&self.store))
}
pub fn call_i32_i32_to_i32(&mut self, name: &str, a: i32, b: i32) -> Result<i32, RuntimeError> {
let func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
func.call(&mut self.store, (a, b))
.map_err(|e| RuntimeError::WasmError(e.to_string()))
}
pub fn call_i64_i64_to_i64(&mut self, name: &str, a: i64, b: i64) -> Result<i64, RuntimeError> {
let func = self
.instance
.get_typed_func::<(i64, i64), i64>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
func.call(&mut self.store, (a, b))
.map_err(|e| RuntimeError::WasmError(e.to_string()))
}
pub fn call_i32_i32(&mut self, name: &str, a: i32, b: i32) -> Result<(), RuntimeError> {
let func = self
.instance
.get_typed_func::<(i32, i32), ()>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
func.call(&mut self.store, (a, b))
.map_err(|e| RuntimeError::WasmError(e.to_string()))
}
pub fn write_value(&mut self, offset: usize, value: &Value) -> Result<usize, RuntimeError> {
let bytes = encode(value).map_err(|e| RuntimeError::AbiError(e.to_string()))?;
self.write_memory(offset, &bytes)?;
Ok(bytes.len())
}
pub fn read_value(&mut self, offset: usize, len: usize) -> Result<Value, RuntimeError> {
let bytes = self.read_memory(offset, len)?;
decode(&bytes).map_err(|e| RuntimeError::AbiError(e.to_string()))
}
pub fn call_with_value(&mut self, name: &str, input: &Value) -> Result<Value, RuntimeError> {
let input_bytes = encode(input).map_err(|e| RuntimeError::AbiError(e.to_string()))?;
let (in_ptr, dynamic_input) = match self.call_pack_alloc(input_bytes.len()) {
Ok(ptr) => (ptr, true),
Err(_) => (INPUT_BUFFER_OFFSET, false),
};
let memory = self.get_memory()?;
memory
.write(&mut self.store, in_ptr, &input_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to write input".into()))?;
let func = self
.instance
.get_typed_func::<(i32, i32, i32, i32), i32>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
let status = func
.call(
&mut self.store,
(
in_ptr as i32,
input_bytes.len() as i32,
RESULT_PTR_OFFSET as i32,
RESULT_LEN_OFFSET as i32,
),
)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
if dynamic_input {
self.call_pack_free(in_ptr, input_bytes.len()).ok();
}
let memory = self.get_memory()?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read result ptr".into()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read result len".into()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
if status != 0 {
let mut err_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut err_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read error".into()))?;
self.call_pack_free(out_ptr, out_len).ok();
let err_msg = String::from_utf8_lossy(&err_bytes).to_string();
return Err(RuntimeError::WasmError(format!(
"function '{}' returned error: {}",
name, err_msg
)));
}
let result = self.read_value(out_ptr, out_len)?;
self.call_pack_free(out_ptr, out_len).ok();
Ok(result)
}
fn call_pack_alloc(&mut self, size: usize) -> Result<usize, RuntimeError> {
let alloc_func = self
.instance
.get_typed_func::<i32, i32>(&mut self.store, "__pack_alloc")
.map_err(|_| RuntimeError::FunctionNotFound("__pack_alloc not found".into()))?;
let ptr = alloc_func
.call(&mut self.store, size as i32)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
if ptr == 0 {
return Err(RuntimeError::MemoryError("Guest allocation failed".into()));
}
Ok(ptr as usize)
}
fn call_pack_free(&mut self, ptr: usize, len: usize) -> Result<(), RuntimeError> {
if let Ok(free_func) = self
.instance
.get_typed_func::<(i32, i32), ()>(&mut self.store, "__pack_free")
{
free_func
.call(&mut self.store, (ptr as i32, len as i32))
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
}
Ok(())
}
pub fn types(
&mut self,
) -> Result<crate::metadata::PackageMetadata, crate::metadata::MetadataError> {
let types_func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, "__pack_types")
.map_err(|_| crate::metadata::MetadataError::NotFound)?;
let status = types_func
.call(
&mut self.store,
(RESULT_PTR_OFFSET as i32, RESULT_LEN_OFFSET as i32),
)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
if status != 0 {
return Err(crate::metadata::MetadataError::CallFailed(
"non-zero status from __pack_types".into(),
));
}
let memory = self
.get_memory()
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
let mut metadata_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut metadata_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
crate::metadata::decode_metadata(&metadata_bytes)
}
pub fn types_with_hashes(
&mut self,
) -> Result<crate::metadata::MetadataWithHashes, crate::metadata::MetadataError> {
let types_func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, "__pack_types")
.map_err(|_| crate::metadata::MetadataError::NotFound)?;
let status = types_func
.call(
&mut self.store,
(RESULT_PTR_OFFSET as i32, RESULT_LEN_OFFSET as i32),
)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
if status != 0 {
return Err(crate::metadata::MetadataError::CallFailed(
"non-zero status from __pack_types".into(),
));
}
let memory = self
.get_memory()
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
let mut metadata_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut metadata_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
crate::metadata::decode_metadata_with_hashes(&metadata_bytes)
}
}
impl<T> Instance<T> {
pub fn validate_interface(&mut self, interface: &Interface) -> Result<(), InterfaceError> {
validate_instance_implements_interface(&mut self.store, &self.instance, interface)
}
fn get_memory(&mut self) -> Result<Memory, RuntimeError> {
self.instance
.get_memory(&mut self.store, "memory")
.ok_or_else(|| RuntimeError::MemoryError("no exported memory named 'memory'".into()))
}
pub fn write_memory(&mut self, offset: usize, data: &[u8]) -> Result<(), RuntimeError> {
let memory = self.get_memory()?;
memory
.write(&mut self.store, offset, data)
.map_err(|e| RuntimeError::MemoryError(e.to_string()))
}
pub fn read_memory(&mut self, offset: usize, len: usize) -> Result<Vec<u8>, RuntimeError> {
let memory = self.get_memory()?;
let mut buffer = vec![0u8; len];
memory
.read(&self.store, offset, &mut buffer)
.map_err(|e| RuntimeError::MemoryError(e.to_string()))?;
Ok(buffer)
}
pub fn memory_size(&mut self) -> Result<usize, RuntimeError> {
let memory = self.get_memory()?;
Ok(memory.data_size(&self.store))
}
pub fn call_i32_i32_to_i32(&mut self, name: &str, a: i32, b: i32) -> Result<i32, RuntimeError> {
let func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
func.call(&mut self.store, (a, b))
.map_err(|e| RuntimeError::WasmError(e.to_string()))
}
pub fn call_i64_i64_to_i64(&mut self, name: &str, a: i64, b: i64) -> Result<i64, RuntimeError> {
let func = self
.instance
.get_typed_func::<(i64, i64), i64>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
func.call(&mut self.store, (a, b))
.map_err(|e| RuntimeError::WasmError(e.to_string()))
}
pub fn call_i32_i32(&mut self, name: &str, a: i32, b: i32) -> Result<(), RuntimeError> {
let func = self
.instance
.get_typed_func::<(i32, i32), ()>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
func.call(&mut self.store, (a, b))
.map_err(|e| RuntimeError::WasmError(e.to_string()))
}
pub fn write_value(&mut self, offset: usize, value: &Value) -> Result<usize, RuntimeError> {
let bytes = encode(value).map_err(|e| RuntimeError::AbiError(e.to_string()))?;
self.write_memory(offset, &bytes)?;
Ok(bytes.len())
}
pub fn read_value(&mut self, offset: usize, len: usize) -> Result<Value, RuntimeError> {
let bytes = self.read_memory(offset, len)?;
decode(&bytes).map_err(|e| RuntimeError::AbiError(e.to_string()))
}
pub fn call_with_value(&mut self, name: &str, input: &Value) -> Result<Value, RuntimeError> {
let input_bytes = encode(input).map_err(|e| RuntimeError::AbiError(e.to_string()))?;
let (in_ptr, dynamic_input) = match self.call_pack_alloc(input_bytes.len()) {
Ok(ptr) => (ptr, true),
Err(_) => (INPUT_BUFFER_OFFSET, false),
};
let memory = self.get_memory()?;
memory
.write(&mut self.store, in_ptr, &input_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to write input".into()))?;
let func = self
.instance
.get_typed_func::<(i32, i32, i32, i32), i32>(&mut self.store, name)
.map_err(|e| RuntimeError::FunctionNotFound(e.to_string()))?;
let status = func
.call(
&mut self.store,
(
in_ptr as i32,
input_bytes.len() as i32,
RESULT_PTR_OFFSET as i32,
RESULT_LEN_OFFSET as i32,
),
)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
if dynamic_input {
self.call_pack_free(in_ptr, input_bytes.len()).ok();
}
let memory = self.get_memory()?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read result ptr".into()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read result len".into()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
if status != 0 {
let mut err_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut err_bytes)
.map_err(|_| RuntimeError::MemoryError("Failed to read error".into()))?;
self.call_pack_free(out_ptr, out_len).ok();
let err_msg = String::from_utf8_lossy(&err_bytes).to_string();
return Err(RuntimeError::WasmError(format!(
"function '{}' returned error: {}",
name, err_msg
)));
}
let result = self.read_value(out_ptr, out_len)?;
self.call_pack_free(out_ptr, out_len).ok();
Ok(result)
}
fn call_pack_alloc(&mut self, size: usize) -> Result<usize, RuntimeError> {
let alloc_func = self
.instance
.get_typed_func::<i32, i32>(&mut self.store, "__pack_alloc")
.map_err(|_| RuntimeError::FunctionNotFound("__pack_alloc not found".into()))?;
let ptr = alloc_func
.call(&mut self.store, size as i32)
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
if ptr == 0 {
return Err(RuntimeError::MemoryError("Guest allocation failed".into()));
}
Ok(ptr as usize)
}
fn call_pack_free(&mut self, ptr: usize, len: usize) -> Result<(), RuntimeError> {
if let Ok(free_func) = self
.instance
.get_typed_func::<(i32, i32), ()>(&mut self.store, "__pack_free")
{
free_func
.call(&mut self.store, (ptr as i32, len as i32))
.map_err(|e| RuntimeError::WasmError(e.to_string()))?;
}
Ok(())
}
pub fn types(
&mut self,
) -> Result<crate::metadata::PackageMetadata, crate::metadata::MetadataError> {
let types_func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, "__pack_types")
.map_err(|_| crate::metadata::MetadataError::NotFound)?;
let status = types_func
.call(
&mut self.store,
(RESULT_PTR_OFFSET as i32, RESULT_LEN_OFFSET as i32),
)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
if status != 0 {
return Err(crate::metadata::MetadataError::CallFailed(
"non-zero status from __pack_types".into(),
));
}
let memory = self
.get_memory()
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
let mut metadata_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut metadata_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
crate::metadata::decode_metadata(&metadata_bytes)
}
pub fn types_with_hashes(
&mut self,
) -> Result<crate::metadata::MetadataWithHashes, crate::metadata::MetadataError> {
let types_func = self
.instance
.get_typed_func::<(i32, i32), i32>(&mut self.store, "__pack_types")
.map_err(|_| crate::metadata::MetadataError::NotFound)?;
let status = types_func
.call(
&mut self.store,
(RESULT_PTR_OFFSET as i32, RESULT_LEN_OFFSET as i32),
)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
if status != 0 {
return Err(crate::metadata::MetadataError::CallFailed(
"non-zero status from __pack_types".into(),
));
}
let memory = self
.get_memory()
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let mut ptr_bytes = [0u8; 4];
let mut len_bytes = [0u8; 4];
memory
.read(&self.store, RESULT_PTR_OFFSET, &mut ptr_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
memory
.read(&self.store, RESULT_LEN_OFFSET, &mut len_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
let out_ptr = i32::from_le_bytes(ptr_bytes) as usize;
let out_len = i32::from_le_bytes(len_bytes) as usize;
let mut metadata_bytes = vec![0u8; out_len];
memory
.read(&self.store, out_ptr, &mut metadata_bytes)
.map_err(|e| crate::metadata::MetadataError::CallFailed(e.to_string()))?;
crate::metadata::decode_metadata_with_hashes(&metadata_bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abi::Value;
use crate::parser::parse_interface;
use crate::types::Type;
#[test]
fn decode_arg_roundtrip() {
let src = r#"
interface api {
variant node { leaf(s64), list(list<node>) }
}
"#;
let interface = parse_interface(src).expect("parse");
let runtime = Runtime::new();
let value = Value::Variant {
type_name: "node".to_string(),
case_name: "leaf".to_string(),
tag: 0,
payload: vec![Value::S64(7)],
};
let bytes = encode(&value).expect("encode");
let decoded = runtime
.decode_arg(&interface.types, &bytes, &Type::named("node".to_string()))
.expect("decode");
assert_eq!(decoded, value);
}
#[test]
fn decode_arg_rejects_mismatch() {
let src = r#"
interface api {
variant node { leaf(s64), list(list<node>) }
}
"#;
let interface = parse_interface(src).expect("parse");
let runtime = Runtime::new();
let value = Value::String("bad".to_string());
let bytes = encode(&value).expect("encode");
let err = runtime
.decode_arg(&interface.types, &bytes, &Type::named("node".to_string()))
.expect_err("expected error");
match err {
RuntimeError::SchemaError(_) => {}
_ => panic!("unexpected error: {err:?}"),
}
}
#[test]
fn encode_result_rejects_mismatch() {
let src = r#"
interface api {
record config { name: string }
}
"#;
let interface = parse_interface(src).expect("parse");
let runtime = Runtime::new();
let value = Value::Record {
type_name: "config".to_string(),
fields: vec![("wrong".to_string(), Value::String("x".to_string()))],
};
let err = runtime
.encode_result_with_schema(&interface.types, &value, &Type::named("config".to_string()))
.expect_err("expected error");
match err {
RuntimeError::SchemaError(_) => {}
_ => panic!("unexpected error: {err:?}"),
}
}
}