mirror of
https://github.com/fluencelabs/wasmer
synced 2025-06-22 05:01:33 +00:00
merge vm_refactor_trap_handling into vm_refactor
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -1114,6 +1114,8 @@ dependencies = [
|
|||||||
"cranelift-native 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cranelift-native 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"cranelift-wasm 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cranelift-wasm 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hashbrown 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hashbrown 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libffi 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"target-lexicon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"target-lexicon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"wasmer-runtime 0.1.0",
|
"wasmer-runtime 0.1.0",
|
||||||
"wasmparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"wasmparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -1140,7 +1142,6 @@ dependencies = [
|
|||||||
"errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"field-offset 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"field-offset 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hashbrown 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hashbrown 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libffi 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"page_size 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"page_size 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"wabt 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"wabt 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -14,3 +14,8 @@ hashbrown = "0.1"
|
|||||||
target-lexicon = "0.2.0"
|
target-lexicon = "0.2.0"
|
||||||
wasmparser = "0.23.0"
|
wasmparser = "0.23.0"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
|
nix = "0.12.0"
|
||||||
|
# We depend on libffi for now.
|
||||||
|
# This will be removed asap.
|
||||||
|
libffi = "0.6.4"
|
||||||
|
|
||||||
|
138
lib/clif-backend/src/call/mod.rs
Normal file
138
lib/clif-backend/src/call/mod.rs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
mod recovery;
|
||||||
|
mod sighandler;
|
||||||
|
|
||||||
|
pub use self::recovery::HandlerData;
|
||||||
|
|
||||||
|
use crate::call::recovery::call_protected;
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
use libffi::high::{arg as libffi_arg, call as libffi_call, CodePtr};
|
||||||
|
use std::iter;
|
||||||
|
use wasmer_runtime::{
|
||||||
|
backend::{ProtectedCaller, Token},
|
||||||
|
error::RuntimeResult,
|
||||||
|
export::Context,
|
||||||
|
module::{ExportIndex, ModuleInner},
|
||||||
|
types::{FuncIndex, FuncSig, LocalOrImport, Type, Value},
|
||||||
|
vm::{self, ImportBacking},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Caller {
|
||||||
|
func_export_set: HashSet<FuncIndex>,
|
||||||
|
handler_data: HandlerData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Caller {
|
||||||
|
pub fn new(module: &ModuleInner, handler_data: HandlerData) -> Self {
|
||||||
|
let mut func_export_set = HashSet::new();
|
||||||
|
for export_index in module.exports.values() {
|
||||||
|
if let ExportIndex::Func(func_index) = export_index {
|
||||||
|
func_export_set.insert(*func_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(start_func_index) = module.start_func {
|
||||||
|
func_export_set.insert(start_func_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
func_export_set,
|
||||||
|
handler_data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtectedCaller for Caller {
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
module: &ModuleInner,
|
||||||
|
func_index: FuncIndex,
|
||||||
|
params: &[Value],
|
||||||
|
returns: &mut [Value],
|
||||||
|
import_backing: &ImportBacking,
|
||||||
|
vmctx: *mut vm::Ctx,
|
||||||
|
_: Token,
|
||||||
|
) -> RuntimeResult<()> {
|
||||||
|
let (func_ptr, ctx, signature) = get_func_from_index(&module, import_backing, func_index);
|
||||||
|
|
||||||
|
let vmctx_ptr = match ctx {
|
||||||
|
Context::External(external_vmctx) => external_vmctx,
|
||||||
|
Context::Internal => vmctx,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(self.func_export_set.contains(&func_index));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
returns.len() == signature.returns.len() && signature.returns.len() <= 1,
|
||||||
|
"multi-value returns not yet supported"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(signature.check_sig(params), "incorrect signature");
|
||||||
|
|
||||||
|
let libffi_args: Vec<_> = params
|
||||||
|
.iter()
|
||||||
|
.map(|val| match val {
|
||||||
|
Value::I32(ref x) => libffi_arg(x),
|
||||||
|
Value::I64(ref x) => libffi_arg(x),
|
||||||
|
Value::F32(ref x) => libffi_arg(x),
|
||||||
|
Value::F64(ref x) => libffi_arg(x),
|
||||||
|
})
|
||||||
|
.chain(iter::once(libffi_arg(&vmctx_ptr)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let code_ptr = CodePtr::from_ptr(func_ptr as _);
|
||||||
|
|
||||||
|
call_protected(&self.handler_data, || {
|
||||||
|
// Only supports zero or one return values for now.
|
||||||
|
// To support multiple returns, we will have to
|
||||||
|
// generate trampolines instead of using libffi.
|
||||||
|
match signature.returns.first() {
|
||||||
|
Some(ty) => {
|
||||||
|
let val = match ty {
|
||||||
|
Type::I32 => Value::I32(unsafe { libffi_call(code_ptr, &libffi_args) }),
|
||||||
|
Type::I64 => Value::I64(unsafe { libffi_call(code_ptr, &libffi_args) }),
|
||||||
|
Type::F32 => Value::F32(unsafe { libffi_call(code_ptr, &libffi_args) }),
|
||||||
|
Type::F64 => Value::F64(unsafe { libffi_call(code_ptr, &libffi_args) }),
|
||||||
|
};
|
||||||
|
returns[0] = val;
|
||||||
|
}
|
||||||
|
// call with no returns
|
||||||
|
None => unsafe {
|
||||||
|
libffi_call::<()>(code_ptr, &libffi_args);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_func_from_index<'a>(
|
||||||
|
module: &'a ModuleInner,
|
||||||
|
import_backing: &ImportBacking,
|
||||||
|
func_index: FuncIndex,
|
||||||
|
) -> (*const vm::Func, Context, &'a FuncSig) {
|
||||||
|
let sig_index = *module
|
||||||
|
.func_assoc
|
||||||
|
.get(func_index)
|
||||||
|
.expect("broken invariant, incorrect func index");
|
||||||
|
|
||||||
|
let (func_ptr, ctx) = match func_index.local_or_import(module) {
|
||||||
|
LocalOrImport::Local(local_func_index) => (
|
||||||
|
module
|
||||||
|
.func_resolver
|
||||||
|
.get(&module, local_func_index)
|
||||||
|
.expect("broken invariant, func resolver not synced with module.exports")
|
||||||
|
.cast()
|
||||||
|
.as_ptr() as *const _,
|
||||||
|
Context::Internal,
|
||||||
|
),
|
||||||
|
LocalOrImport::Import(imported_func_index) => {
|
||||||
|
let imported_func = import_backing.imported_func(imported_func_index);
|
||||||
|
(
|
||||||
|
imported_func.func as *const _,
|
||||||
|
Context::External(imported_func.vmctx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let signature = module.sig_registry.lookup_func_sig(sig_index);
|
||||||
|
|
||||||
|
(func_ptr, ctx, signature)
|
||||||
|
}
|
189
lib/clif-backend/src/call/recovery.rs
Normal file
189
lib/clif-backend/src/call/recovery.rs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
//! When a WebAssembly module triggers any traps, we perform recovery here.
|
||||||
|
//!
|
||||||
|
//! This module uses TLS (thread-local storage) to track recovery information. Since the four signals we're handling
|
||||||
|
//! 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.
|
||||||
|
|
||||||
|
use crate::call::sighandler::install_sighandler;
|
||||||
|
use crate::relocation::{TrapData, TrapSink};
|
||||||
|
use cranelift_codegen::ir::TrapCode;
|
||||||
|
use nix::libc::{c_void, siginfo_t};
|
||||||
|
use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV};
|
||||||
|
use std::cell::{Cell, UnsafeCell};
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::Once;
|
||||||
|
use wasmer_runtime::{
|
||||||
|
error::{RuntimeError, RuntimeResult},
|
||||||
|
structures::TypedIndex,
|
||||||
|
types::{MemoryIndex, TableIndex},
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
pub fn setjmp(env: *mut ::nix::libc::c_void) -> ::nix::libc::c_int;
|
||||||
|
fn longjmp(env: *mut ::nix::libc::c_void, val: ::nix::libc::c_int) -> !;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SETJMP_BUFFER_LEN: usize = 27;
|
||||||
|
pub static SIGHANDLER_INIT: Once = Once::new();
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static SETJMP_BUFFER: UnsafeCell<[::nix::libc::c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]);
|
||||||
|
pub static CAUGHT_ADDRESSES: Cell<(*const c_void, *const c_void)> = Cell::new((ptr::null(), ptr::null()));
|
||||||
|
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HandlerData {
|
||||||
|
trap_data: TrapSink,
|
||||||
|
buffer_ptr: *const c_void,
|
||||||
|
buffer_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandlerData {
|
||||||
|
pub fn new(trap_data: TrapSink, buffer_ptr: *const c_void, buffer_size: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
trap_data,
|
||||||
|
buffer_ptr,
|
||||||
|
buffer_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup(&self, ip: *const c_void) -> Option<TrapData> {
|
||||||
|
let ip = ip as usize;
|
||||||
|
let buffer_ptr = self.buffer_ptr as usize;
|
||||||
|
|
||||||
|
if buffer_ptr <= ip && ip < buffer_ptr + self.buffer_size {
|
||||||
|
let offset = ip - buffer_ptr;
|
||||||
|
self.trap_data.lookup(offset)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> RuntimeResult<T> {
|
||||||
|
unsafe {
|
||||||
|
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||||
|
let prev_jmp_buf = *jmp_buf;
|
||||||
|
|
||||||
|
SIGHANDLER_INIT.call_once(|| {
|
||||||
|
install_sighandler();
|
||||||
|
});
|
||||||
|
|
||||||
|
let signum = setjmp(jmp_buf as *mut ::nix::libc::c_void);
|
||||||
|
if signum != 0 {
|
||||||
|
*jmp_buf = prev_jmp_buf;
|
||||||
|
let (faulting_addr, _) = CAUGHT_ADDRESSES.with(|cell| cell.get());
|
||||||
|
|
||||||
|
if let Some(TrapData {
|
||||||
|
trapcode,
|
||||||
|
srcloc: _,
|
||||||
|
}) = handler_data.lookup(faulting_addr)
|
||||||
|
{
|
||||||
|
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 => {
|
||||||
|
let addr =
|
||||||
|
(faulting_addr as usize) - (handler_data.buffer_ptr as usize);
|
||||||
|
if addr <= handler_data.buffer_size {
|
||||||
|
// in the memory
|
||||||
|
RuntimeError::OutOfBoundsAccess {
|
||||||
|
memory: MemoryIndex::new(0),
|
||||||
|
addr: addr as u32,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if there's an invalid access outside of the memory, including guard pages
|
||||||
|
// just kill the process.
|
||||||
|
panic!("invalid memory access, way out of bounds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds {
|
||||||
|
table: TableIndex::new(0),
|
||||||
|
},
|
||||||
|
_ => RuntimeError::Unknown {
|
||||||
|
msg: "unknown trap".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ok(SIGSEGV) | Ok(SIGBUS) => {
|
||||||
|
let addr = (faulting_addr as usize) - (handler_data.buffer_ptr as usize);
|
||||||
|
if addr <= handler_data.buffer_size {
|
||||||
|
// in the memory
|
||||||
|
RuntimeError::OutOfBoundsAccess {
|
||||||
|
memory: MemoryIndex::new(0),
|
||||||
|
addr: addr as u32,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if there's an invalid access outside of the memory, including guard pages
|
||||||
|
// just kill the process.
|
||||||
|
panic!("invalid memory access, way out of bounds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation,
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
.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 {
|
||||||
|
let ret = f(); // TODO: Switch stack?
|
||||||
|
*jmp_buf = prev_jmp_buf;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwinds to last protected_call.
|
||||||
|
pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t, ucontext: *const c_void) -> ! {
|
||||||
|
// Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.)
|
||||||
|
// itself, accessing TLS here is safe. In case any other code calls this, it often indicates a memory safety bug and you should
|
||||||
|
// temporarily disable the signal handlers to debug it.
|
||||||
|
|
||||||
|
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
||||||
|
if *jmp_buf == [0; SETJMP_BUFFER_LEN] {
|
||||||
|
::std::process::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
CAUGHT_ADDRESSES.with(|cell| cell.set(get_faulting_addr_and_ip(siginfo, ucontext)));
|
||||||
|
|
||||||
|
longjmp(jmp_buf as *mut ::nix::libc::c_void, signum)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||||
|
unsafe fn get_faulting_addr_and_ip(
|
||||||
|
siginfo: *mut siginfo_t,
|
||||||
|
_ucontext: *const c_void,
|
||||||
|
) -> (*const c_void, *const c_void) {
|
||||||
|
(ptr::null(), ptr::null())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
|
||||||
|
unsafe fn get_faulting_addr_and_ip(
|
||||||
|
siginfo: *mut siginfo_t,
|
||||||
|
_ucontext: *const c_void,
|
||||||
|
) -> (*const c_void, *const c_void) {
|
||||||
|
((*siginfo).si_addr, ptr::null())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
all(target_os = "macos", target_arch = "x86_64"),
|
||||||
|
all(target_os = "linux", target_arch = "x86_64"),
|
||||||
|
)))]
|
||||||
|
compile_error!("This crate doesn't yet support compiling on operating systems other than linux and macos and architectures other than x86_64");
|
@ -1,10 +1,8 @@
|
|||||||
//! We install signal handlers to handle WebAssembly traps within
|
//! Installing signal handlers allows us to handle traps and out-of-bounds memory
|
||||||
//! our Rust code. Otherwise we will have errors that stop the Rust process
|
//! accesses that occur when runniing webassembly.
|
||||||
//! such as `process didn't exit successfully: ... (signal: 8, SIGFPE: erroneous arithmetic operation)`
|
|
||||||
//!
|
//!
|
||||||
//! Please read more about this here: https://github.com/CraneStation/wasmtime/issues/15
|
|
||||||
//! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622
|
//! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622
|
||||||
use crate::recovery;
|
use crate::call::recovery;
|
||||||
use nix::libc::{c_void, siginfo_t};
|
use nix::libc::{c_void, siginfo_t};
|
||||||
use nix::sys::signal::{
|
use nix::sys::signal::{
|
||||||
sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
|
sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
|
||||||
@ -25,9 +23,9 @@ pub unsafe fn install_sighandler() {
|
|||||||
extern "C" fn signal_trap_handler(
|
extern "C" fn signal_trap_handler(
|
||||||
signum: ::nix::libc::c_int,
|
signum: ::nix::libc::c_int,
|
||||||
siginfo: *mut siginfo_t,
|
siginfo: *mut siginfo_t,
|
||||||
_ucontext: *mut c_void,
|
ucontext: *mut c_void,
|
||||||
) {
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
recovery::do_unwind(signum, siginfo);
|
recovery::do_unwind(signum, siginfo, ucontext);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
// pub mod codegen;
|
||||||
|
mod call;
|
||||||
mod func_env;
|
mod func_env;
|
||||||
mod libcalls;
|
mod libcalls;
|
||||||
mod module;
|
mod module;
|
||||||
@ -11,7 +13,7 @@ use cranelift_codegen::{
|
|||||||
};
|
};
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
use wasmer_runtime::{
|
use wasmer_runtime::{
|
||||||
backend::Compiler,
|
backend::{Compiler, Token},
|
||||||
error::{CompileError, CompileResult},
|
error::{CompileError, CompileResult},
|
||||||
module::ModuleInner,
|
module::ModuleInner,
|
||||||
};
|
};
|
||||||
@ -27,7 +29,7 @@ impl CraneliftCompiler {
|
|||||||
|
|
||||||
impl Compiler for CraneliftCompiler {
|
impl Compiler for CraneliftCompiler {
|
||||||
// Compiles wasm binary to a wasmer module.
|
// Compiles wasm binary to a wasmer module.
|
||||||
fn compile(&self, wasm: &[u8]) -> CompileResult<ModuleInner> {
|
fn compile(&self, wasm: &[u8], _: Token) -> CompileResult<ModuleInner> {
|
||||||
validate(wasm)?;
|
validate(wasm)?;
|
||||||
|
|
||||||
let isa = get_isa();
|
let isa = get_isa();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::resolver::FuncResolverBuilder;
|
use crate::{call::Caller, resolver::FuncResolverBuilder};
|
||||||
use cranelift_codegen::{ir, isa};
|
use cranelift_codegen::{ir, isa};
|
||||||
use cranelift_entity::EntityRef;
|
use cranelift_entity::EntityRef;
|
||||||
use cranelift_wasm;
|
use cranelift_wasm;
|
||||||
@ -8,20 +8,21 @@ use std::{
|
|||||||
ptr::NonNull,
|
ptr::NonNull,
|
||||||
};
|
};
|
||||||
use wasmer_runtime::{
|
use wasmer_runtime::{
|
||||||
backend::FuncResolver,
|
|
||||||
backend::SigRegistry,
|
backend::SigRegistry,
|
||||||
error::CompileResult,
|
backend::{FuncResolver, ProtectedCaller, Token},
|
||||||
|
error::{CompileResult, RuntimeResult},
|
||||||
module::ModuleInner,
|
module::ModuleInner,
|
||||||
structures::{Map, TypedIndex},
|
structures::{Map, TypedIndex},
|
||||||
types::{
|
types::{
|
||||||
FuncIndex, FuncSig, GlobalIndex, LocalFuncIndex, MemoryIndex, SigIndex, TableIndex, Type,
|
FuncIndex, FuncSig, GlobalIndex, LocalFuncIndex, MemoryIndex, SigIndex, TableIndex, Type,
|
||||||
|
Value,
|
||||||
},
|
},
|
||||||
vm,
|
vm::{self, ImportBacking},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PlaceholderFuncResolver;
|
struct Placeholder;
|
||||||
|
|
||||||
impl FuncResolver for PlaceholderFuncResolver {
|
impl FuncResolver for Placeholder {
|
||||||
fn get(
|
fn get(
|
||||||
&self,
|
&self,
|
||||||
_module: &ModuleInner,
|
_module: &ModuleInner,
|
||||||
@ -31,6 +32,21 @@ impl FuncResolver for PlaceholderFuncResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ProtectedCaller for Placeholder {
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
_module: &ModuleInner,
|
||||||
|
_func_index: FuncIndex,
|
||||||
|
_params: &[Value],
|
||||||
|
_returns: &mut [Value],
|
||||||
|
_import_backing: &ImportBacking,
|
||||||
|
_vmctx: *mut vm::Ctx,
|
||||||
|
_: Token,
|
||||||
|
) -> RuntimeResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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`.
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
pub module: ModuleInner,
|
pub module: ModuleInner,
|
||||||
@ -41,7 +57,9 @@ impl Module {
|
|||||||
Self {
|
Self {
|
||||||
module: ModuleInner {
|
module: ModuleInner {
|
||||||
// this is a placeholder
|
// this is a placeholder
|
||||||
func_resolver: Box::new(PlaceholderFuncResolver),
|
func_resolver: Box::new(Placeholder),
|
||||||
|
protected_caller: Box::new(Placeholder),
|
||||||
|
|
||||||
memories: Map::new(),
|
memories: Map::new(),
|
||||||
globals: Map::new(),
|
globals: Map::new(),
|
||||||
tables: Map::new(),
|
tables: Map::new(),
|
||||||
@ -76,8 +94,11 @@ impl Module {
|
|||||||
*sig_index = sig_registry.lookup_deduplicated_sigindex(*sig_index);
|
*sig_index = sig_registry.lookup_deduplicated_sigindex(*sig_index);
|
||||||
});
|
});
|
||||||
|
|
||||||
let func_resolver_builder = FuncResolverBuilder::new(isa, functions)?;
|
let (func_resolver_builder, handler_data) = FuncResolverBuilder::new(isa, functions)?;
|
||||||
self.module.func_resolver = Box::new(func_resolver_builder.finalize()?);
|
self.module.func_resolver = Box::new(func_resolver_builder.finalize()?);
|
||||||
|
|
||||||
|
self.module.protected_caller = Box::new(Caller::new(&self.module, handler_data));
|
||||||
|
|
||||||
Ok(self.module)
|
Ok(self.module)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
//! any other calls that this function is doing, so we can "patch" the
|
//! any other calls that this function is doing, so we can "patch" the
|
||||||
//! function addrs in runtime with the functions we need.
|
//! function addrs in runtime with the functions we need.
|
||||||
use cranelift_codegen::binemit;
|
use cranelift_codegen::binemit;
|
||||||
use cranelift_codegen::ir::{self, ExternalName, LibCall, SourceLoc, TrapCode};
|
|
||||||
use wasmer_runtime::{structures::TypedIndex, types::LocalFuncIndex};
|
|
||||||
|
|
||||||
pub use cranelift_codegen::binemit::Reloc;
|
pub use cranelift_codegen::binemit::Reloc;
|
||||||
|
use cranelift_codegen::ir::{self, ExternalName, LibCall, SourceLoc, TrapCode};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use wasmer_runtime::{structures::TypedIndex, types::LocalFuncIndex};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Relocation {
|
pub struct Relocation {
|
||||||
@ -133,30 +133,50 @@ impl RelocSink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct TrapData {
|
pub struct TrapData {
|
||||||
pub offset: usize,
|
pub trapcode: TrapCode,
|
||||||
pub code: TrapCode,
|
pub srcloc: SourceLoc,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simple implementation of a TrapSink
|
/// Simple implementation of a TrapSink
|
||||||
/// that saves the info for later.
|
/// that saves the info for later.
|
||||||
pub struct TrapSink {
|
pub struct TrapSink {
|
||||||
trap_datas: Vec<TrapData>,
|
trap_datas: HashMap<usize, TrapData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrapSink {
|
impl TrapSink {
|
||||||
pub fn new() -> TrapSink {
|
pub fn new() -> TrapSink {
|
||||||
TrapSink {
|
TrapSink {
|
||||||
trap_datas: Vec::new(),
|
trap_datas: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl binemit::TrapSink for TrapSink {
|
pub fn lookup(&self, offset: usize) -> Option<TrapData> {
|
||||||
fn trap(&mut self, offset: u32, _: SourceLoc, code: TrapCode) {
|
self.trap_datas.get(&offset).cloned()
|
||||||
self.trap_datas.push(TrapData {
|
}
|
||||||
offset: offset as usize,
|
|
||||||
code,
|
pub fn drain_local(&mut self, current_func_offset: usize, local: &mut LocalTrapSink) {
|
||||||
|
local.trap_datas.drain(..).for_each(|(offset, trap_data)| {
|
||||||
|
self.trap_datas
|
||||||
|
.insert(current_func_offset + offset, trap_data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct LocalTrapSink {
|
||||||
|
trap_datas: Vec<(usize, TrapData)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalTrapSink {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
LocalTrapSink { trap_datas: vec![] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl binemit::TrapSink for LocalTrapSink {
|
||||||
|
fn trap(&mut self, offset: u32, srcloc: SourceLoc, trapcode: TrapCode) {
|
||||||
|
self.trap_datas
|
||||||
|
.push((offset as usize, TrapData { trapcode, srcloc }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
use crate::call::HandlerData;
|
||||||
use crate::libcalls;
|
use crate::libcalls;
|
||||||
use crate::relocation::{Reloc, RelocSink, Relocation, RelocationType, TrapSink, VmCall};
|
use crate::relocation::{
|
||||||
|
LocalTrapSink, Reloc, RelocSink, Relocation, RelocationType, TrapSink, VmCall,
|
||||||
|
};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use cranelift_codegen::{ir, isa, Context};
|
use cranelift_codegen::{ir, isa, Context};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
@ -20,17 +23,18 @@ use wasmer_runtime::{
|
|||||||
pub struct FuncResolverBuilder {
|
pub struct FuncResolverBuilder {
|
||||||
resolver: FuncResolver,
|
resolver: FuncResolver,
|
||||||
relocations: Map<LocalFuncIndex, Vec<Relocation>>,
|
relocations: Map<LocalFuncIndex, Vec<Relocation>>,
|
||||||
trap_sinks: Map<LocalFuncIndex, TrapSink>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncResolverBuilder {
|
impl FuncResolverBuilder {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
isa: &isa::TargetIsa,
|
isa: &isa::TargetIsa,
|
||||||
function_bodies: Map<LocalFuncIndex, ir::Function>,
|
function_bodies: Map<LocalFuncIndex, ir::Function>,
|
||||||
) -> CompileResult<Self> {
|
) -> CompileResult<(Self, HandlerData)> {
|
||||||
let mut compiled_functions: Vec<Vec<u8>> = Vec::with_capacity(function_bodies.len());
|
let mut compiled_functions: Vec<Vec<u8>> = Vec::with_capacity(function_bodies.len());
|
||||||
let mut relocations = Map::with_capacity(function_bodies.len());
|
let mut relocations = Map::with_capacity(function_bodies.len());
|
||||||
let mut trap_sinks = Map::with_capacity(function_bodies.len());
|
|
||||||
|
let mut trap_sink = TrapSink::new();
|
||||||
|
let mut local_trap_sink = LocalTrapSink::new();
|
||||||
|
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
let mut total_size = 0;
|
let mut total_size = 0;
|
||||||
@ -39,17 +43,20 @@ impl FuncResolverBuilder {
|
|||||||
ctx.func = func;
|
ctx.func = func;
|
||||||
let mut code_buf = Vec::new();
|
let mut code_buf = Vec::new();
|
||||||
let mut reloc_sink = RelocSink::new();
|
let mut reloc_sink = RelocSink::new();
|
||||||
let mut trap_sink = TrapSink::new();
|
|
||||||
|
|
||||||
ctx.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
|
ctx.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut local_trap_sink)
|
||||||
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
|
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
|
||||||
ctx.clear();
|
ctx.clear();
|
||||||
|
|
||||||
|
// Clear the local trap sink and consolidate all trap info
|
||||||
|
// into a single location.
|
||||||
|
trap_sink.drain_local(total_size, &mut local_trap_sink);
|
||||||
|
|
||||||
// Round up each function's size to pointer alignment.
|
// Round up each function's size to pointer alignment.
|
||||||
total_size += round_up(code_buf.len(), mem::size_of::<usize>());
|
total_size += round_up(code_buf.len(), mem::size_of::<usize>());
|
||||||
|
|
||||||
compiled_functions.push(code_buf);
|
compiled_functions.push(code_buf);
|
||||||
relocations.push(reloc_sink.func_relocs);
|
relocations.push(reloc_sink.func_relocs);
|
||||||
trap_sinks.push(trap_sink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut memory = Memory::with_size(total_size)
|
let mut memory = Memory::with_size(total_size)
|
||||||
@ -87,11 +94,15 @@ impl FuncResolverBuilder {
|
|||||||
previous_end = new_end;
|
previous_end = new_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
let handler_data = HandlerData::new(trap_sink, memory.as_ptr() as _, memory.size());
|
||||||
resolver: FuncResolver { map, memory },
|
|
||||||
relocations,
|
Ok((
|
||||||
trap_sinks,
|
Self {
|
||||||
})
|
resolver: FuncResolver { map, memory },
|
||||||
|
relocations,
|
||||||
|
},
|
||||||
|
handler_data,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(mut self) -> CompileResult<FuncResolver> {
|
pub fn finalize(mut self) -> CompileResult<FuncResolver> {
|
||||||
|
@ -7,7 +7,6 @@ build = "build/mod.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hashbrown = "0.1"
|
hashbrown = "0.1"
|
||||||
libffi = "0.6.4"
|
|
||||||
nix = "0.12.0"
|
nix = "0.12.0"
|
||||||
page_size = "0.4.1"
|
page_size = "0.4.1"
|
||||||
|
|
||||||
|
@ -439,7 +439,7 @@ fn {}_assert_invalid() {{
|
|||||||
format!(
|
format!(
|
||||||
"fn {func_name}(instance: &mut Instance) {{
|
"fn {func_name}(instance: &mut Instance) {{
|
||||||
println!(\"Executing function {{}}\", \"{func_name}\");
|
println!(\"Executing function {{}}\", \"{func_name}\");
|
||||||
let result = instance.call(\"{field}\", &[{args_values}]).unwrap().expect(\"Missing result in {func_name}\");
|
let result = instance.call(\"{field}\", &[{args_values}]).unwrap().first().expect(\"Missing result in {func_name}\").clone();
|
||||||
{assertion}
|
{assertion}
|
||||||
}}\n",
|
}}\n",
|
||||||
func_name=func_name,
|
func_name=func_name,
|
||||||
@ -498,7 +498,7 @@ fn {}_assert_invalid() {{
|
|||||||
format!(
|
format!(
|
||||||
"fn {func_name}(instance: &mut Instance) {{
|
"fn {func_name}(instance: &mut Instance) {{
|
||||||
println!(\"Executing function {{}}\", \"{func_name}\");
|
println!(\"Executing function {{}}\", \"{func_name}\");
|
||||||
let result = instance.call(\"{field}\", &[{args_values}]).unwrap().expect(\"Missing result in {func_name}\");
|
let result = instance.call(\"{field}\", &[{args_values}]).unwrap().first().expect(\"Missing result in {func_name}\").clone();
|
||||||
{assertion}
|
{assertion}
|
||||||
}}\n",
|
}}\n",
|
||||||
func_name=func_name,
|
func_name=func_name,
|
||||||
@ -561,10 +561,10 @@ fn {}_assert_malformed() {{
|
|||||||
} else {
|
} else {
|
||||||
"should not use this expect result".to_string()
|
"should not use this expect result".to_string()
|
||||||
};
|
};
|
||||||
let expected_some_result = if expected.len() > 0 {
|
let expected_vec_result = if expected.len() > 0 {
|
||||||
format!("Ok(Some({}))", wabt2rust_value(&expected[0]))
|
format!("Ok(vec![{}])", wabt2rust_value(&expected[0]))
|
||||||
} else {
|
} else {
|
||||||
"Ok(None)".to_string()
|
"Ok(vec![])".to_string()
|
||||||
};
|
};
|
||||||
let return_type = if expected.len() > 0 {
|
let return_type = if expected.len() > 0 {
|
||||||
wabt2rust_type(&expected[0])
|
wabt2rust_type(&expected[0])
|
||||||
@ -584,9 +584,9 @@ fn {}_assert_malformed() {{
|
|||||||
let assertion = if expected.len() > 0 && is_nan(&expected[0]) {
|
let assertion = if expected.len() > 0 && is_nan(&expected[0]) {
|
||||||
format!(
|
format!(
|
||||||
"let expected = {expected_result};
|
"let expected = {expected_result};
|
||||||
if let {return_type_destructure} = result.clone().unwrap().unwrap() {{
|
if let {return_type_destructure} = result.clone().unwrap().first().unwrap() {{
|
||||||
assert!((result as {return_type}).is_nan());
|
assert!((*result as {return_type}).is_nan());
|
||||||
assert_eq!((result as {return_type}).is_sign_positive(), (expected as {return_type}).is_sign_positive());
|
assert_eq!((*result as {return_type}).is_sign_positive(), (expected as {return_type}).is_sign_positive());
|
||||||
}} else {{
|
}} else {{
|
||||||
panic!(\"Unexpected result type {{:?}}\", result);
|
panic!(\"Unexpected result type {{:?}}\", result);
|
||||||
}}",
|
}}",
|
||||||
@ -595,7 +595,7 @@ fn {}_assert_malformed() {{
|
|||||||
return_type_destructure=return_type_destructure
|
return_type_destructure=return_type_destructure
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("assert_eq!(result, {});", expected_some_result)
|
format!("assert_eq!(result, {});", expected_vec_result)
|
||||||
};
|
};
|
||||||
(func_return, assertion)
|
(func_return, assertion)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
use crate::{error::CompileResult, module::ModuleInner, types::LocalFuncIndex, vm};
|
use crate::{
|
||||||
|
backing::ImportBacking,
|
||||||
|
error::CompileResult,
|
||||||
|
error::RuntimeResult,
|
||||||
|
module::ModuleInner,
|
||||||
|
types::{FuncIndex, LocalFuncIndex, Value},
|
||||||
|
vm,
|
||||||
|
};
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
pub mod sys {
|
pub mod sys {
|
||||||
@ -6,12 +13,59 @@ pub mod sys {
|
|||||||
}
|
}
|
||||||
pub use crate::sig_registry::SigRegistry;
|
pub use crate::sig_registry::SigRegistry;
|
||||||
|
|
||||||
|
/// This type cannot be constructed from
|
||||||
|
/// outside the runtime crate.
|
||||||
|
pub struct Token {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Token {
|
||||||
|
pub(crate) fn generate() -> Self {
|
||||||
|
Self { _private: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Compiler {
|
pub trait Compiler {
|
||||||
/// Compiles a `Module` from WebAssembly binary format
|
/// Compiles a `Module` from WebAssembly binary format.
|
||||||
fn compile(&self, wasm: &[u8]) -> CompileResult<ModuleInner>;
|
/// The `CompileToken` parameter ensures that this can only
|
||||||
|
/// be called from inside the runtime.
|
||||||
|
fn compile(&self, wasm: &[u8], _: Token) -> CompileResult<ModuleInner>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The functionality exposed by this trait is expected to be used
|
||||||
|
/// for calling functions exported by a webassembly module from
|
||||||
|
/// host code only.
|
||||||
|
pub trait ProtectedCaller {
|
||||||
|
/// This calls the exported function designated by `local_func_index`.
|
||||||
|
/// Important to note, this supports calling imported functions that are
|
||||||
|
/// then exported.
|
||||||
|
///
|
||||||
|
/// It's invalid to attempt to call a local function that isn't exported and
|
||||||
|
/// the implementation is expected to check for that. The implementation
|
||||||
|
/// is also expected to check for correct parameter types and correct
|
||||||
|
/// parameter number.
|
||||||
|
///
|
||||||
|
/// The `returns` parameter is filled with dummy values when passed in and upon function
|
||||||
|
/// return, will be filled with the return values of the wasm function, as long as the
|
||||||
|
/// call completed successfully.
|
||||||
|
///
|
||||||
|
/// The existance of the Token parameter ensures that this can only be called from
|
||||||
|
/// within the runtime crate.
|
||||||
|
fn call(
|
||||||
|
&self,
|
||||||
|
module: &ModuleInner,
|
||||||
|
func_index: FuncIndex,
|
||||||
|
params: &[Value],
|
||||||
|
returns: &mut [Value],
|
||||||
|
import_backing: &ImportBacking,
|
||||||
|
vmctx: *mut vm::Ctx,
|
||||||
|
_: Token,
|
||||||
|
) -> RuntimeResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FuncResolver {
|
pub trait FuncResolver {
|
||||||
|
/// This returns a pointer to the function designated by the `local_func_index`
|
||||||
|
/// parameter.
|
||||||
fn get(
|
fn get(
|
||||||
&self,
|
&self,
|
||||||
module: &ModuleInner,
|
module: &ModuleInner,
|
||||||
|
@ -346,6 +346,10 @@ impl ImportBacking {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn imported_func(&self, func_index: ImportedFuncIndex) -> vm::ImportedFunc {
|
||||||
|
self.functions[func_index].clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn imported_memory(&self, memory_index: ImportedMemoryIndex) -> vm::ImportedMemory {
|
pub fn imported_memory(&self, memory_index: ImportedMemoryIndex) -> vm::ImportedMemory {
|
||||||
self.memories[memory_index].clone()
|
self.memories[memory_index].clone()
|
||||||
}
|
}
|
||||||
@ -408,6 +412,10 @@ fn import_functions(
|
|||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
None => Err(LinkError::ImportNotFound {
|
||||||
|
namespace: namespace.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
})?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,6 +483,10 @@ fn import_memories(
|
|||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
None => Err(LinkError::ImportNotFound {
|
||||||
|
namespace: namespace.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
})?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,6 +552,10 @@ fn import_tables(
|
|||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
None => Err(LinkError::ImportNotFound {
|
||||||
|
namespace: namespace.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
})?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,6 +612,10 @@ fn import_globals(
|
|||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
None => Err(LinkError::ImportNotFound {
|
||||||
|
namespace: namespace.clone(),
|
||||||
|
name: name.clone(),
|
||||||
|
})?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,11 @@ use crate::types::{FuncSig, GlobalDesc, Memory, MemoryIndex, Table, TableIndex,
|
|||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Box<Error>>;
|
pub type Result<T> = std::result::Result<T, Box<Error>>;
|
||||||
pub type CompileResult<T> = std::result::Result<T, Box<CompileError>>;
|
pub type CompileResult<T> = std::result::Result<T, Box<CompileError>>;
|
||||||
|
<<<<<<< HEAD
|
||||||
pub type LinkResult<T> = std::result::Result<T, Vec<LinkError>>;
|
pub type LinkResult<T> = std::result::Result<T, Vec<LinkError>>;
|
||||||
|
=======
|
||||||
|
pub type LinkResult<T> = std::result::Result<T, Box<LinkError>>;
|
||||||
|
>>>>>>> feature/vm_refactor_trap_handler
|
||||||
pub type RuntimeResult<T> = std::result::Result<T, Box<RuntimeError>>;
|
pub type RuntimeResult<T> = std::result::Result<T, Box<RuntimeError>>;
|
||||||
pub type CallResult<T> = std::result::Result<T, Box<CallError>>;
|
pub type CallResult<T> = std::result::Result<T, Box<CallError>>;
|
||||||
|
|
||||||
@ -80,8 +84,10 @@ impl PartialEq for LinkError {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum RuntimeError {
|
pub enum RuntimeError {
|
||||||
OutOfBoundsAccess { memory: MemoryIndex, addr: u32 },
|
OutOfBoundsAccess { memory: MemoryIndex, addr: u32 },
|
||||||
|
TableOutOfBounds { table: TableIndex },
|
||||||
IndirectCallSignature { table: TableIndex },
|
IndirectCallSignature { table: TableIndex },
|
||||||
IndirectCallToNull { table: TableIndex },
|
IndirectCallToNull { table: TableIndex },
|
||||||
|
IllegalArithmeticOperation,
|
||||||
Unknown { msg: String },
|
Unknown { msg: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::recovery::call_protected;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
backend::Token,
|
||||||
backing::{ImportBacking, LocalBacking},
|
backing::{ImportBacking, LocalBacking},
|
||||||
error::{CallError, CallResult, Result},
|
error::{CallError, CallResult, Result},
|
||||||
export::{
|
export::{
|
||||||
@ -9,13 +9,12 @@ use crate::{
|
|||||||
module::{ExportIndex, Module, ModuleInner},
|
module::{ExportIndex, Module, ModuleInner},
|
||||||
types::{
|
types::{
|
||||||
FuncIndex, FuncSig, GlobalDesc, GlobalIndex, LocalOrImport, Memory, MemoryIndex, Table,
|
FuncIndex, FuncSig, GlobalDesc, GlobalIndex, LocalOrImport, Memory, MemoryIndex, Table,
|
||||||
TableIndex, Type, Value,
|
TableIndex, Value,
|
||||||
},
|
},
|
||||||
vm,
|
vm,
|
||||||
};
|
};
|
||||||
use libffi::high::{arg as libffi_arg, call as libffi_call, CodePtr};
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{iter, mem};
|
|
||||||
|
|
||||||
pub(crate) struct InstanceInner {
|
pub(crate) struct InstanceInner {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -69,11 +68,11 @@ impl Instance {
|
|||||||
|
|
||||||
/// Call an exported webassembly function given the export name.
|
/// Call an exported webassembly function given the export name.
|
||||||
/// Pass arguments by wrapping each one in the `Value` enum.
|
/// Pass arguments by wrapping each one in the `Value` enum.
|
||||||
/// The returned value is also returned in a `Value`.
|
/// The returned values are also each wrapped in a `Value`.
|
||||||
///
|
///
|
||||||
/// This will eventually return `Result<Option<Vec<Value>>, String>` in
|
/// This returns `CallResult<Vec<Value>>` in order to support
|
||||||
/// order to support multi-value returns.
|
/// the future multi-value returns webassembly feature.
|
||||||
pub fn call(&mut self, name: &str, args: &[Value]) -> CallResult<Option<Value>> {
|
pub fn call(&mut self, name: &str, args: &[Value]) -> CallResult<Vec<Value>> {
|
||||||
let export_index =
|
let export_index =
|
||||||
self.module
|
self.module
|
||||||
.exports
|
.exports
|
||||||
@ -104,23 +103,13 @@ impl Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
fn call_with_index(
|
fn call_with_index(&mut self, func_index: FuncIndex, args: &[Value]) -> CallResult<Vec<Value>> {
|
||||||
&mut self,
|
let sig_index = *self
|
||||||
func_index: FuncIndex,
|
.module
|
||||||
args: &[Value],
|
.func_assoc
|
||||||
) -> CallResult<Option<Value>> {
|
.get(func_index)
|
||||||
let (func_ref, ctx, signature) = self.inner.get_func_from_index(&self.module, func_index);
|
.expect("broken invariant, incorrect func index");
|
||||||
|
let signature = self.module.sig_registry.lookup_func_sig(sig_index);
|
||||||
let func_ptr = CodePtr::from_ptr(func_ref.inner() as _);
|
|
||||||
let vmctx_ptr = match ctx {
|
|
||||||
Context::External(vmctx) => vmctx,
|
|
||||||
Context::Internal => &mut *self.inner.vmctx,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
signature.returns.len() <= 1,
|
|
||||||
"multi-value returns not yet supported"
|
|
||||||
);
|
|
||||||
|
|
||||||
if !signature.check_sig(args) {
|
if !signature.check_sig(args) {
|
||||||
Err(CallError::Signature {
|
Err(CallError::Signature {
|
||||||
@ -129,35 +118,29 @@ impl Instance {
|
|||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
|
|
||||||
let libffi_args: Vec<_> = args
|
// Create an output vector that's full of dummy values.
|
||||||
.iter()
|
let mut returns = vec![Value::I32(0); signature.returns.len()];
|
||||||
.map(|val| match val {
|
|
||||||
Value::I32(ref x) => libffi_arg(x),
|
|
||||||
Value::I64(ref x) => libffi_arg(x),
|
|
||||||
Value::F32(ref x) => libffi_arg(x),
|
|
||||||
Value::F64(ref x) => libffi_arg(x),
|
|
||||||
})
|
|
||||||
.chain(iter::once(libffi_arg(&vmctx_ptr)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(call_protected(|| {
|
let vmctx = match func_index.local_or_import(&self.module) {
|
||||||
signature
|
LocalOrImport::Local(_) => &mut *self.inner.vmctx,
|
||||||
.returns
|
LocalOrImport::Import(imported_func_index) => {
|
||||||
.first()
|
self.inner.import_backing.functions[imported_func_index].vmctx
|
||||||
.map(|ty| match ty {
|
}
|
||||||
Type::I32 => Value::I32(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
};
|
||||||
Type::I64 => Value::I64(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
|
||||||
Type::F32 => Value::F32(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
let token = Token::generate();
|
||||||
Type::F64 => Value::F64(unsafe { libffi_call(func_ptr, &libffi_args) }),
|
|
||||||
})
|
self.module.protected_caller.call(
|
||||||
.or_else(|| {
|
&self.module,
|
||||||
// call with no returns
|
func_index,
|
||||||
unsafe {
|
args,
|
||||||
libffi_call::<()>(func_ptr, &libffi_args);
|
&mut returns,
|
||||||
}
|
&self.inner.import_backing,
|
||||||
None
|
vmctx,
|
||||||
})
|
token,
|
||||||
})?)
|
)?;
|
||||||
|
|
||||||
|
Ok(returns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,9 +13,7 @@ pub mod import;
|
|||||||
pub mod instance;
|
pub mod instance;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
mod recovery;
|
|
||||||
mod sig_registry;
|
mod sig_registry;
|
||||||
mod sighandler;
|
|
||||||
pub mod structures;
|
pub mod structures;
|
||||||
mod sys;
|
mod sys;
|
||||||
pub mod table;
|
pub mod table;
|
||||||
@ -31,9 +29,10 @@ pub use self::instance::Instance;
|
|||||||
pub use self::module::Module;
|
pub use self::module::Module;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
/// Compile a WebAssembly module using the provided compiler.
|
/// Compile a webassembly module using the provided compiler.
|
||||||
pub fn compile(wasm: &[u8], compiler: &dyn backend::Compiler) -> CompileResult<module::Module> {
|
pub fn compile(wasm: &[u8], compiler: &dyn backend::Compiler) -> CompileResult<module::Module> {
|
||||||
|
let token = backend::Token::generate();
|
||||||
compiler
|
compiler
|
||||||
.compile(wasm)
|
.compile(wasm, token)
|
||||||
.map(|inner| module::Module::new(Rc::new(inner)))
|
.map(|inner| module::Module::new(Rc::new(inner)))
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
backend::FuncResolver,
|
backend::{FuncResolver, ProtectedCaller},
|
||||||
error::Result,
|
error::Result,
|
||||||
import::Imports,
|
import::Imports,
|
||||||
sig_registry::SigRegistry,
|
sig_registry::SigRegistry,
|
||||||
@ -18,6 +18,8 @@ use std::rc::Rc;
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct ModuleInner {
|
pub struct ModuleInner {
|
||||||
pub func_resolver: Box<dyn FuncResolver>,
|
pub func_resolver: Box<dyn FuncResolver>,
|
||||||
|
pub protected_caller: Box<dyn ProtectedCaller>,
|
||||||
|
|
||||||
// This are strictly local and the typsystem ensures that.
|
// This are strictly local and the typsystem ensures that.
|
||||||
pub memories: Map<LocalMemoryIndex, Memory>,
|
pub memories: Map<LocalMemoryIndex, Memory>,
|
||||||
pub globals: Map<LocalGlobalIndex, Global>,
|
pub globals: Map<LocalGlobalIndex, Global>,
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
//! When a WebAssembly module triggers any traps, we perform recovery here.
|
|
||||||
//!
|
|
||||||
//! This module uses TLS (thread-local storage) to track recovery information. Since the four signals we're handling
|
|
||||||
//! 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.
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
error::{RuntimeError, RuntimeResult},
|
|
||||||
sighandler::install_sighandler,
|
|
||||||
};
|
|
||||||
use nix::libc::siginfo_t;
|
|
||||||
use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV};
|
|
||||||
use std::cell::{Cell, UnsafeCell};
|
|
||||||
use std::sync::Once;
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
pub fn setjmp(env: *mut ::nix::libc::c_void) -> ::nix::libc::c_int;
|
|
||||||
fn longjmp(env: *mut ::nix::libc::c_void, val: ::nix::libc::c_int) -> !;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SETJMP_BUFFER_LEN: usize = 27;
|
|
||||||
pub static SIGHANDLER_INIT: Once = Once::new();
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
pub static SETJMP_BUFFER: UnsafeCell<[::nix::libc::c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]);
|
|
||||||
pub static CAUGHT_ADDRESS: Cell<usize> = Cell::new(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn call_protected<T>(f: impl FnOnce() -> T) -> RuntimeResult<T> {
|
|
||||||
unsafe {
|
|
||||||
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
|
||||||
let prev_jmp_buf = *jmp_buf;
|
|
||||||
|
|
||||||
SIGHANDLER_INIT.call_once(|| {
|
|
||||||
install_sighandler();
|
|
||||||
});
|
|
||||||
|
|
||||||
let signum = setjmp(jmp_buf as *mut ::nix::libc::c_void);
|
|
||||||
if signum != 0 {
|
|
||||||
*jmp_buf = prev_jmp_buf;
|
|
||||||
let addr = CAUGHT_ADDRESS.with(|cell| cell.get());
|
|
||||||
|
|
||||||
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 {:#x} - {}", addr, signal),
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
} else {
|
|
||||||
let ret = f(); // TODO: Switch stack?
|
|
||||||
*jmp_buf = prev_jmp_buf;
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unwinds to last protected_call.
|
|
||||||
pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t) -> ! {
|
|
||||||
// Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.)
|
|
||||||
// itself, accessing TLS here is safe. In case any other code calls this, it often indicates a memory safety bug and you should
|
|
||||||
// temporarily disable the signal handlers to debug it.
|
|
||||||
|
|
||||||
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
|
|
||||||
if *jmp_buf == [0; SETJMP_BUFFER_LEN] {
|
|
||||||
::std::process::abort();
|
|
||||||
}
|
|
||||||
// We only target macos at the moment as other ones might not have si_addr field
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
CAUGHT_ADDRESS.with(|cell| cell.set((*siginfo).si_addr as _));
|
|
||||||
|
|
||||||
longjmp(jmp_buf as *mut ::nix::libc::c_void, signum)
|
|
||||||
}
|
|
@ -159,7 +159,7 @@ pub trait LocalImport {
|
|||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
macro_rules! define_map_index {
|
macro_rules! define_map_index {
|
||||||
($ty:ident) => {
|
($ty:ident) => {
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct $ty (u32);
|
pub struct $ty (u32);
|
||||||
impl TypedIndex for $ty {
|
impl TypedIndex for $ty {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
Reference in New Issue
Block a user