[wasmtime-api] Record original Trap from API callback. (#657)
* Record original Trap from API callback. Fixes #645 * use TrapRegistry * comment about magic number
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use crate::data_structures::ir;
|
use crate::data_structures::ir;
|
||||||
use crate::r#ref::HostRef;
|
use crate::r#ref::HostRef;
|
||||||
use crate::runtime::Store;
|
use crate::runtime::Store;
|
||||||
use crate::trampoline::generate_func_export;
|
use crate::trampoline::{generate_func_export, take_api_trap};
|
||||||
use crate::trap::Trap;
|
use crate::trap::Trap;
|
||||||
use crate::types::FuncType;
|
use crate::types::FuncType;
|
||||||
use crate::values::Val;
|
use crate::values::Val;
|
||||||
@@ -90,7 +90,8 @@ impl WrappedCallable for WasmtimeFn {
|
|||||||
values_vec.as_mut_ptr() as *mut u8,
|
values_vec.as_mut_ptr() as *mut u8,
|
||||||
)
|
)
|
||||||
} {
|
} {
|
||||||
return Err(HostRef::new(Trap::new(message)));
|
let trap = take_api_trap().unwrap_or_else(|| HostRef::new(Trap::new(message)));
|
||||||
|
return Err(trap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the return values out of `values_vec`.
|
// Load the return values out of `values_vec`.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::externals::Extern;
|
|||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::r#ref::HostRef;
|
use crate::r#ref::HostRef;
|
||||||
use crate::runtime::Store;
|
use crate::runtime::Store;
|
||||||
|
use crate::trampoline::take_api_trap;
|
||||||
use crate::types::{ExportType, ExternType, Name};
|
use crate::types::{ExportType, ExternType, Name};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
@@ -40,7 +41,12 @@ pub fn instantiate_in_context(
|
|||||||
&mut resolver,
|
&mut resolver,
|
||||||
exports,
|
exports,
|
||||||
debug_info,
|
debug_info,
|
||||||
)?;
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
// TODO wrap HostRef<Trap> into Error
|
||||||
|
drop(take_api_trap());
|
||||||
|
e
|
||||||
|
})?;
|
||||||
contexts.insert(context);
|
contexts.insert(context);
|
||||||
Ok((instance, contexts))
|
Ok((instance, contexts))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,62 @@
|
|||||||
//! Support for a calling of an imported function.
|
//! Support for a calling of an imported function.
|
||||||
|
|
||||||
use super::create_handle::create_handle;
|
use super::create_handle::create_handle;
|
||||||
use super::ir::{
|
use super::ir::{ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind};
|
||||||
ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind, TrapCode,
|
use super::trap::{record_api_trap, TrapSink, API_TRAP_CODE};
|
||||||
};
|
|
||||||
use super::{binemit, pretty_error, TargetIsa};
|
use super::{binemit, pretty_error, TargetIsa};
|
||||||
use super::{Context, FunctionBuilder, FunctionBuilderContext};
|
use super::{Context, FunctionBuilder, FunctionBuilderContext};
|
||||||
use crate::data_structures::ir::{self, types};
|
use crate::data_structures::ir::{self, types};
|
||||||
use crate::data_structures::wasm::{DefinedFuncIndex, FuncIndex};
|
use crate::data_structures::wasm::{DefinedFuncIndex, FuncIndex};
|
||||||
use crate::data_structures::{native_isa_builder, settings, EntityRef, PrimaryMap};
|
use crate::data_structures::{native_isa_builder, settings, EntityRef, PrimaryMap};
|
||||||
use crate::r#ref::HostRef;
|
use crate::r#ref::HostRef;
|
||||||
use crate::{Callable, FuncType, Store, Trap, Val};
|
use crate::{Callable, FuncType, Store, Val};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use wasmtime_environ::{CompiledFunction, Export, Module};
|
use wasmtime_environ::{CompiledFunction, Export, Module, TrapInformation};
|
||||||
use wasmtime_jit::CodeMemory;
|
use wasmtime_jit::CodeMemory;
|
||||||
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody};
|
use wasmtime_runtime::{
|
||||||
|
get_mut_trap_registry, InstanceHandle, TrapRegistrationGuard, VMContext, VMFunctionBody,
|
||||||
|
};
|
||||||
|
|
||||||
struct TrampolineState {
|
struct TrampolineState {
|
||||||
func: Rc<dyn Callable + 'static>,
|
func: Rc<dyn Callable + 'static>,
|
||||||
trap: Option<HostRef<Trap>>,
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
code_memory: CodeMemory,
|
code_memory: CodeMemory,
|
||||||
|
trap_registration_guards: Vec<TrapRegistrationGuard>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrampolineState {
|
||||||
|
fn new(
|
||||||
|
func: Rc<dyn Callable + 'static>,
|
||||||
|
code_memory: CodeMemory,
|
||||||
|
func_addr: *const VMFunctionBody,
|
||||||
|
func_traps: &[TrapInformation],
|
||||||
|
) -> Self {
|
||||||
|
let mut trap_registry = get_mut_trap_registry();
|
||||||
|
let mut trap_registration_guards = Vec::new();
|
||||||
|
for trap_desc in func_traps.iter() {
|
||||||
|
let func_addr = func_addr as *const u8 as usize;
|
||||||
|
let offset = usize::try_from(trap_desc.code_offset).unwrap();
|
||||||
|
let trap_addr = func_addr + offset;
|
||||||
|
let guard =
|
||||||
|
trap_registry.register_trap(trap_addr, trap_desc.source_loc, trap_desc.trap_code);
|
||||||
|
trap_registration_guards.push(guard);
|
||||||
|
}
|
||||||
|
TrampolineState {
|
||||||
|
func,
|
||||||
|
code_memory,
|
||||||
|
trap_registration_guards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TrampolineState {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// We must deregister traps before freeing the code memory.
|
||||||
|
self.trap_registration_guards.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, call_id: u32, values_vec: *mut i64) -> u32 {
|
unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, call_id: u32, values_vec: *mut i64) -> u32 {
|
||||||
@@ -58,12 +92,7 @@ unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, call_id: u32, values_vec: *m
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
Err(trap) => {
|
Err(trap) => {
|
||||||
// TODO read custom exception
|
record_api_trap(trap);
|
||||||
InstanceHandle::from_vmctx(vmctx)
|
|
||||||
.host_state()
|
|
||||||
.downcast_mut::<TrampolineState>()
|
|
||||||
.expect("state")
|
|
||||||
.trap = Some(trap);
|
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +105,7 @@ fn make_trampoline(
|
|||||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||||
call_id: u32,
|
call_id: u32,
|
||||||
signature: &ir::Signature,
|
signature: &ir::Signature,
|
||||||
) -> *const VMFunctionBody {
|
) -> (*const VMFunctionBody, Vec<TrapInformation>) {
|
||||||
// Mostly reverse copy of the similar method from wasmtime's
|
// Mostly reverse copy of the similar method from wasmtime's
|
||||||
// wasmtime-jit/src/compiler.rs.
|
// wasmtime-jit/src/compiler.rs.
|
||||||
let pointer_type = isa.pointer_type();
|
let pointer_type = isa.pointer_type();
|
||||||
@@ -147,7 +176,7 @@ fn make_trampoline(
|
|||||||
.call_indirect(new_sig, callee_value, &callee_args);
|
.call_indirect(new_sig, callee_value, &callee_args);
|
||||||
|
|
||||||
let call_result = builder.func.dfg.inst_results(call)[0];
|
let call_result = builder.func.dfg.inst_results(call)[0];
|
||||||
builder.ins().trapnz(call_result, TrapCode::User(0));
|
builder.ins().trapnz(call_result, API_TRAP_CODE);
|
||||||
|
|
||||||
let mflags = MemFlags::trusted();
|
let mflags = MemFlags::trusted();
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
@@ -166,7 +195,7 @@ fn make_trampoline(
|
|||||||
|
|
||||||
let mut code_buf: Vec<u8> = Vec::new();
|
let mut code_buf: Vec<u8> = Vec::new();
|
||||||
let mut reloc_sink = binemit::TrampolineRelocSink {};
|
let mut reloc_sink = binemit::TrampolineRelocSink {};
|
||||||
let mut trap_sink = binemit::NullTrapSink {};
|
let mut trap_sink = TrapSink::new();
|
||||||
let mut stackmap_sink = binemit::NullStackmapSink {};
|
let mut stackmap_sink = binemit::NullStackmapSink {};
|
||||||
context
|
context
|
||||||
.compile_and_emit(
|
.compile_and_emit(
|
||||||
@@ -182,14 +211,17 @@ fn make_trampoline(
|
|||||||
let mut unwind_info = Vec::new();
|
let mut unwind_info = Vec::new();
|
||||||
context.emit_unwind_info(isa, &mut unwind_info);
|
context.emit_unwind_info(isa, &mut unwind_info);
|
||||||
|
|
||||||
code_memory
|
let traps = trap_sink.traps;
|
||||||
|
|
||||||
|
let addr = code_memory
|
||||||
.allocate_for_function(&CompiledFunction {
|
.allocate_for_function(&CompiledFunction {
|
||||||
body: code_buf,
|
body: code_buf,
|
||||||
jt_offsets: context.func.jt_offsets,
|
jt_offsets: context.func.jt_offsets,
|
||||||
unwind_info,
|
unwind_info,
|
||||||
})
|
})
|
||||||
.expect("allocate_for_function")
|
.expect("allocate_for_function")
|
||||||
.as_ptr()
|
.as_ptr();
|
||||||
|
(addr, traps)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_handle_with_function(
|
pub fn create_handle_with_function(
|
||||||
@@ -216,7 +248,7 @@ pub fn create_handle_with_function(
|
|||||||
module
|
module
|
||||||
.exports
|
.exports
|
||||||
.insert("trampoline".to_string(), Export::Function(func_id));
|
.insert("trampoline".to_string(), Export::Function(func_id));
|
||||||
let trampoline = make_trampoline(
|
let (trampoline, traps) = make_trampoline(
|
||||||
isa.as_ref(),
|
isa.as_ref(),
|
||||||
&mut code_memory,
|
&mut code_memory,
|
||||||
&mut fn_builder_ctx,
|
&mut fn_builder_ctx,
|
||||||
@@ -227,11 +259,7 @@ pub fn create_handle_with_function(
|
|||||||
|
|
||||||
finished_functions.push(trampoline);
|
finished_functions.push(trampoline);
|
||||||
|
|
||||||
let trampoline_state = TrampolineState {
|
let trampoline_state = TrampolineState::new(func.clone(), code_memory, trampoline, &traps);
|
||||||
func: func.clone(),
|
|
||||||
trap: None,
|
|
||||||
code_memory,
|
|
||||||
};
|
|
||||||
|
|
||||||
create_handle(
|
create_handle(
|
||||||
module,
|
module,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod func;
|
|||||||
mod global;
|
mod global;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod table;
|
mod table;
|
||||||
|
mod trap;
|
||||||
|
|
||||||
use self::func::create_handle_with_function;
|
use self::func::create_handle_with_function;
|
||||||
use self::global::create_global;
|
use self::global::create_global;
|
||||||
@@ -16,6 +17,7 @@ use anyhow::Result;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub use self::global::GlobalState;
|
pub use self::global::GlobalState;
|
||||||
|
pub use self::trap::take_api_trap;
|
||||||
|
|
||||||
pub fn generate_func_export(
|
pub fn generate_func_export(
|
||||||
ft: &FuncType,
|
ft: &FuncType,
|
||||||
@@ -53,7 +55,7 @@ pub fn generate_table_export(
|
|||||||
pub(crate) use cranelift_codegen::print_errors::pretty_error;
|
pub(crate) use cranelift_codegen::print_errors::pretty_error;
|
||||||
|
|
||||||
pub(crate) mod binemit {
|
pub(crate) mod binemit {
|
||||||
pub(crate) use cranelift_codegen::binemit::{NullStackmapSink, NullTrapSink};
|
pub(crate) use cranelift_codegen::binemit::{CodeOffset, NullStackmapSink, TrapSink};
|
||||||
|
|
||||||
pub use cranelift_codegen::{binemit, ir};
|
pub use cranelift_codegen::{binemit, ir};
|
||||||
|
|
||||||
@@ -100,7 +102,8 @@ pub(crate) mod binemit {
|
|||||||
|
|
||||||
pub(crate) mod ir {
|
pub(crate) mod ir {
|
||||||
pub(crate) use cranelift_codegen::ir::{
|
pub(crate) use cranelift_codegen::ir::{
|
||||||
ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind, TrapCode,
|
ExternalName, Function, InstBuilder, MemFlags, SourceLoc, StackSlotData, StackSlotKind,
|
||||||
|
TrapCode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub(crate) use cranelift_codegen::isa::TargetIsa;
|
pub(crate) use cranelift_codegen::isa::TargetIsa;
|
||||||
|
|||||||
54
crates/api/src/trampoline/trap.rs
Normal file
54
crates/api/src/trampoline/trap.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use super::binemit;
|
||||||
|
use super::ir::{SourceLoc, TrapCode};
|
||||||
|
use crate::r#ref::HostRef;
|
||||||
|
use crate::Trap;
|
||||||
|
use wasmtime_environ::TrapInformation;
|
||||||
|
|
||||||
|
// Randomly selected user TrapCode magic number 13.
|
||||||
|
pub const API_TRAP_CODE: TrapCode = TrapCode::User(13);
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static RECORDED_API_TRAP: Cell<Option<HostRef<Trap>>> = Cell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_api_trap(trap: HostRef<Trap>) {
|
||||||
|
RECORDED_API_TRAP.with(|data| {
|
||||||
|
let trap = Cell::new(Some(trap));
|
||||||
|
data.swap(&trap);
|
||||||
|
assert!(
|
||||||
|
trap.take().is_none(),
|
||||||
|
"Only one API trap per thread can be recorded at a moment!"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_api_trap() -> Option<HostRef<Trap>> {
|
||||||
|
RECORDED_API_TRAP.with(|data| data.take())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct TrapSink {
|
||||||
|
pub traps: Vec<TrapInformation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrapSink {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { traps: Vec::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl binemit::TrapSink for TrapSink {
|
||||||
|
fn trap(
|
||||||
|
&mut self,
|
||||||
|
code_offset: binemit::CodeOffset,
|
||||||
|
source_loc: SourceLoc,
|
||||||
|
trap_code: TrapCode,
|
||||||
|
) {
|
||||||
|
self.traps.push(TrapInformation {
|
||||||
|
code_offset,
|
||||||
|
source_loc,
|
||||||
|
trap_code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
49
crates/api/tests/traps.rs
Normal file
49
crates/api/tests/traps.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
use wasmtime::*;
|
||||||
|
use wat::parse_str;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trap_return() -> Result<(), String> {
|
||||||
|
struct HelloCallback;
|
||||||
|
|
||||||
|
impl Callable for HelloCallback {
|
||||||
|
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), HostRef<Trap>> {
|
||||||
|
Err(HostRef::new(Trap::new("test 123".into())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let engine = HostRef::new(Engine::default());
|
||||||
|
let store = HostRef::new(Store::new(&engine));
|
||||||
|
let binary = parse_str(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(func $hello (import "" "hello"))
|
||||||
|
(func (export "run") (call $hello))
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?;
|
||||||
|
|
||||||
|
let module = HostRef::new(
|
||||||
|
Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?,
|
||||||
|
);
|
||||||
|
let hello_type = FuncType::new(Box::new([]), Box::new([]));
|
||||||
|
let hello_func = HostRef::new(Func::new(&store, hello_type, Rc::new(HelloCallback)));
|
||||||
|
|
||||||
|
let imports = vec![hello_func.into()];
|
||||||
|
let instance = Instance::new(&store, &module, imports.as_slice())
|
||||||
|
.map_err(|e| format!("failed to instantiate module: {}", e))?;
|
||||||
|
let run_func = instance.exports()[0]
|
||||||
|
.func()
|
||||||
|
.expect("expected function export");
|
||||||
|
|
||||||
|
let e = run_func
|
||||||
|
.borrow()
|
||||||
|
.call(&[])
|
||||||
|
.err()
|
||||||
|
.expect("error calling function");
|
||||||
|
|
||||||
|
assert_eq!(e.borrow().message(), "test 123");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user