Implement InternalEvent::Breakpoint in the llvm backend.

Enable now-working metering unit tests when run with the llvm backend.
This commit is contained in:
Nick Lewycky 2019-08-01 12:12:53 -07:00
parent bd3be45fcd
commit 536f9813dc
5 changed files with 62 additions and 7 deletions

View File

@ -1,9 +1,10 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <llvm/ExecutionEngine/RuntimeDyld.h> #include <exception>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <exception>
#include <llvm/ExecutionEngine/RuntimeDyld.h>
typedef enum typedef enum
{ {
@ -77,6 +78,19 @@ struct UserException : UncatchableException
box_any_t error_data; box_any_t error_data;
}; };
struct BreakpointException : UncatchableException
{
public:
BreakpointException(uintptr_t callback) : callback(callback) {}
virtual std::string description() const noexcept override
{
return "breakpoint exception";
}
uintptr_t callback;
};
struct WasmTrap : UncatchableException struct WasmTrap : UncatchableException
{ {
public: public:
@ -166,6 +180,8 @@ struct WasmModule
extern "C" extern "C"
{ {
void callback_trampoline(void *, void *);
result_t module_load(const uint8_t *mem_ptr, size_t mem_size, callbacks_t callbacks, WasmModule **module_out) result_t module_load(const uint8_t *mem_ptr, size_t mem_size, callbacks_t callbacks, WasmModule **module_out)
{ {
*module_out = new WasmModule(mem_ptr, mem_size, callbacks); *module_out = new WasmModule(mem_ptr, mem_size, callbacks);
@ -192,6 +208,12 @@ extern "C"
throw UserException(data, vtable); throw UserException(data, vtable);
} }
// Throw a pointer that's assumed to be codegen::BreakpointHandler on the
// rust side.
[[noreturn]] void throw_breakpoint(uintptr_t callback) {
throw BreakpointException(callback);
}
bool invoke_trampoline( bool invoke_trampoline(
trampoline_t trampoline, trampoline_t trampoline,
void *ctx, void *ctx,
@ -217,6 +239,10 @@ extern "C"
*user_error = e.error_data; *user_error = e.error_data;
return false; return false;
} }
catch (const BreakpointException &e) {
callback_trampoline(user_error, (void *)e.callback);
return false;
}
catch (const WasmException &e) catch (const WasmException &e)
{ {
*trap_out = WasmTrap::Type::Unknown; *trap_out = WasmTrap::Type::Unknown;

View File

@ -39,7 +39,8 @@ extern "C" {
fn module_delete(module: *mut LLVMModule); fn module_delete(module: *mut LLVMModule);
fn get_func_symbol(module: *mut LLVMModule, name: *const c_char) -> *const vm::Func; fn get_func_symbol(module: *mut LLVMModule, name: *const c_char) -> *const vm::Func;
fn throw_trap(ty: i32); fn throw_trap(ty: i32) -> !;
fn throw_breakpoint(ty: i64) -> !;
/// This should be the same as spliting up the fat pointer into two arguments, /// This should be the same as spliting up the fat pointer into two arguments,
/// but this is cleaner, I think? /// but this is cleaner, I think?
@ -103,6 +104,7 @@ fn get_callbacks() -> Callbacks {
fn_name!("vm.memory.size.static.local") => vmcalls::local_static_memory_size as _, fn_name!("vm.memory.size.static.local") => vmcalls::local_static_memory_size as _,
fn_name!("vm.exception.trap") => throw_trap as _, fn_name!("vm.exception.trap") => throw_trap as _,
fn_name!("vm.breakpoint") => throw_breakpoint as _,
_ => ptr::null(), _ => ptr::null(),
} }

View File

@ -496,6 +496,21 @@ pub struct CodegenError {
pub message: String, pub message: String,
} }
// This is only called by C++ code, the 'pub' + '#[no_mangle]' combination
// prevents unused function elimination.
#[no_mangle]
pub unsafe extern "C" fn callback_trampoline(
b: *mut Option<Box<dyn std::any::Any>>,
callback: *mut BreakpointHandler,
) {
let callback = Box::from_raw(callback);
let result: Result<(), Box<dyn std::any::Any>> = callback(BreakpointInfo { fault: None });
match result {
Ok(()) => *b = None,
Err(e) => *b = Some(e),
}
}
pub struct LLVMModuleCodeGenerator { pub struct LLVMModuleCodeGenerator {
context: Option<Context>, context: Option<Context>,
builder: Option<Builder>, builder: Option<Builder>,
@ -612,7 +627,14 @@ impl FunctionCodeGenerator<CodegenError> for LLVMFunctionCodeGenerator {
InternalEvent::FunctionBegin(_) | InternalEvent::FunctionEnd => { InternalEvent::FunctionBegin(_) | InternalEvent::FunctionEnd => {
return Ok(()); return Ok(());
} }
InternalEvent::Breakpoint(_callback) => { InternalEvent::Breakpoint(callback) => {
let raw = Box::into_raw(Box::new(callback)) as u64;
let callback = intrinsics.i64_ty.const_int(raw, false);
builder.build_call(
intrinsics.throw_breakpoint,
&[callback.as_basic_value_enum()],
"",
);
return Ok(()); return Ok(());
} }
InternalEvent::GetInternal(idx) => { InternalEvent::GetInternal(idx) => {

View File

@ -147,6 +147,7 @@ pub struct Intrinsics {
pub memory_size_shared_import: FunctionValue, pub memory_size_shared_import: FunctionValue,
pub throw_trap: FunctionValue, pub throw_trap: FunctionValue,
pub throw_breakpoint: FunctionValue,
pub ctx_ptr_ty: PointerType, pub ctx_ptr_ty: PointerType,
} }
@ -309,7 +310,6 @@ impl Intrinsics {
i32_ty.fn_type(&[ctx_ptr_ty.as_basic_type_enum(), i32_ty_basic], false); i32_ty.fn_type(&[ctx_ptr_ty.as_basic_type_enum(), i32_ty_basic], false);
let ret_i1_take_i1_i1 = i1_ty.fn_type(&[i1_ty_basic, i1_ty_basic], false); let ret_i1_take_i1_i1 = i1_ty.fn_type(&[i1_ty_basic, i1_ty_basic], false);
Self { Self {
ctlz_i32: module.add_function("llvm.ctlz.i32", ret_i32_take_i32_i1, None), ctlz_i32: module.add_function("llvm.ctlz.i32", ret_i32_take_i32_i1, None),
ctlz_i64: module.add_function("llvm.ctlz.i64", ret_i64_take_i64_i1, None), ctlz_i64: module.add_function("llvm.ctlz.i64", ret_i64_take_i64_i1, None),
@ -525,6 +525,11 @@ impl Intrinsics {
void_ty.fn_type(&[i32_ty_basic], false), void_ty.fn_type(&[i32_ty_basic], false),
None, None,
), ),
throw_breakpoint: module.add_function(
"vm.breakpoint",
void_ty.fn_type(&[i64_ty_basic], false),
None,
),
ctx_ptr_ty, ctx_ptr_ty,
} }
} }

View File

@ -129,7 +129,7 @@ pub fn set_points_used_ctx(ctx: &mut Ctx, value: u64) {
ctx.set_internal(&INTERNAL_FIELD, value); ctx.set_internal(&INTERNAL_FIELD, value);
} }
#[cfg(all(test, feature = "singlepass"))] #[cfg(all(test, any(feature = "singlepass", feature = "llvm")))]
mod tests { mod tests {
use super::*; use super::*;
use wabt::wat2wasm; use wabt::wat2wasm;