mirror of
https://github.com/fluencelabs/wasmer
synced 2025-06-13 17:11:21 +00:00
Naive short circuiting implementation for user panics and results. (#167)
* Add panic and result catching * exit process on panic and user runtime error * Complete initial implementation
This commit is contained in:
@ -16,7 +16,7 @@ use wasmer_runtime_core::{
|
|||||||
cache::{Cache, Error as CacheError},
|
cache::{Cache, Error as CacheError},
|
||||||
};
|
};
|
||||||
use wasmer_runtime_core::{
|
use wasmer_runtime_core::{
|
||||||
backend::{Backend, FuncResolver, ProtectedCaller, Token},
|
backend::{Backend, FuncResolver, ProtectedCaller, Token, UserTrapper},
|
||||||
error::{CompileResult, RuntimeResult},
|
error::{CompileResult, RuntimeResult},
|
||||||
module::{ModuleInfo, ModuleInner, StringTable},
|
module::{ModuleInfo, ModuleInner, StringTable},
|
||||||
structures::{Map, TypedIndex},
|
structures::{Map, TypedIndex},
|
||||||
@ -51,6 +51,10 @@ impl ProtectedCaller for Placeholder {
|
|||||||
) -> RuntimeResult<Vec<Value>> {
|
) -> RuntimeResult<Vec<Value>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This contains all of the items in a `ModuleInner` except the `func_resolver`.
|
/// This contains all of the items in a `ModuleInner` except the `func_resolver`.
|
||||||
|
@ -2,9 +2,9 @@ use crate::relocation::{TrapData, TrapSink};
|
|||||||
use crate::trampoline::Trampolines;
|
use crate::trampoline::Trampolines;
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use std::sync::Arc;
|
use std::{cell::Cell, sync::Arc};
|
||||||
use wasmer_runtime_core::{
|
use wasmer_runtime_core::{
|
||||||
backend::{ProtectedCaller, Token},
|
backend::{ProtectedCaller, Token, UserTrapper},
|
||||||
error::RuntimeResult,
|
error::RuntimeResult,
|
||||||
export::Context,
|
export::Context,
|
||||||
module::{ExportIndex, ModuleInfo, ModuleInner},
|
module::{ExportIndex, ModuleInfo, ModuleInner},
|
||||||
@ -24,6 +24,19 @@ pub use self::unix::*;
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub use self::windows::*;
|
pub use self::windows::*;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static TRAP_EARLY_DATA: Cell<Option<String>> = Cell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Trapper;
|
||||||
|
|
||||||
|
impl UserTrapper for Trapper {
|
||||||
|
unsafe fn do_early_trap(&self, msg: String) -> ! {
|
||||||
|
TRAP_EARLY_DATA.with(|cell| cell.set(Some(msg)));
|
||||||
|
trigger_trap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Caller {
|
pub struct Caller {
|
||||||
func_export_set: HashSet<FuncIndex>,
|
func_export_set: HashSet<FuncIndex>,
|
||||||
handler_data: HandlerData,
|
handler_data: HandlerData,
|
||||||
@ -118,6 +131,10 @@ impl ProtectedCaller for Caller {
|
|||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
|
||||||
|
Box::new(Trapper)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_func_from_index(
|
fn get_func_from_index(
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
|
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
|
||||||
//! unless you have memory unsafety elsewhere in your code.
|
//! unless you have memory unsafety elsewhere in your code.
|
||||||
//!
|
//!
|
||||||
use crate::relocation::{TrapCode, TrapData, TrapSink};
|
use crate::relocation::{TrapCode, TrapData};
|
||||||
use crate::signal::HandlerData;
|
use crate::signal::HandlerData;
|
||||||
use libc::{c_int, c_void, siginfo_t};
|
use libc::{c_int, c_void, siginfo_t};
|
||||||
use nix::sys::signal::{
|
use nix::sys::signal::{
|
||||||
@ -60,6 +60,12 @@ thread_local! {
|
|||||||
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
|
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe fn trigger_trap() -> ! {
|
||||||
|
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||||
|
|
||||||
|
longjmp(jmp_buf as *mut c_void, 0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> RuntimeResult<T> {
|
pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> RuntimeResult<T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||||
@ -72,54 +78,59 @@ pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R
|
|||||||
let signum = setjmp(jmp_buf as *mut _);
|
let signum = setjmp(jmp_buf as *mut _);
|
||||||
if signum != 0 {
|
if signum != 0 {
|
||||||
*jmp_buf = prev_jmp_buf;
|
*jmp_buf = prev_jmp_buf;
|
||||||
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
|
|
||||||
|
|
||||||
if let Some(TrapData {
|
if let Some(msg) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
|
||||||
trapcode,
|
Err(RuntimeError::User { msg })
|
||||||
srcloc: _,
|
} else {
|
||||||
}) = handler_data.lookup(inst_ptr)
|
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
|
||||||
{
|
|
||||||
Err(match Signal::from_c_int(signum) {
|
if let Some(TrapData {
|
||||||
Ok(SIGILL) => match trapcode {
|
trapcode,
|
||||||
TrapCode::BadSignature => RuntimeError::IndirectCallSignature {
|
srcloc: _,
|
||||||
table: TableIndex::new(0),
|
}) = handler_data.lookup(inst_ptr)
|
||||||
|
{
|
||||||
|
Err(match Signal::from_c_int(signum) {
|
||||||
|
Ok(SIGILL) => match trapcode {
|
||||||
|
TrapCode::BadSignature => RuntimeError::IndirectCallSignature {
|
||||||
|
table: TableIndex::new(0),
|
||||||
|
},
|
||||||
|
TrapCode::IndirectCallToNull => RuntimeError::IndirectCallToNull {
|
||||||
|
table: TableIndex::new(0),
|
||||||
|
},
|
||||||
|
TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess {
|
||||||
|
memory: MemoryIndex::new(0),
|
||||||
|
addr: None,
|
||||||
|
},
|
||||||
|
TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds {
|
||||||
|
table: TableIndex::new(0),
|
||||||
|
},
|
||||||
|
_ => RuntimeError::Unknown {
|
||||||
|
msg: "unknown trap".to_string(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
TrapCode::IndirectCallToNull => RuntimeError::IndirectCallToNull {
|
Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::OutOfBoundsAccess {
|
||||||
table: TableIndex::new(0),
|
|
||||||
},
|
|
||||||
TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess {
|
|
||||||
memory: MemoryIndex::new(0),
|
memory: MemoryIndex::new(0),
|
||||||
addr: None,
|
addr: None,
|
||||||
},
|
},
|
||||||
TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds {
|
Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation,
|
||||||
table: TableIndex::new(0),
|
_ => unimplemented!(),
|
||||||
},
|
}
|
||||||
_ => RuntimeError::Unknown {
|
.into())
|
||||||
msg: "unknown trap".to_string(),
|
} else {
|
||||||
},
|
let signal = match Signal::from_c_int(signum) {
|
||||||
},
|
Ok(SIGFPE) => "floating-point exception",
|
||||||
Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::OutOfBoundsAccess {
|
Ok(SIGILL) => "illegal instruction",
|
||||||
memory: MemoryIndex::new(0),
|
Ok(SIGSEGV) => "segmentation violation",
|
||||||
addr: None,
|
Ok(SIGBUS) => "bus error",
|
||||||
},
|
Err(_) => "error while getting the Signal",
|
||||||
Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation,
|
_ => "unkown trapped signal",
|
||||||
_ => unimplemented!(),
|
};
|
||||||
|
// When the trap-handler is fully implemented, this will return more information.
|
||||||
|
Err(RuntimeError::Unknown {
|
||||||
|
msg: format!("trap at {:p} - {}", faulting_addr, signal),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
.into())
|
|
||||||
} else {
|
|
||||||
let signal = match Signal::from_c_int(signum) {
|
|
||||||
Ok(SIGFPE) => "floating-point exception",
|
|
||||||
Ok(SIGILL) => "illegal instruction",
|
|
||||||
Ok(SIGSEGV) => "segmentation violation",
|
|
||||||
Ok(SIGBUS) => "bus error",
|
|
||||||
Err(_) => "error while getting the Signal",
|
|
||||||
_ => "unkown trapped signal",
|
|
||||||
};
|
|
||||||
// When the trap-handler is fully implemented, this will return more information.
|
|
||||||
Err(RuntimeError::Unknown {
|
|
||||||
msg: format!("trap at {:p} - {}", faulting_addr, signal),
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let ret = f(); // TODO: Switch stack?
|
let ret = f(); // TODO: Switch stack?
|
||||||
|
@ -82,6 +82,12 @@ pub trait ProtectedCaller: Send + Sync {
|
|||||||
vmctx: *mut vm::Ctx,
|
vmctx: *mut vm::Ctx,
|
||||||
_: Token,
|
_: Token,
|
||||||
) -> RuntimeResult<Vec<Value>>;
|
) -> RuntimeResult<Vec<Value>>;
|
||||||
|
|
||||||
|
fn get_early_trapper(&self) -> Box<dyn UserTrapper>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UserTrapper {
|
||||||
|
unsafe fn do_early_trap(&self, msg: String) -> !;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FuncResolver: Send + Sync {
|
pub trait FuncResolver: Send + Sync {
|
||||||
|
@ -97,6 +97,9 @@ pub enum RuntimeError {
|
|||||||
table: TableIndex,
|
table: TableIndex,
|
||||||
},
|
},
|
||||||
IllegalArithmeticOperation,
|
IllegalArithmeticOperation,
|
||||||
|
User {
|
||||||
|
msg: String,
|
||||||
|
},
|
||||||
Unknown {
|
Unknown {
|
||||||
msg: String,
|
msg: String,
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,6 @@ use crate::{
|
|||||||
import::{ImportObject, LikeNamespace},
|
import::{ImportObject, LikeNamespace},
|
||||||
memory::Memory,
|
memory::Memory,
|
||||||
module::{ExportIndex, Module, ModuleInner},
|
module::{ExportIndex, Module, ModuleInner},
|
||||||
sig_registry::SigRegistry,
|
|
||||||
table::Table,
|
table::Table,
|
||||||
typed_func::{Func, Safe, WasmTypeList},
|
typed_func::{Func, Safe, WasmTypeList},
|
||||||
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value},
|
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value},
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
error::Result,
|
error::Result,
|
||||||
import::ImportObject,
|
import::ImportObject,
|
||||||
structures::{Map, TypedIndex},
|
structures::{Map, TypedIndex},
|
||||||
|
typed_func::EARLY_TRAPPER,
|
||||||
types::{
|
types::{
|
||||||
FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex,
|
FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex,
|
||||||
ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer,
|
ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer,
|
||||||
@ -63,6 +64,10 @@ pub struct Module(#[doc(hidden)] pub Arc<ModuleInner>);
|
|||||||
|
|
||||||
impl Module {
|
impl Module {
|
||||||
pub(crate) fn new(inner: Arc<ModuleInner>) -> Self {
|
pub(crate) fn new(inner: Arc<ModuleInner>) -> Self {
|
||||||
|
unsafe {
|
||||||
|
EARLY_TRAPPER
|
||||||
|
.with(|ucell| *ucell.get() = Some(inner.protected_caller.get_early_trapper()));
|
||||||
|
}
|
||||||
Module(inner)
|
Module(inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
backend::UserTrapper,
|
||||||
error::RuntimeError,
|
error::RuntimeError,
|
||||||
export::{Context, Export, FuncPointer},
|
export::{Context, Export, FuncPointer},
|
||||||
import::IsExport,
|
import::IsExport,
|
||||||
types::{FuncSig, Type, WasmExternType},
|
types::{FuncSig, Type, WasmExternType},
|
||||||
vm::Ctx,
|
vm::Ctx,
|
||||||
};
|
};
|
||||||
use std::{marker::PhantomData, mem, ptr, sync::Arc};
|
use std::{cell::UnsafeCell, fmt, marker::PhantomData, mem, panic, ptr, sync::Arc};
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static EARLY_TRAPPER: UnsafeCell<Option<Box<dyn UserTrapper>>> = UnsafeCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Safeness {}
|
pub trait Safeness {}
|
||||||
pub struct Safe;
|
pub struct Safe;
|
||||||
@ -28,9 +33,44 @@ where
|
|||||||
Args: WasmTypeList,
|
Args: WasmTypeList,
|
||||||
Rets: WasmTypeList,
|
Rets: WasmTypeList,
|
||||||
{
|
{
|
||||||
fn to_raw(self) -> *const ();
|
fn to_raw(&self) -> *const ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait TrapEarly<Rets>
|
||||||
|
where
|
||||||
|
Rets: WasmTypeList,
|
||||||
|
{
|
||||||
|
fn report(self) -> Result<Rets, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Rets> TrapEarly<Rets> for Rets
|
||||||
|
where
|
||||||
|
Rets: WasmTypeList,
|
||||||
|
{
|
||||||
|
fn report(self) -> Result<Rets, String> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Rets, E> TrapEarly<Rets> for Result<Rets, E>
|
||||||
|
where
|
||||||
|
Rets: WasmTypeList,
|
||||||
|
E: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn report(self) -> Result<Rets, String> {
|
||||||
|
self.map_err(|err| format!("Error: {:?}", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn Func<'a, Args, Rets, F>(f: F) -> Func<'a, Args, Rets, Unsafe>
|
||||||
|
// where
|
||||||
|
// Args: WasmTypeList,
|
||||||
|
// Rets: WasmTypeList,
|
||||||
|
// F: ExternalFunction<Args, Rets>
|
||||||
|
// {
|
||||||
|
// Func::new(f)
|
||||||
|
// }
|
||||||
|
|
||||||
pub struct Func<'a, Args = (), Rets = (), Safety: Safeness = Safe> {
|
pub struct Func<'a, Args = (), Rets = (), Safety: Safeness = Safe> {
|
||||||
f: *const (),
|
f: *const (),
|
||||||
ctx: *mut Ctx,
|
ctx: *mut Ctx,
|
||||||
@ -143,18 +183,41 @@ macro_rules! impl_traits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl< $( $x: WasmExternType, )* Rets: WasmTypeList, FN: Fn( $( $x, )* &mut Ctx) -> Rets> ExternalFunction<($( $x ),*), Rets> for FN {
|
impl< $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( $( $x, )* &mut Ctx) -> Trap> ExternalFunction<($( $x ),*), Rets> for FN {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn to_raw(self) -> *const () {
|
fn to_raw(&self) -> *const () {
|
||||||
assert_eq!(mem::size_of::<Self>(), 0, "you cannot use a closure that captures state for `Func`.");
|
assert_eq!(mem::size_of::<Self>(), 0, "you cannot use a closure that captures state for `Func`.");
|
||||||
|
|
||||||
extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, FN: Fn( $( $x, )* &mut Ctx) -> Rets>( $( $x: $x, )* ctx: &mut Ctx) -> Rets::CStruct {
|
extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( $( $x, )* &mut Ctx) -> Trap>( $( $x: $x, )* ctx: &mut Ctx) -> Rets::CStruct {
|
||||||
let f: FN = unsafe { mem::transmute_copy(&()) };
|
let f: FN = unsafe { mem::transmute_copy(&()) };
|
||||||
let rets = f( $( $x, )* ctx);
|
|
||||||
rets.into_c_struct()
|
let msg = match panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||||
|
f( $( $x, )* ctx).report()
|
||||||
|
})) {
|
||||||
|
Ok(Ok(returns)) => return returns.into_c_struct(),
|
||||||
|
Ok(Err(err)) => err,
|
||||||
|
Err(err) => {
|
||||||
|
if let Some(s) = err.downcast_ref::<&str>() {
|
||||||
|
s.to_string()
|
||||||
|
} else if let Some(s) = err.downcast_ref::<String>() {
|
||||||
|
s.clone()
|
||||||
|
} else {
|
||||||
|
"a panic occurred, but no additional information is available".to_string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if let Some(early_trapper) = &*EARLY_TRAPPER.with(|ucell| ucell.get()) {
|
||||||
|
early_trapper.do_early_trap(msg)
|
||||||
|
} else {
|
||||||
|
eprintln!("panic handling not setup");
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap::<$( $x, )* Rets, Self> as *const ()
|
wrap::<$( $x, )* Rets, Trap, Self> as *const ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,7 +493,7 @@ mod vm_ctx_tests {
|
|||||||
|
|
||||||
fn generate_module() -> ModuleInner {
|
fn generate_module() -> ModuleInner {
|
||||||
use super::Func;
|
use super::Func;
|
||||||
use crate::backend::{Backend, FuncResolver, ProtectedCaller, Token};
|
use crate::backend::{Backend, FuncResolver, ProtectedCaller, Token, UserTrapper};
|
||||||
use crate::error::RuntimeResult;
|
use crate::error::RuntimeResult;
|
||||||
use crate::types::{FuncIndex, LocalFuncIndex, Value};
|
use crate::types::{FuncIndex, LocalFuncIndex, Value};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@ -520,6 +520,9 @@ mod vm_ctx_tests {
|
|||||||
) -> RuntimeResult<Vec<Value>> {
|
) -> RuntimeResult<Vec<Value>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleInner {
|
ModuleInner {
|
||||||
|
@ -3,7 +3,7 @@ use wabt::wat2wasm;
|
|||||||
use wasmer_clif_backend::CraneliftCompiler;
|
use wasmer_clif_backend::CraneliftCompiler;
|
||||||
use wasmer_runtime_core::{
|
use wasmer_runtime_core::{
|
||||||
cache::Cache,
|
cache::Cache,
|
||||||
error::Result,
|
error,
|
||||||
global::Global,
|
global::Global,
|
||||||
memory::Memory,
|
memory::Memory,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -14,7 +14,7 @@ use wasmer_runtime_core::{
|
|||||||
|
|
||||||
static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm");
|
static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm");
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> error::Result<()> {
|
||||||
let compiler = CraneliftCompiler::new();
|
let compiler = CraneliftCompiler::new();
|
||||||
let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed");
|
let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed");
|
||||||
|
|
||||||
@ -61,14 +61,14 @@ fn main() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_num(n: i32, ctx: &mut vm::Ctx) -> i32 {
|
fn print_num(n: i32, ctx: &mut vm::Ctx) -> Result<i32, ()> {
|
||||||
println!("print_num({})", n);
|
println!("print_num({})", n);
|
||||||
|
|
||||||
let memory: &Memory = ctx.memory(0);
|
let memory: &Memory = ctx.memory(0);
|
||||||
|
|
||||||
let a: i32 = memory.view()[0].get();
|
let a: i32 = memory.view()[0].get();
|
||||||
|
|
||||||
a + n + 1
|
Ok(a + n + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
static IMPORT_MODULE: &str = r#"
|
static IMPORT_MODULE: &str = r#"
|
||||||
|
Reference in New Issue
Block a user