From 177c507a4eb08c28a2af864be19bb1e5f45bab78 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 31 Oct 2019 22:04:00 +0100 Subject: [PATCH 01/25] feat(runtime-core) Introduce `vm::FuncCtx`. `vm::FuncCtx` replaces `vm::Ctx` as first argument passed to host functions (aka imported functions). --- lib/runtime-core/src/backing.rs | 29 +++++++++++++++++++++++------ lib/runtime-core/src/instance.rs | 8 ++++---- lib/runtime-core/src/typed_func.rs | 6 ++++-- lib/runtime-core/src/vm.rs | 21 ++++++++++++++++----- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index ff21e9195f1..d413ef00e2a 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -15,7 +15,7 @@ use crate::{ }, vm, }; -use std::{fmt::Debug, slice}; +use std::{fmt::Debug, ptr, slice}; pub const INTERNALS_SIZE: usize = 256; @@ -384,7 +384,7 @@ impl LocalBacking { LocalOrImport::Import(imported_func_index) => { let vm::ImportedFunc { func, vmctx } = imports.vm_functions[imported_func_index]; - (func, vmctx) + (func, vmctx as _) } }; @@ -417,7 +417,7 @@ impl LocalBacking { LocalOrImport::Import(imported_func_index) => { let vm::ImportedFunc { func, vmctx } = imports.vm_functions[imported_func_index]; - (func, vmctx) + (func, vmctx as _) } }; @@ -541,6 +541,15 @@ impl ImportBacking { } } +impl Drop for ImportBacking { + fn drop(&mut self) { + for (_imported_func_index, imported_func) in (*self.vm_functions).iter_mut() { + let _: Box = unsafe { Box::from_raw(imported_func.vmctx) }; + imported_func.vmctx = ptr::null_mut(); + } + } +} + fn import_functions( module: &ModuleInner, imports: &ImportObject, @@ -564,6 +573,7 @@ fn import_functions( let import = imports.maybe_with_namespace(namespace, |namespace| namespace.get_export(name)); + dbg!(vmctx); match import { Some(Export::Function { func, @@ -573,9 +583,16 @@ fn import_functions( if *expected_sig == *signature { functions.push(vm::ImportedFunc { func: func.inner(), - vmctx: match ctx { - Context::External(ctx) => ctx, - Context::Internal => vmctx, + vmctx: { + let _ = match ctx { + Context::External(ctx) => ctx, + Context::Internal => vmctx, + }; + + Box::into_raw(Box::new(vm::FuncCtx { + vmctx, + func_env: ptr::null_mut(), + })) }, }); } else { diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 85e9f8f768b..2716e272ad6 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -111,7 +111,7 @@ impl Instance { let ctx_ptr = match start_index.local_or_import(&instance.module.info) { LocalOrImport::Local(_) => instance.inner.vmctx, LocalOrImport::Import(imported_func_index) => { - instance.inner.import_backing.vm_functions[imported_func_index].vmctx + instance.inner.import_backing.vm_functions[imported_func_index].vmctx as _ } }; @@ -196,7 +196,7 @@ impl Instance { let ctx = match func_index.local_or_import(&self.module.info) { LocalOrImport::Local(_) => self.inner.vmctx, LocalOrImport::Import(imported_func_index) => { - self.inner.import_backing.vm_functions[imported_func_index].vmctx + self.inner.import_backing.vm_functions[imported_func_index].vmctx as _ } }; @@ -449,7 +449,7 @@ impl InstanceInner { let imported_func = &self.import_backing.vm_functions[imported_func_index]; ( imported_func.func as *const _, - Context::External(imported_func.vmctx), + Context::External(imported_func.vmctx as _), ) } }; @@ -575,7 +575,7 @@ fn call_func_with_index( let ctx_ptr = match func_index.local_or_import(info) { LocalOrImport::Local(_) => local_ctx, LocalOrImport::Import(imported_func_index) => { - import_backing.vm_functions[imported_func_index].vmctx + import_backing.vm_functions[imported_func_index].vmctx as _ } }; diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 2a38beb1e97..8e0819985bd 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -490,7 +490,7 @@ macro_rules! impl_traits { /// This is required for the llvm backend to be able to unwind through this function. #[cfg_attr(nightly, unwind(allowed))] extern fn wrap<$( $x, )* Rets, Trap, FN>( - vmctx: &mut vm::Ctx $( , $x: <$x as WasmExternType>::Native )* + func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct where $( $x: WasmExternType, )* @@ -498,6 +498,7 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, { + let vmctx = unsafe { &mut *func_ctx.vmctx }; let f: FN = unsafe { mem::transmute_copy(&()) }; let err = match panic::catch_unwind( @@ -548,7 +549,7 @@ macro_rules! impl_traits { /// This is required for the llvm backend to be able to unwind through this function. #[cfg_attr(nightly, unwind(allowed))] extern fn wrap<$( $x, )* Rets, Trap, FN>( - vmctx: &mut vm::Ctx $( , $x: <$x as WasmExternType>::Native )* + func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct where $( $x: WasmExternType, )* @@ -556,6 +557,7 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn($( $x, )*) -> Trap, { + let vmctx = unsafe { &mut *func_ctx.vmctx }; let f: FN = unsafe { mem::transmute_copy(&()) }; let err = match panic::catch_unwind( diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 2bb734a04d8..adc33f2c5e2 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -498,19 +498,30 @@ impl Ctx { } } -enum InnerFunc {} /// Used to provide type safety (ish) for passing around function pointers. -/// The typesystem ensures this cannot be dereferenced since an -/// empty enum cannot actually exist. #[repr(C)] -pub struct Func(InnerFunc); +pub struct Func { + _private: [u8; 0], +} + +#[repr(C)] +pub struct FuncEnv { + _private: [u8; 0], +} + +#[derive(Debug)] +#[repr(C)] +pub struct FuncCtx { + pub vmctx: *mut Ctx, + pub func_env: *mut FuncEnv, +} /// An imported function, which contains the vmctx that owns this function. #[derive(Debug, Clone)] #[repr(C)] pub struct ImportedFunc { pub func: *const Func, - pub vmctx: *mut Ctx, + pub vmctx: *mut FuncCtx, } // manually implemented because ImportedFunc contains raw pointers directly; `Func` is marked Send (But `Ctx` actually isn't! (TODO: review this, shouldn't `Ctx` be Send?)) From e002c377efcf104c49297efbd0e217b6385045e8 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 11:45:09 +0100 Subject: [PATCH 02/25] feat(runtime-core) `vm::ImportedFunc` and `vm::FuncCtx` have `NonNull` pointers. --- lib/runtime-core/src/backing.rs | 30 +++++++++++++++++------------- lib/runtime-core/src/instance.rs | 26 +++++++++++++++++++------- lib/runtime-core/src/typed_func.rs | 4 ++-- lib/runtime-core/src/vm.rs | 4 ++-- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index d413ef00e2a..f9b320b45ea 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -15,7 +15,11 @@ use crate::{ }, vm, }; -use std::{fmt::Debug, ptr, slice}; +use std::{ + fmt::Debug, + ptr::{self, NonNull}, + slice, +}; pub const INTERNALS_SIZE: usize = 256; @@ -382,9 +386,9 @@ impl LocalBacking { vmctx, ), LocalOrImport::Import(imported_func_index) => { - let vm::ImportedFunc { func, vmctx } = + let vm::ImportedFunc { func, func_ctx } = imports.vm_functions[imported_func_index]; - (func, vmctx as _) + (func, unsafe { func_ctx.as_ref() }.vmctx.as_ptr()) } }; @@ -415,9 +419,9 @@ impl LocalBacking { vmctx, ), LocalOrImport::Import(imported_func_index) => { - let vm::ImportedFunc { func, vmctx } = + let vm::ImportedFunc { func, func_ctx } = imports.vm_functions[imported_func_index]; - (func, vmctx as _) + (func, unsafe { func_ctx.as_ref() }.vmctx.as_ptr()) } }; @@ -544,8 +548,7 @@ impl ImportBacking { impl Drop for ImportBacking { fn drop(&mut self) { for (_imported_func_index, imported_func) in (*self.vm_functions).iter_mut() { - let _: Box = unsafe { Box::from_raw(imported_func.vmctx) }; - imported_func.vmctx = ptr::null_mut(); + let _: Box = unsafe { Box::from_raw(imported_func.func_ctx.as_ptr()) }; } } } @@ -583,16 +586,17 @@ fn import_functions( if *expected_sig == *signature { functions.push(vm::ImportedFunc { func: func.inner(), - vmctx: { + func_ctx: { let _ = match ctx { Context::External(ctx) => ctx, Context::Internal => vmctx, }; - Box::into_raw(Box::new(vm::FuncCtx { - vmctx, + NonNull::new(Box::into_raw(Box::new(vm::FuncCtx { + vmctx: NonNull::new(vmctx).expect("`vmctx` must not be null."), func_env: ptr::null_mut(), - })) + }))) + .unwrap() }, }); } else { @@ -622,8 +626,8 @@ fn import_functions( None => { if imports.allow_missing_functions { functions.push(vm::ImportedFunc { - func: ::std::ptr::null(), - vmctx: ::std::ptr::null_mut(), + func: ptr::null(), + func_ctx: unsafe { NonNull::new_unchecked(ptr::null_mut()) }, // TODO: Non-sense… }); } else { link_errors.push(LinkError::ImportNotFound { diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 2716e272ad6..5cf92a312f9 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -110,9 +110,13 @@ impl Instance { let ctx_ptr = match start_index.local_or_import(&instance.module.info) { LocalOrImport::Local(_) => instance.inner.vmctx, - LocalOrImport::Import(imported_func_index) => { - instance.inner.import_backing.vm_functions[imported_func_index].vmctx as _ + LocalOrImport::Import(imported_func_index) => unsafe { + instance.inner.import_backing.vm_functions[imported_func_index] + .func_ctx + .as_ref() } + .vmctx + .as_ptr(), }; let sig_index = *instance @@ -195,9 +199,13 @@ impl Instance { let ctx = match func_index.local_or_import(&self.module.info) { LocalOrImport::Local(_) => self.inner.vmctx, - LocalOrImport::Import(imported_func_index) => { - self.inner.import_backing.vm_functions[imported_func_index].vmctx as _ + LocalOrImport::Import(imported_func_index) => unsafe { + self.inner.import_backing.vm_functions[imported_func_index] + .func_ctx + .as_ref() } + .vmctx + .as_ptr(), }; let func_wasm_inner = self @@ -449,7 +457,7 @@ impl InstanceInner { let imported_func = &self.import_backing.vm_functions[imported_func_index]; ( imported_func.func as *const _, - Context::External(imported_func.vmctx as _), + Context::External(unsafe { imported_func.func_ctx.as_ref() }.vmctx.as_ptr()), ) } }; @@ -574,9 +582,13 @@ fn call_func_with_index( let ctx_ptr = match func_index.local_or_import(info) { LocalOrImport::Local(_) => local_ctx, - LocalOrImport::Import(imported_func_index) => { - import_backing.vm_functions[imported_func_index].vmctx as _ + LocalOrImport::Import(imported_func_index) => unsafe { + import_backing.vm_functions[imported_func_index] + .func_ctx + .as_ref() } + .vmctx + .as_ptr(), }; let wasm = runnable diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 8e0819985bd..049dfc16130 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -498,7 +498,7 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, { - let vmctx = unsafe { &mut *func_ctx.vmctx }; + let vmctx = unsafe { func_ctx.vmctx.as_mut() }; let f: FN = unsafe { mem::transmute_copy(&()) }; let err = match panic::catch_unwind( @@ -557,7 +557,7 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn($( $x, )*) -> Trap, { - let vmctx = unsafe { &mut *func_ctx.vmctx }; + let vmctx = unsafe { func_ctx.vmctx.as_mut() }; let f: FN = unsafe { mem::transmute_copy(&()) }; let err = match panic::catch_unwind( diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index adc33f2c5e2..fa4d3020bf6 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -512,7 +512,7 @@ pub struct FuncEnv { #[derive(Debug)] #[repr(C)] pub struct FuncCtx { - pub vmctx: *mut Ctx, + pub vmctx: NonNull, pub func_env: *mut FuncEnv, } @@ -521,7 +521,7 @@ pub struct FuncCtx { #[repr(C)] pub struct ImportedFunc { pub func: *const Func, - pub vmctx: *mut FuncCtx, + pub func_ctx: NonNull, } // manually implemented because ImportedFunc contains raw pointers directly; `Func` is marked Send (But `Ctx` actually isn't! (TODO: review this, shouldn't `Ctx` be Send?)) From 6035bd2d9be80c51b169954af206bb3ab4d5887f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 12:24:30 +0100 Subject: [PATCH 03/25] feat(runtime-core,clif-backend,llvm-backend) Rename an `ImportFunc` offset. `ImportedFunc::offset_vmctx` becomes `ImportedFunc::offset_func_ctx`. --- lib/clif-backend/src/code.rs | 18 ++++++++++-------- lib/llvm-backend/src/stackmap.rs | 2 +- lib/runtime-core/src/vm.rs | 10 ++++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/clif-backend/src/code.rs b/lib/clif-backend/src/code.rs index f1d8489da13..d07b5ce6dda 100644 --- a/lib/clif-backend/src/code.rs +++ b/lib/clif-backend/src/code.rs @@ -763,20 +763,22 @@ impl FuncEnvironment for FunctionEnvironment { readonly: true, }); - let imported_vmctx_addr = pos.func.create_global_value(ir::GlobalValueData::Load { - base: imported_func_struct_addr, - offset: (vm::ImportedFunc::offset_vmctx() as i32).into(), - global_type: ptr_type, - readonly: true, - }); + let imported_func_ctx_addr = + pos.func.create_global_value(ir::GlobalValueData::Load { + base: imported_func_struct_addr, + offset: (vm::ImportedFunc::offset_func_ctx() as i32).into(), + global_type: ptr_type, + readonly: true, + }); let imported_func_addr = pos.ins().global_value(ptr_type, imported_func_addr); - let imported_vmctx_addr = pos.ins().global_value(ptr_type, imported_vmctx_addr); + let imported_func_ctx_addr = + pos.ins().global_value(ptr_type, imported_func_ctx_addr); let sig_ref = pos.func.dfg.ext_funcs[callee].signature; let mut args = Vec::with_capacity(call_args.len() + 1); - args.push(imported_vmctx_addr); + args.push(imported_func_ctx_addr); args.extend(call_args.iter().cloned()); Ok(pos diff --git a/lib/llvm-backend/src/stackmap.rs b/lib/llvm-backend/src/stackmap.rs index a56c3c6a38d..4a9dbf81633 100644 --- a/lib/llvm-backend/src/stackmap.rs +++ b/lib/llvm-backend/src/stackmap.rs @@ -161,7 +161,7 @@ impl StackmapEntry { ValueSemantic::ImportedFuncCtx(idx) => MachineValue::VmctxDeref(vec![ Ctx::offset_imported_funcs() as usize, vm::ImportedFunc::size() as usize * idx - + vm::ImportedFunc::offset_vmctx() as usize, + + vm::ImportedFunc::offset_func_ctx() as usize, 0, ]), ValueSemantic::DynamicSigindice(idx) => { diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index fa4d3020bf6..1cc228c6548 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -524,7 +524,9 @@ pub struct ImportedFunc { pub func_ctx: NonNull, } -// manually implemented because ImportedFunc contains raw pointers directly; `Func` is marked Send (But `Ctx` actually isn't! (TODO: review this, shouldn't `Ctx` be Send?)) +// manually implemented because ImportedFunc contains raw pointers +// directly; `Func` is marked Send (But `Ctx` actually isn't! (TODO: +// review this, shouldn't `Ctx` be Send?)) unsafe impl Send for ImportedFunc {} impl ImportedFunc { @@ -533,7 +535,7 @@ impl ImportedFunc { 0 * (mem::size_of::() as u8) } - pub fn offset_vmctx() -> u8 { + pub fn offset_func_ctx() -> u8 { 1 * (mem::size_of::() as u8) } @@ -756,8 +758,8 @@ mod vm_offset_tests { ); assert_eq!( - ImportedFunc::offset_vmctx() as usize, - offset_of!(ImportedFunc => vmctx).get_byte_offset(), + ImportedFunc::offset_func_ctx() as usize, + offset_of!(ImportedFunc => func_ctx).get_byte_offset(), ); } From 0e27f2fa72361d6fc90d94240af8c5671db5f571 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 13:02:29 +0100 Subject: [PATCH 04/25] test(runtime-core) Test more host functions as closures. --- lib/runtime-core-tests/tests/imports.rs | 54 +++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index 69b9040cdc6..61702e5ac36 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -11,21 +11,37 @@ fn imported_functions_forms() { (type $type (func (param i32) (result i32))) (import "env" "memory" (memory 1 1)) (import "env" "callback_fn" (func $callback_fn (type $type))) + (import "env" "callback_closure" (func $callback_closure (type $type))) (import "env" "callback_fn_with_vmctx" (func $callback_fn_with_vmctx (type $type))) + (import "env" "callback_closure_with_vmctx" (func $callback_closure_with_vmctx (type $type))) (import "env" "callback_fn_trap" (func $callback_fn_trap (type $type))) + (import "env" "callback_closure_trap" (func $callback_closure_trap (type $type))) (import "env" "callback_fn_trap_with_vmctx" (func $callback_fn_trap_with_vmctx (type $type))) + (import "env" "callback_closure_trap_with_vmctx" (func $callback_closure_trap_with_vmctx (type $type))) (func (export "function_fn") (type $type) get_local 0 call $callback_fn) + (func (export "function_closure") (type $type) + get_local 0 + call $callback_closure) (func (export "function_fn_with_vmctx") (type $type) get_local 0 call $callback_fn_with_vmctx) + (func (export "function_closure_with_vmctx") (type $type) + get_local 0 + call $callback_closure_with_vmctx) (func (export "function_fn_trap") (type $type) get_local 0 call $callback_fn_trap) + (func (export "function_closure_trap") (type $type) + get_local 0 + call $callback_closure_trap) (func (export "function_fn_trap_with_vmctx") (type $type) get_local 0 - call $callback_fn_trap_with_vmctx)) + call $callback_fn_trap_with_vmctx) + (func (export "function_closure_trap_with_vmctx") (type $type) + get_local 0 + call $callback_closure_trap_with_vmctx)) "#; let wasm_binary = wat2wasm(MODULE.as_bytes()).expect("WAST not valid or malformed"); @@ -40,9 +56,27 @@ fn imported_functions_forms() { "env" => { "memory" => memory.clone(), "callback_fn" => Func::new(callback_fn), + "callback_closure" => Func::new(|n: i32| -> Result { + Ok(n + 1) + }), "callback_fn_with_vmctx" => Func::new(callback_fn_with_vmctx), + "callback_closure_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { + let memory = vmctx.memory(0); + let shift: i32 = memory.view()[0].get(); + + Ok(shift + n + 1) + }), "callback_fn_trap" => Func::new(callback_fn_trap), + "callback_closure_trap" => Func::new(|n: i32| -> Result { + Err(format!("bar {}", n + 1)) + }), "callback_fn_trap_with_vmctx" => Func::new(callback_fn_trap_with_vmctx), + "callback_closure_trap_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { + let memory = vmctx.memory(0); + let shift: i32 = memory.view()[0].get(); + + Err(format!("qux {}", shift + n + 1)) + }), }, }; let instance = module.instantiate(&import_object).unwrap(); @@ -99,11 +133,19 @@ fn imported_functions_forms() { } call_and_assert!(function_fn, Ok(2)); + call_and_assert!(function_closure, Ok(2)); call_and_assert!(function_fn_with_vmctx, Ok(2 + SHIFT)); + call_and_assert!(function_closure_with_vmctx, Ok(2 + SHIFT)); call_and_assert!( function_fn_trap, Err(RuntimeError::Error { - data: Box::new(format!("foo {}", 1)) + data: Box::new(format!("foo {}", 2)) + }) + ); + call_and_assert!( + function_closure_trap, + Err(RuntimeError::Error { + data: Box::new(format!("bar {}", 2)) }) ); call_and_assert!( @@ -112,6 +154,12 @@ fn imported_functions_forms() { data: Box::new(format!("baz {}", 2 + SHIFT)) }) ); + call_and_assert!( + function_closure_trap_with_vmctx, + Err(RuntimeError::Error { + data: Box::new(format!("qux {}", 2 + SHIFT)) + }) + ); } fn callback_fn(n: i32) -> Result { @@ -126,7 +174,7 @@ fn callback_fn_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result { } fn callback_fn_trap(n: i32) -> Result { - Err(format!("foo {}", n)) + Err(format!("foo {}", n + 1)) } fn callback_fn_trap_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result { From 2a041f898eb9cc8a70a4dd11c328df18e90c50c8 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 14:14:12 +0100 Subject: [PATCH 05/25] !temp --- lib/runtime-core-tests/tests/imports.rs | 18 ++ lib/runtime-core/src/backing.rs | 24 +-- lib/runtime-core/src/typed_func.rs | 265 ++++++++++++++---------- lib/runtime-core/src/vm.rs | 8 +- 4 files changed, 189 insertions(+), 126 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index 61702e5ac36..24fc4ad5f11 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -12,33 +12,46 @@ fn imported_functions_forms() { (import "env" "memory" (memory 1 1)) (import "env" "callback_fn" (func $callback_fn (type $type))) (import "env" "callback_closure" (func $callback_closure (type $type))) + (import "env" "callback_closure_with_env" (func $callback_closure_with_env (type $type))) (import "env" "callback_fn_with_vmctx" (func $callback_fn_with_vmctx (type $type))) (import "env" "callback_closure_with_vmctx" (func $callback_closure_with_vmctx (type $type))) (import "env" "callback_fn_trap" (func $callback_fn_trap (type $type))) (import "env" "callback_closure_trap" (func $callback_closure_trap (type $type))) (import "env" "callback_fn_trap_with_vmctx" (func $callback_fn_trap_with_vmctx (type $type))) (import "env" "callback_closure_trap_with_vmctx" (func $callback_closure_trap_with_vmctx (type $type))) + (func (export "function_fn") (type $type) get_local 0 call $callback_fn) + (func (export "function_closure") (type $type) get_local 0 call $callback_closure) + + (func (export "function_closure_with_env") (type $type) + get_local 0 + call $callback_closure_with_env) + (func (export "function_fn_with_vmctx") (type $type) get_local 0 call $callback_fn_with_vmctx) + (func (export "function_closure_with_vmctx") (type $type) get_local 0 call $callback_closure_with_vmctx) + (func (export "function_fn_trap") (type $type) get_local 0 call $callback_fn_trap) + (func (export "function_closure_trap") (type $type) get_local 0 call $callback_closure_trap) + (func (export "function_fn_trap_with_vmctx") (type $type) get_local 0 call $callback_fn_trap_with_vmctx) + (func (export "function_closure_trap_with_vmctx") (type $type) get_local 0 call $callback_closure_trap_with_vmctx)) @@ -51,6 +64,7 @@ fn imported_functions_forms() { const SHIFT: i32 = 10; memory.view()[0].set(SHIFT); + let shift = 100; let import_object = imports! { "env" => { @@ -59,6 +73,9 @@ fn imported_functions_forms() { "callback_closure" => Func::new(|n: i32| -> Result { Ok(n + 1) }), + "callback_closure_with_env" => Func::new(move |n: i32| -> Result { + Ok(shift + n + 1) + }), "callback_fn_with_vmctx" => Func::new(callback_fn_with_vmctx), "callback_closure_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); @@ -134,6 +151,7 @@ fn imported_functions_forms() { call_and_assert!(function_fn, Ok(2)); call_and_assert!(function_closure, Ok(2)); + call_and_assert!(function_closure_with_env, Ok(2 + shift)); call_and_assert!(function_fn_with_vmctx, Ok(2 + SHIFT)); call_and_assert!(function_closure_with_vmctx, Ok(2 + SHIFT)); call_and_assert!( diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index f9b320b45ea..0f57b2bbf99 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -586,18 +586,18 @@ fn import_functions( if *expected_sig == *signature { functions.push(vm::ImportedFunc { func: func.inner(), - func_ctx: { - let _ = match ctx { - Context::External(ctx) => ctx, - Context::Internal => vmctx, - }; - - NonNull::new(Box::into_raw(Box::new(vm::FuncCtx { - vmctx: NonNull::new(vmctx).expect("`vmctx` must not be null."), - func_env: ptr::null_mut(), - }))) - .unwrap() - }, + func_ctx: NonNull::new(Box::into_raw(Box::new(vm::FuncCtx { + vmctx: NonNull::new(vmctx).expect("`vmctx` must not be null."), + func_env: match ctx { + Context::External(ctx) => { + NonNull::new(ctx).map(|pointer| { + pointer.cast() // `*mut vm::FuncEnv` was casted to `*mut vm::Ctx` to fit in `Context::External`. Cast it back. + }) + } + Context::Internal => None, + }, + }))) + .unwrap(), }); } else { link_errors.push(LinkError::IncorrectImportSignature { diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 049dfc16130..584749ecf0d 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -175,7 +175,7 @@ where Args: WasmTypeList, Rets: WasmTypeList, { - fn to_raw(&self) -> NonNull; + fn to_raw(self) -> (NonNull, Option>); } pub trait TrapEarly @@ -208,10 +208,12 @@ where } /// Represents a function that can be used by WebAssembly. +#[allow(dead_code)] pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { inner: Inner, - f: NonNull, - ctx: *mut vm::Ctx, + func: NonNull, + func_env: Option>, + vmctx: *mut vm::Ctx, _phantom: PhantomData<(&'a (), Args, Rets)>, } @@ -225,19 +227,20 @@ where { pub(crate) unsafe fn from_raw_parts( inner: Wasm, - f: NonNull, - ctx: *mut vm::Ctx, + func: NonNull, + vmctx: *mut vm::Ctx, ) -> Func<'a, Args, Rets, Wasm> { Func { inner, - f, - ctx, + func, + func_env: None, + vmctx, _phantom: PhantomData, } } pub fn get_vm_func(&self) -> NonNull { - self.f + self.func } } @@ -246,15 +249,18 @@ where Args: WasmTypeList, Rets: WasmTypeList, { - pub fn new(f: F) -> Func<'a, Args, Rets, Host> + pub fn new(func: F) -> Func<'a, Args, Rets, Host> where Kind: ExternalFunctionKind, F: ExternalFunction, { + let (func, func_env) = func.to_raw(); + Func { inner: Host(()), - f: f.to_raw(), - ctx: ptr::null_mut(), + func, + func_env, + vmctx: ptr::null_mut(), _phantom: PhantomData, } } @@ -391,7 +397,7 @@ where Rets: WasmTypeList, { pub fn call(&self, a: A) -> Result { - unsafe { ::call(a, self.f, self.inner, self.ctx) } + unsafe { ::call(a, self.func, self.inner, self.vmctx) } } } @@ -482,57 +488,75 @@ macro_rules! impl_traits { $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly, - FN: Fn(&mut vm::Ctx $( , $x )*) -> Trap, + FN: Fn(&mut vm::Ctx $( , $x )*) -> Trap + 'static, { #[allow(non_snake_case)] - fn to_raw(&self) -> NonNull { - if mem::size_of::() == 0 { - /// This is required for the llvm backend to be able to unwind through this function. - #[cfg_attr(nightly, unwind(allowed))] - extern fn wrap<$( $x, )* Rets, Trap, FN>( - func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* - ) -> Rets::CStruct - where - $( $x: WasmExternType, )* - Rets: WasmTypeList, - Trap: TrapEarly, - FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, - { - let vmctx = unsafe { func_ctx.vmctx.as_mut() }; - let f: FN = unsafe { mem::transmute_copy(&()) }; - - let err = match panic::catch_unwind( - panic::AssertUnwindSafe( - || { - f(vmctx $( , WasmExternType::from_native($x) )* ).report() - } - ) - ) { - Ok(Ok(returns)) => return returns.into_c_struct(), - Ok(Err(err)) => { - let b: Box<_> = err.into(); - b as Box - }, - Err(err) => err, - }; - - unsafe { - (&*vmctx.module).runnable_module.do_early_trap(err) - } + fn to_raw(self) -> (NonNull, Option>) { + /// This is required for the llvm backend to be able to unwind through this function. + #[cfg_attr(nightly, unwind(allowed))] + extern fn wrap<$( $x, )* Rets, Trap, FN>( + func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* + ) -> Rets::CStruct + where + $( $x: WasmExternType, )* + Rets: WasmTypeList, + Trap: TrapEarly, + FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, + { + dbg!(func_ctx.vmctx.as_ptr()); + + let vmctx = unsafe { func_ctx.vmctx.as_mut() }; + let func_env = func_ctx.func_env; + + dbg!(func_env); + + let func: &FN = match func_env { + Some(func_env) => unsafe { + let func: NonNull = func_env.cast(); + + &*func.as_ptr() + }, + None => unsafe { mem::transmute_copy(&()) } + }; + + let err = match panic::catch_unwind( + panic::AssertUnwindSafe( + || { + func(vmctx $( , WasmExternType::from_native($x) )* ).report() + } + ) + ) { + Ok(Ok(returns)) => return returns.into_c_struct(), + Ok(Err(err)) => { + let b: Box<_> = err.into(); + b as Box + }, + Err(err) => err, + }; + + unsafe { + (&*vmctx.module).runnable_module.do_early_trap(err) } - - NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap() - } else { - assert_eq!( - mem::size_of::(), - mem::size_of::(), - "you cannot use a closure that captures state for `Func`." - ); - - NonNull::new(unsafe { - mem::transmute_copy::<_, *mut vm::Func>(self) - }).unwrap() } + + let func_env: Option> = + // `FN` is a function pointer, or a closure + // _without_ a captured environment. + if mem::size_of::() == 0 { + None + } + // `FN` is a closure _with_ a captured + // environment. Grab it. + else { + NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast) + }; + + dbg!(func_env); + + ( + NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap(), + func_env + ) } } @@ -541,57 +565,75 @@ macro_rules! impl_traits { $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly, - FN: Fn($( $x, )*) -> Trap, + FN: Fn($( $x, )*) -> Trap + 'static, { #[allow(non_snake_case)] - fn to_raw(&self) -> NonNull { - if mem::size_of::() == 0 { - /// This is required for the llvm backend to be able to unwind through this function. - #[cfg_attr(nightly, unwind(allowed))] - extern fn wrap<$( $x, )* Rets, Trap, FN>( - func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* - ) -> Rets::CStruct - where - $( $x: WasmExternType, )* - Rets: WasmTypeList, - Trap: TrapEarly, - FN: Fn($( $x, )*) -> Trap, - { - let vmctx = unsafe { func_ctx.vmctx.as_mut() }; - let f: FN = unsafe { mem::transmute_copy(&()) }; - - let err = match panic::catch_unwind( - panic::AssertUnwindSafe( - || { - f($( WasmExternType::from_native($x), )* ).report() - } - ) - ) { - Ok(Ok(returns)) => return returns.into_c_struct(), - Ok(Err(err)) => { - let b: Box<_> = err.into(); - b as Box - }, - Err(err) => err, - }; - - unsafe { - (&*vmctx.module).runnable_module.do_early_trap(err) - } + fn to_raw(self) -> (NonNull, Option>) { + /// This is required for the llvm backend to be able to unwind through this function. + #[cfg_attr(nightly, unwind(allowed))] + extern fn wrap<$( $x, )* Rets, Trap, FN>( + func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* + ) -> Rets::CStruct + where + $( $x: WasmExternType, )* + Rets: WasmTypeList, + Trap: TrapEarly, + FN: Fn($( $x, )*) -> Trap, + { + dbg!(func_ctx.vmctx.as_ptr()); + + let vmctx = unsafe { func_ctx.vmctx.as_mut() }; + let func_env = func_ctx.func_env; + + dbg!(func_env); + + let func: &FN = match func_env { + Some(func_env) => unsafe { + let func: NonNull = func_env.cast(); + + &*func.as_ptr() + }, + None => unsafe { mem::transmute_copy(&()) } + }; + + let err = match panic::catch_unwind( + panic::AssertUnwindSafe( + || { + func($( WasmExternType::from_native($x), )* ).report() + } + ) + ) { + Ok(Ok(returns)) => return returns.into_c_struct(), + Ok(Err(err)) => { + let b: Box<_> = err.into(); + b as Box + }, + Err(err) => err, + }; + + unsafe { + (&*vmctx.module).runnable_module.do_early_trap(err) } - - NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap() - } else { - assert_eq!( - mem::size_of::(), - mem::size_of::(), - "you cannot use a closure that captures state for `Func`." - ); - - NonNull::new(unsafe { - mem::transmute_copy::<_, *mut vm::Func>(self) - }).unwrap() } + + let func_env: Option> = + // `FN` is a function pointer, or a closure + // _without_ a captured environment. + if mem::size_of::() == 0 { + None + } + // `FN` is a closure _with_ a captured + // environment. Grab it. + else { + NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast) + }; + + dbg!(func_env); + + ( + NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap(), + func_env + ) } } @@ -606,9 +648,9 @@ macro_rules! impl_traits { unsafe { <( $( $x ),* ) as WasmTypeList>::call( ( $( $x ),* ), - self.f, + self.func, self.inner, - self.ctx + self.vmctx ) } } @@ -646,8 +688,11 @@ where Inner: Kind, { fn to_export(&self) -> Export { - let func = unsafe { FuncPointer::new(self.f.as_ptr()) }; - let ctx = Context::Internal; + let func = unsafe { FuncPointer::new(self.func.as_ptr()) }; + let ctx = match self.func_env { + Some(func_env) => Context::External(func_env.cast().as_ptr()), + None => Context::Internal, + }; let signature = Arc::new(FuncSig::new(Args::types(), Rets::types())); Export::Function { diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 1cc228c6548..dc92c788f9f 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -512,16 +512,16 @@ pub struct FuncEnv { #[derive(Debug)] #[repr(C)] pub struct FuncCtx { - pub vmctx: NonNull, - pub func_env: *mut FuncEnv, + pub(crate) vmctx: NonNull, + pub(crate) func_env: Option>, } /// An imported function, which contains the vmctx that owns this function. #[derive(Debug, Clone)] #[repr(C)] pub struct ImportedFunc { - pub func: *const Func, - pub func_ctx: NonNull, + pub(crate) func: *const Func, + pub(crate) func_ctx: NonNull, } // manually implemented because ImportedFunc contains raw pointers From 3b34a9187a3c941b48455b1bb07c9ea65d0bc79b Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 15:13:10 +0100 Subject: [PATCH 06/25] chore(runtime-core) Remove `dbg!`. --- lib/runtime-core/src/backing.rs | 2 +- lib/runtime-core/src/typed_func.rs | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index 0f57b2bbf99..c6fffb55329 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -576,7 +576,7 @@ fn import_functions( let import = imports.maybe_with_namespace(namespace, |namespace| namespace.get_export(name)); - dbg!(vmctx); + match import { Some(Export::Function { func, diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 584749ecf0d..28693359ba9 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -503,13 +503,9 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, { - dbg!(func_ctx.vmctx.as_ptr()); - let vmctx = unsafe { func_ctx.vmctx.as_mut() }; let func_env = func_ctx.func_env; - dbg!(func_env); - let func: &FN = match func_env { Some(func_env) => unsafe { let func: NonNull = func_env.cast(); @@ -551,8 +547,6 @@ macro_rules! impl_traits { NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast) }; - dbg!(func_env); - ( NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap(), func_env @@ -580,13 +574,9 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn($( $x, )*) -> Trap, { - dbg!(func_ctx.vmctx.as_ptr()); - let vmctx = unsafe { func_ctx.vmctx.as_mut() }; let func_env = func_ctx.func_env; - dbg!(func_env); - let func: &FN = match func_env { Some(func_env) => unsafe { let func: NonNull = func_env.cast(); @@ -628,8 +618,6 @@ macro_rules! impl_traits { NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast) }; - dbg!(func_env); - ( NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap(), func_env From a52b4b2280fb74109c1be8e29e9f8a539288d5d2 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 15:21:09 +0100 Subject: [PATCH 07/25] test(runtime-core) Test closures with a captured environment. --- lib/runtime-core-tests/tests/imports.rs | 57 ++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index 24fc4ad5f11..128a552c53c 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -15,10 +15,12 @@ fn imported_functions_forms() { (import "env" "callback_closure_with_env" (func $callback_closure_with_env (type $type))) (import "env" "callback_fn_with_vmctx" (func $callback_fn_with_vmctx (type $type))) (import "env" "callback_closure_with_vmctx" (func $callback_closure_with_vmctx (type $type))) + (import "env" "callback_closure_with_vmctx_and_env" (func $callback_closure_with_vmctx_and_env (type $type))) (import "env" "callback_fn_trap" (func $callback_fn_trap (type $type))) (import "env" "callback_closure_trap" (func $callback_closure_trap (type $type))) (import "env" "callback_fn_trap_with_vmctx" (func $callback_fn_trap_with_vmctx (type $type))) (import "env" "callback_closure_trap_with_vmctx" (func $callback_closure_trap_with_vmctx (type $type))) + (import "env" "callback_closure_trap_with_vmctx_and_env" (func $callback_closure_trap_with_vmctx_and_env (type $type))) (func (export "function_fn") (type $type) get_local 0 @@ -40,6 +42,10 @@ fn imported_functions_forms() { get_local 0 call $callback_closure_with_vmctx) + (func (export "function_closure_with_vmctx_and_env") (type $type) + get_local 0 + call $callback_closure_with_vmctx_and_env) + (func (export "function_fn_trap") (type $type) get_local 0 call $callback_fn_trap) @@ -54,7 +60,11 @@ fn imported_functions_forms() { (func (export "function_closure_trap_with_vmctx") (type $type) get_local 0 - call $callback_closure_trap_with_vmctx)) + call $callback_closure_trap_with_vmctx) + + (func (export "function_closure_trap_with_vmctx_and_env") (type $type) + get_local 0 + call $callback_closure_trap_with_vmctx_and_env)) "#; let wasm_binary = wat2wasm(MODULE.as_bytes()).expect("WAST not valid or malformed"); @@ -69,31 +79,67 @@ fn imported_functions_forms() { let import_object = imports! { "env" => { "memory" => memory.clone(), + + // Regular function. "callback_fn" => Func::new(callback_fn), + + // Closure without a captured environment. "callback_closure" => Func::new(|n: i32| -> Result { Ok(n + 1) }), + + // Closure with a captured environment (a single variable + an instance of `Memory`). "callback_closure_with_env" => Func::new(move |n: i32| -> Result { + let shift = shift + memory.view::()[0].get(); + Ok(shift + n + 1) }), + + // Regular function with an explicit `vmctx`. "callback_fn_with_vmctx" => Func::new(callback_fn_with_vmctx), + + // Closure without a captured environment but with an explicit `vmctx`. "callback_closure_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); let shift: i32 = memory.view()[0].get(); Ok(shift + n + 1) }), + + // Closure with a captured environment (a single variable) and with an explicit `vmctx`. + "callback_closure_with_vmctx_and_env" => Func::new(move |vmctx: &mut vm::Ctx, n: i32| -> Result { + let memory = vmctx.memory(0); + let shift = shift + memory.view::()[0].get(); + + Ok(shift + n + 1) + }), + + // Trap a regular function. "callback_fn_trap" => Func::new(callback_fn_trap), + + // Trap a closure without a captured environment. "callback_closure_trap" => Func::new(|n: i32| -> Result { Err(format!("bar {}", n + 1)) }), + + // Trap a regular function with an explicit `vmctx`. "callback_fn_trap_with_vmctx" => Func::new(callback_fn_trap_with_vmctx), + + // Trap a closure without a captured environment but with an explicit `vmctx`. "callback_closure_trap_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); let shift: i32 = memory.view()[0].get(); Err(format!("qux {}", shift + n + 1)) }), + + // Trap a closure with a captured environment (a single variable) and with an explicit `vmctx`. + "callback_closure_trap_with_vmctx_and_env" => Func::new(move |vmctx: &mut vm::Ctx, n: i32| -> Result { + let memory = vmctx.memory(0); + let shift = shift + memory.view::()[0].get(); + + Err(format!("! {}", shift + n + 1)) + }), }, }; let instance = module.instantiate(&import_object).unwrap(); @@ -151,9 +197,10 @@ fn imported_functions_forms() { call_and_assert!(function_fn, Ok(2)); call_and_assert!(function_closure, Ok(2)); - call_and_assert!(function_closure_with_env, Ok(2 + shift)); + call_and_assert!(function_closure_with_env, Ok(2 + shift + SHIFT)); call_and_assert!(function_fn_with_vmctx, Ok(2 + SHIFT)); call_and_assert!(function_closure_with_vmctx, Ok(2 + SHIFT)); + call_and_assert!(function_closure_with_vmctx_and_env, Ok(2 + shift + SHIFT)); call_and_assert!( function_fn_trap, Err(RuntimeError::Error { @@ -178,6 +225,12 @@ fn imported_functions_forms() { data: Box::new(format!("qux {}", 2 + SHIFT)) }) ); + call_and_assert!( + function_closure_trap_with_vmctx_and_env, + Err(RuntimeError::Error { + data: Box::new(format!("! {}", 2 + shift + SHIFT)) + }) + ); } fn callback_fn(n: i32) -> Result { From 3435ce436bc0feb3c867c24b7a7a71b1cc1e3dd1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 15:40:20 +0100 Subject: [PATCH 08/25] test(runtime-core) Extract `assert` as tests. --- lib/runtime-core-tests/tests/imports.rs | 242 +++++++++++++----------- 1 file changed, 136 insertions(+), 106 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index 128a552c53c..bba5050f1ae 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -1,11 +1,64 @@ use wasmer_runtime_core::{ compile_with, error::RuntimeError, imports, memory::Memory, typed_func::Func, - types::MemoryDescriptor, units::Pages, vm, + types::MemoryDescriptor, units::Pages, vm, Instance, }; use wasmer_runtime_core_tests::{get_compiler, wat2wasm}; -#[test] -fn imported_functions_forms() { +macro_rules! call_and_assert { + ($instance:ident, $function:ident, $expected_value:expr) => { + let $function: Func = $instance.func(stringify!($function)).unwrap(); + + let result = $function.call(1); + + match (result, $expected_value) { + (Ok(value), expected_value) => assert_eq!( + Ok(value), + expected_value, + concat!("Expected right when calling `", stringify!($function), "`.") + ), + ( + Err(RuntimeError::Error { data }), + Err(RuntimeError::Error { + data: expected_data, + }), + ) => { + if let (Some(data), Some(expected_data)) = ( + data.downcast_ref::<&str>(), + expected_data.downcast_ref::<&str>(), + ) { + assert_eq!( + data, expected_data, + concat!("Expected right when calling `", stringify!($function), "`.") + ) + } else if let (Some(data), Some(expected_data)) = ( + data.downcast_ref::(), + expected_data.downcast_ref::(), + ) { + assert_eq!( + data, expected_data, + concat!("Expected right when calling `", stringify!($function), "`.") + ) + } else { + assert!(false, "Unexpected error, cannot compare it.") + } + } + (result, expected_value) => assert!( + false, + format!( + "Unexpected assertion for `{}`: left = `{:?}`, right = `{:?}`.", + stringify!($function), + result, + expected_value + ) + ), + } + }; +} + +const SHIFT: i32 = 10; +const shift: i32 = 100; + +fn imported_functions_forms(test: &dyn Fn(&Instance)) { const MODULE: &str = r#" (module (type $type (func (param i32) (result i32))) @@ -72,9 +125,7 @@ fn imported_functions_forms() { let memory_descriptor = MemoryDescriptor::new(Pages(1), Some(Pages(1)), false).unwrap(); let memory = Memory::new(memory_descriptor).unwrap(); - const SHIFT: i32 = 10; memory.view()[0].set(SHIFT); - let shift = 100; let import_object = imports! { "env" => { @@ -90,9 +141,9 @@ fn imported_functions_forms() { // Closure with a captured environment (a single variable + an instance of `Memory`). "callback_closure_with_env" => Func::new(move |n: i32| -> Result { - let shift = shift + memory.view::()[0].get(); + let shift_ = shift + memory.view::()[0].get(); - Ok(shift + n + 1) + Ok(shift_ + n + 1) }), // Regular function with an explicit `vmctx`. @@ -101,17 +152,17 @@ fn imported_functions_forms() { // Closure without a captured environment but with an explicit `vmctx`. "callback_closure_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); - let shift: i32 = memory.view()[0].get(); + let shift_: i32 = memory.view()[0].get(); - Ok(shift + n + 1) + Ok(shift_ + n + 1) }), // Closure with a captured environment (a single variable) and with an explicit `vmctx`. "callback_closure_with_vmctx_and_env" => Func::new(move |vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); - let shift = shift + memory.view::()[0].get(); + let shift_ = shift + memory.view::()[0].get(); - Ok(shift + n + 1) + Ok(shift_ + n + 1) }), // Trap a regular function. @@ -128,109 +179,23 @@ fn imported_functions_forms() { // Trap a closure without a captured environment but with an explicit `vmctx`. "callback_closure_trap_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); - let shift: i32 = memory.view()[0].get(); + let shift_: i32 = memory.view()[0].get(); - Err(format!("qux {}", shift + n + 1)) + Err(format!("qux {}", shift_ + n + 1)) }), // Trap a closure with a captured environment (a single variable) and with an explicit `vmctx`. "callback_closure_trap_with_vmctx_and_env" => Func::new(move |vmctx: &mut vm::Ctx, n: i32| -> Result { let memory = vmctx.memory(0); - let shift = shift + memory.view::()[0].get(); + let shift_ = shift + memory.view::()[0].get(); - Err(format!("! {}", shift + n + 1)) + Err(format!("! {}", shift_ + n + 1)) }), }, }; let instance = module.instantiate(&import_object).unwrap(); - macro_rules! call_and_assert { - ($function:ident, $expected_value:expr) => { - let $function: Func = instance.func(stringify!($function)).unwrap(); - - let result = $function.call(1); - - match (result, $expected_value) { - (Ok(value), expected_value) => assert_eq!( - Ok(value), - expected_value, - concat!("Expected right when calling `", stringify!($function), "`.") - ), - ( - Err(RuntimeError::Error { data }), - Err(RuntimeError::Error { - data: expected_data, - }), - ) => { - if let (Some(data), Some(expected_data)) = ( - data.downcast_ref::<&str>(), - expected_data.downcast_ref::<&str>(), - ) { - assert_eq!( - data, expected_data, - concat!("Expected right when calling `", stringify!($function), "`.") - ) - } else if let (Some(data), Some(expected_data)) = ( - data.downcast_ref::(), - expected_data.downcast_ref::(), - ) { - assert_eq!( - data, expected_data, - concat!("Expected right when calling `", stringify!($function), "`.") - ) - } else { - assert!(false, "Unexpected error, cannot compare it.") - } - } - (result, expected_value) => assert!( - false, - format!( - "Unexpected assertion for `{}`: left = `{:?}`, right = `{:?}`.", - stringify!($function), - result, - expected_value - ) - ), - } - }; - } - - call_and_assert!(function_fn, Ok(2)); - call_and_assert!(function_closure, Ok(2)); - call_and_assert!(function_closure_with_env, Ok(2 + shift + SHIFT)); - call_and_assert!(function_fn_with_vmctx, Ok(2 + SHIFT)); - call_and_assert!(function_closure_with_vmctx, Ok(2 + SHIFT)); - call_and_assert!(function_closure_with_vmctx_and_env, Ok(2 + shift + SHIFT)); - call_and_assert!( - function_fn_trap, - Err(RuntimeError::Error { - data: Box::new(format!("foo {}", 2)) - }) - ); - call_and_assert!( - function_closure_trap, - Err(RuntimeError::Error { - data: Box::new(format!("bar {}", 2)) - }) - ); - call_and_assert!( - function_fn_trap_with_vmctx, - Err(RuntimeError::Error { - data: Box::new(format!("baz {}", 2 + SHIFT)) - }) - ); - call_and_assert!( - function_closure_trap_with_vmctx, - Err(RuntimeError::Error { - data: Box::new(format!("qux {}", 2 + SHIFT)) - }) - ); - call_and_assert!( - function_closure_trap_with_vmctx_and_env, - Err(RuntimeError::Error { - data: Box::new(format!("! {}", 2 + shift + SHIFT)) - }) - ); + test(&instance); } fn callback_fn(n: i32) -> Result { @@ -239,9 +204,9 @@ fn callback_fn(n: i32) -> Result { fn callback_fn_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result { let memory = vmctx.memory(0); - let shift: i32 = memory.view()[0].get(); + let shift_: i32 = memory.view()[0].get(); - Ok(shift + n + 1) + Ok(shift_ + n + 1) } fn callback_fn_trap(n: i32) -> Result { @@ -250,7 +215,72 @@ fn callback_fn_trap(n: i32) -> Result { fn callback_fn_trap_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result { let memory = vmctx.memory(0); - let shift: i32 = memory.view()[0].get(); + let shift_: i32 = memory.view()[0].get(); - Err(format!("baz {}", shift + n + 1)) + Err(format!("baz {}", shift_ + n + 1)) } + +macro_rules! test { + ($test_name:ident, $function:ident, $expected_value:expr) => { + #[test] + fn $test_name() { + imported_functions_forms(&|instance| { + call_and_assert!(instance, $function, $expected_value); + }); + } + }; +} + +test!(test_fn, function_fn, Ok(2)); +test!(test_closure, function_closure, Ok(2)); +test!( + test_closure_with_env, + function_closure_with_env, + Ok(2 + shift + SHIFT) +); +test!(test_fn_with_vmctx, function_fn_with_vmctx, Ok(2 + SHIFT)); +test!( + test_closure_with_vmctx, + function_closure_with_vmctx, + Ok(2 + SHIFT) +); +test!( + test_closure_with_vmctx_and_env, + function_closure_with_vmctx_and_env, + Ok(2 + shift + SHIFT) +); +test!( + test_fn_trap, + function_fn_trap, + Err(RuntimeError::Error { + data: Box::new(format!("foo {}", 2)) + }) +); +test!( + test_closure_trap, + function_closure_trap, + Err(RuntimeError::Error { + data: Box::new(format!("bar {}", 2)) + }) +); +test!( + test_fn_trap_with_vmctx, + function_fn_trap_with_vmctx, + Err(RuntimeError::Error { + data: Box::new(format!("baz {}", 2 + SHIFT)) + }) +); +test!( + test_closure_trap_with_vmctx, + function_closure_trap_with_vmctx, + Err(RuntimeError::Error { + data: Box::new(format!("qux {}", 2 + SHIFT)) + }) +); +test!( + test_closure_trap_with_vmctx_and_env, + function_closure_trap_with_vmctx_and_env, + Err(RuntimeError::Error { + data: Box::new(format!("! {}", 2 + shift + SHIFT)) + }) +); From 81326cee13f09f76bd80250f4703d63e7108b9a9 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 21:45:23 +0100 Subject: [PATCH 09/25] doc(runtime-core) Write more documentation. --- lib/runtime-core/src/typed_func.rs | 52 ++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 28693359ba9..6bc287bae3b 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -208,7 +208,6 @@ where } /// Represents a function that can be used by WebAssembly. -#[allow(dead_code)] pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { inner: Inner, func: NonNull, @@ -492,7 +491,14 @@ macro_rules! impl_traits { { #[allow(non_snake_case)] fn to_raw(self) -> (NonNull, Option>) { - /// This is required for the llvm backend to be able to unwind through this function. + // The `wrap` function is a wrapper around the + // imported function. It manages the argument passed + // to the imported function (in this case, the + // `vmctx` along with the regular WebAssembly + // arguments), and it manages the trapping. + // + // It is also required for the LLVM backend to be + // able to unwind through this function. #[cfg_attr(nightly, unwind(allowed))] extern fn wrap<$( $x, )* Rets, Trap, FN>( func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* @@ -503,22 +509,36 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, { + // Extract `vm::Ctx` from `vm::FuncCtx`. The + // pointer is always non-null. let vmctx = unsafe { func_ctx.vmctx.as_mut() }; + + // Extract `vm::FuncEnv` from `vm::FuncCtx`. let func_env = func_ctx.func_env; let func: &FN = match func_env { + // The imported function is a closure with a + // captured environment. Some(func_env) => unsafe { let func: NonNull = func_env.cast(); &*func.as_ptr() }, + + // The imported function is a regular function + // or a closure without a captured + // environment. None => unsafe { mem::transmute_copy(&()) } }; + // Catch unwind in case of errors. let err = match panic::catch_unwind( panic::AssertUnwindSafe( || { func(vmctx $( , WasmExternType::from_native($x) )* ).report() + // ^^^^^ The imported function + // expects `vm::Ctx` as first + // argument; provide it. } ) ) { @@ -530,11 +550,15 @@ macro_rules! impl_traits { Err(err) => err, }; + // At this point, there is an error that needs to + // be trapped. unsafe { (&*vmctx.module).runnable_module.do_early_trap(err) } } + // Extract the captured environment of the imported + // function if any. let func_env: Option> = // `FN` is a function pointer, or a closure // _without_ a captured environment. @@ -563,7 +587,14 @@ macro_rules! impl_traits { { #[allow(non_snake_case)] fn to_raw(self) -> (NonNull, Option>) { - /// This is required for the llvm backend to be able to unwind through this function. + // The `wrap` function is a wrapper around the + // imported function. It manages the argument passed + // to the imported function (in this case, only the + // regular WebAssembly arguments), and it manages the + // trapping. + // + // It is also required for the LLVM backend to be + // able to unwind through this function. #[cfg_attr(nightly, unwind(allowed))] extern fn wrap<$( $x, )* Rets, Trap, FN>( func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* @@ -574,18 +605,29 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn($( $x, )*) -> Trap, { + // Extract `vm::Ctx` from `vm::FuncCtx`. The + // pointer is always non-null. let vmctx = unsafe { func_ctx.vmctx.as_mut() }; + + // Extract `vm::FuncEnv` from `vm::FuncCtx`. let func_env = func_ctx.func_env; let func: &FN = match func_env { + // The imported function is a closure with a + // captured environment. Some(func_env) => unsafe { let func: NonNull = func_env.cast(); &*func.as_ptr() }, + + // The imported function is a regular function + // or a closure without a captured + // environment. None => unsafe { mem::transmute_copy(&()) } }; + // Catch unwind in case of errors. let err = match panic::catch_unwind( panic::AssertUnwindSafe( || { @@ -601,11 +643,15 @@ macro_rules! impl_traits { Err(err) => err, }; + // At this point, there is an error that needs to + // be trapped. unsafe { (&*vmctx.module).runnable_module.do_early_trap(err) } } + // Extract the captured environment of the imported + // function if any. let func_env: Option> = // `FN` is a function pointer, or a closure // _without_ a captured environment. From 0f82cd3c4090f9538c3d81ae3377dc88bffb6cb4 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 21:50:19 +0100 Subject: [PATCH 10/25] doc(runtime-core) Write more documentation. --- lib/runtime-core/src/vm.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index dc92c788f9f..240f71e1cab 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -498,25 +498,33 @@ impl Ctx { } } -/// Used to provide type safety (ish) for passing around function pointers. +/// Represents a function pointer. It is mostly used in the +/// `typed_func` module within the `wrap` functions, to wrap imported +/// functions. #[repr(C)] pub struct Func { _private: [u8; 0], } +/// Represents a function environment pointer, like a captured +/// environment of a closure. It is mostly used in the `typed_func` +/// module within the `wrap` functions, to wrap imported functions. #[repr(C)] pub struct FuncEnv { _private: [u8; 0], } +/// Represents a function context. It is used by imported function +/// only. #[derive(Debug)] #[repr(C)] -pub struct FuncCtx { +pub(crate) struct FuncCtx { pub(crate) vmctx: NonNull, pub(crate) func_env: Option>, } -/// An imported function, which contains the vmctx that owns this function. +/// An imported function is a function pointer associated to a +/// function context. #[derive(Debug, Clone)] #[repr(C)] pub struct ImportedFunc { @@ -524,7 +532,7 @@ pub struct ImportedFunc { pub(crate) func_ctx: NonNull, } -// manually implemented because ImportedFunc contains raw pointers +// Manually implemented because ImportedFunc contains raw pointers // directly; `Func` is marked Send (But `Ctx` actually isn't! (TODO: // review this, shouldn't `Ctx` be Send?)) unsafe impl Send for ImportedFunc {} From 293b71ac54c72858d875a371962d453cefe5f299 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 21:56:36 +0100 Subject: [PATCH 11/25] doc(runtime-core) Write more documentation. --- lib/runtime-core/src/backing.rs | 12 +++++++++--- lib/runtime-core/src/typed_func.rs | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index c6fffb55329..2493e9d35bc 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -547,6 +547,7 @@ impl ImportBacking { impl Drop for ImportBacking { fn drop(&mut self) { + // Properly drop the `vm::FuncCtx` in `vm::ImportedFunc`. for (_imported_func_index, imported_func) in (*self.vm_functions).iter_mut() { let _: Box = unsafe { Box::from_raw(imported_func.func_ctx.as_ptr()) }; } @@ -587,12 +588,17 @@ fn import_functions( functions.push(vm::ImportedFunc { func: func.inner(), func_ctx: NonNull::new(Box::into_raw(Box::new(vm::FuncCtx { + // ^^^^^^^^ `vm::FuncCtx` is purposely leaked. + // It is dropped by the specific `Drop` + // implementation of `ImportBacking`. vmctx: NonNull::new(vmctx).expect("`vmctx` must not be null."), func_env: match ctx { Context::External(ctx) => { - NonNull::new(ctx).map(|pointer| { - pointer.cast() // `*mut vm::FuncEnv` was casted to `*mut vm::Ctx` to fit in `Context::External`. Cast it back. - }) + NonNull::new(ctx).map(NonNull::cast) + // ^^^^^^^^^^^^^ + // `*mut vm::FuncEnv` was casted to + // `*mut vm::Ctx` to fit in + // `Context::External`. Cast it back. } Context::Internal => None, }, diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 6bc287bae3b..6d7f79e4ca9 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -725,6 +725,10 @@ where let func = unsafe { FuncPointer::new(self.func.as_ptr()) }; let ctx = match self.func_env { Some(func_env) => Context::External(func_env.cast().as_ptr()), + // ^^^^^^ + // `Context::External` expects a `vm::Ctx`. + // Casting to `vm::FuncCtx` happens in the + // `backing` module. None => Context::Internal, }; let signature = Arc::new(FuncSig::new(Args::types(), Rets::types())); From a9e0e9baebf630b81aa4ca7f9fef759ab35d4973 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 4 Nov 2019 21:59:18 +0100 Subject: [PATCH 12/25] test(runtime-core) Write more documentation. --- lib/runtime-core-tests/tests/imports.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index bba5050f1ae..cbdc5a38973 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -55,7 +55,14 @@ macro_rules! call_and_assert { }; } +/// The shift that is set in the instance memory. The value is part of +/// the result returned by the imported functions if the memory is +/// read properly. const SHIFT: i32 = 10; + +/// The shift that is captured in the environment of a closure. The +/// value is part of the result returned by the imported function if +/// the closure captures its environment properly. const shift: i32 = 100; fn imported_functions_forms(test: &dyn Fn(&Instance)) { From a4ba429ed0e80b479101ae7a3cd4a1cbf868b509 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 5 Nov 2019 15:07:37 +0100 Subject: [PATCH 13/25] feat(singlepass-backend) Inject `FuncCtx` to the function pointer of an host function. --- lib/singlepass-backend/src/codegen_x64.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index f4411a74ce4..3ee60babf4f 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -554,18 +554,24 @@ impl ModuleCodeGenerator // Emits a tail call trampoline that loads the address of the target import function // from Ctx and jumps to it. + let imported_funcs_addr = vm::Ctx::offset_imported_funcs(); + let imported_func = vm::ImportedFunc::size() as usize * id; + let imported_func_addr = imported_func + vm::ImportedFunc::offset_func() as usize; + let imported_func_ctx_addr = imported_func + vm::ImportedFunc::offset_func_ctx() as usize; + a.emit_mov( Size::S64, - Location::Memory(GPR::RDI, vm::Ctx::offset_imported_funcs() as i32), + Location::Memory(GPR::RDI, imported_funcs_addr as i32), Location::GPR(GPR::RAX), ); a.emit_mov( Size::S64, - Location::Memory( - GPR::RAX, - (vm::ImportedFunc::size() as usize * id + vm::ImportedFunc::offset_func() as usize) - as i32, - ), + Location::Memory(GPR::RAX, imported_func_ctx_addr as i32), + Location::GPR(GPR::RDI), + ); + a.emit_mov( + Size::S64, + Location::Memory(GPR::RAX, imported_func_addr as i32), Location::GPR(GPR::RAX), ); a.emit_jmp_location(Location::GPR(GPR::RAX)); From c4c88f8af55a9faa3b47e409e2d0779ab97b2c8d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 6 Nov 2019 13:58:49 +0100 Subject: [PATCH 14/25] fix(runtime-core) Remove undefined behavior with `mem::transmute`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the `wrap` functions, we use `std::mem::transmute(&())` to get the pointer to the value “around” `wrap` (`Fn` has a method `to_raw` which declares a `wrap` function, which uses `transmute` to retrieve `Fn`). This is an undefined behavior. It was working until the `FuncCtx` is introduced. Since then, the undefined behavior was causing an error with the Singlepass backend. This patch stores the pointer to `Fn` in `func_env`, so that the pointer to the user-defined host function is always predictable. --- lib/runtime-core/src/typed_func.rs | 28 ++++++++++++++-------------- lib/runtime-core/src/vm.rs | 8 +++++++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 6d7f79e4ca9..9c798460d35 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -517,18 +517,18 @@ macro_rules! impl_traits { let func_env = func_ctx.func_env; let func: &FN = match func_env { - // The imported function is a closure with a - // captured environment. + // The imported function is a regular + // function, a closure without a captured + // environmet, or a closure with a captured + // environment. Some(func_env) => unsafe { let func: NonNull = func_env.cast(); &*func.as_ptr() }, - // The imported function is a regular function - // or a closure without a captured - // environment. - None => unsafe { mem::transmute_copy(&()) } + // This branch is supposed to be unreachable. + None => unreachable!() }; // Catch unwind in case of errors. @@ -563,7 +563,7 @@ macro_rules! impl_traits { // `FN` is a function pointer, or a closure // _without_ a captured environment. if mem::size_of::() == 0 { - None + NonNull::new(&self as *const _ as *mut vm::FuncEnv) } // `FN` is a closure _with_ a captured // environment. Grab it. @@ -613,18 +613,18 @@ macro_rules! impl_traits { let func_env = func_ctx.func_env; let func: &FN = match func_env { - // The imported function is a closure with a - // captured environment. + // The imported function is a regular + // function, a closure without a captured + // environmet, or a closure with a captured + // environment. Some(func_env) => unsafe { let func: NonNull = func_env.cast(); &*func.as_ptr() }, - // The imported function is a regular function - // or a closure without a captured - // environment. - None => unsafe { mem::transmute_copy(&()) } + // This branch is supposed to be unreachable. + None => unreachable!() }; // Catch unwind in case of errors. @@ -656,7 +656,7 @@ macro_rules! impl_traits { // `FN` is a function pointer, or a closure // _without_ a captured environment. if mem::size_of::() == 0 { - None + NonNull::new(&self as *const _ as *mut vm::FuncEnv) } // `FN` is a closure _with_ a captured // environment. Grab it. diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 240f71e1cab..1c9a8b7add4 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -514,12 +514,18 @@ pub struct FuncEnv { _private: [u8; 0], } -/// Represents a function context. It is used by imported function +/// Represents a function context. It is used by imported functions /// only. #[derive(Debug)] #[repr(C)] pub(crate) struct FuncCtx { + /// The `Ctx` pointer. pub(crate) vmctx: NonNull, + + /// A pointer to the function environment. It is used by imported + /// functions only to store the pointer to the real host function, + /// whether it is a regular function, or a closure with or without + /// a captured environment. pub(crate) func_env: Option>, } From 5ccaf12b2a2217de3c079aa8409e4970b989b780 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 6 Nov 2019 14:23:26 +0100 Subject: [PATCH 15/25] doc(runtime-core) Fix inline documentations. --- lib/runtime-core/src/typed_func.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 9c798460d35..8c6a7826d86 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -519,7 +519,7 @@ macro_rules! impl_traits { let func: &FN = match func_env { // The imported function is a regular // function, a closure without a captured - // environmet, or a closure with a captured + // environment, or a closure with a captured // environment. Some(func_env) => unsafe { let func: NonNull = func_env.cast(); @@ -566,7 +566,7 @@ macro_rules! impl_traits { NonNull::new(&self as *const _ as *mut vm::FuncEnv) } // `FN` is a closure _with_ a captured - // environment. Grab it. + // environment. else { NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast) }; @@ -615,7 +615,7 @@ macro_rules! impl_traits { let func: &FN = match func_env { // The imported function is a regular // function, a closure without a captured - // environmet, or a closure with a captured + // environment, or a closure with a captured // environment. Some(func_env) => unsafe { let func: NonNull = func_env.cast(); @@ -659,7 +659,7 @@ macro_rules! impl_traits { NonNull::new(&self as *const _ as *mut vm::FuncEnv) } // `FN` is a closure _with_ a captured - // environment. Grab it. + // environment. else { NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast) }; From 6f84a6f15be9feff42abdd4294fecd1b1c878475 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 6 Nov 2019 14:23:45 +0100 Subject: [PATCH 16/25] test(runtime-core) Remove a warning in tests. --- lib/runtime-core-tests/tests/imports.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index cbdc5a38973..6b7223c6735 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -63,6 +63,7 @@ const SHIFT: i32 = 10; /// The shift that is captured in the environment of a closure. The /// value is part of the result returned by the imported function if /// the closure captures its environment properly. +#[allow(non_upper_case_globals)] const shift: i32 = 100; fn imported_functions_forms(test: &dyn Fn(&Instance)) { From ac8aece380eebcc7b1aa02b84d9ea3a7e24fe319 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 6 Nov 2019 14:57:10 +0100 Subject: [PATCH 17/25] doc(changelog) Add #915, #917 and #925. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afe906514e8..f09f738dc31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## **[Unreleased]** +- [#925](https://github.com/wasmerio/wasmer/pull/925) Host functions can be closures with a captured environment. +- [#917](https://github.com/wasmerio/wasmer/pull/917) Host functions (aka imported functions) may not have `&mut vm::Ctx` as first argument, i.e. the presence of the `&mut vm::Ctx` argument is optional. +- [#915](https://github.com/wasmerio/wasmer/pull/915) All backends share the same definition of `Trampoline` (defined in `wasmer-runtime-core`). - [#921](https://github.com/wasmerio/wasmer/pull/921) In LLVM backend, annotate all memory accesses with TBAA metadata. - [#883](https://github.com/wasmerio/wasmer/pull/883) Allow floating point operations to have arbitrary inputs, even including SNaNs. - [#856](https://github.com/wasmerio/wasmer/pull/856) Expose methods in the runtime C API to get a WASI import object From dfaad35f8dabc3a8143c48604a00aa9c44dc04b6 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Nov 2019 14:31:43 +0100 Subject: [PATCH 18/25] fix(spectests) Remove a warning. --- lib/spectests/examples/test.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/spectests/examples/test.rs b/lib/spectests/examples/test.rs index 8ce199695fc..006fc1397d2 100644 --- a/lib/spectests/examples/test.rs +++ b/lib/spectests/examples/test.rs @@ -1,5 +1,4 @@ use wabt::wat2wasm; -use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core::{backend::Compiler, import::ImportObject, Instance}; fn main() { From 2e05104d45b91f0efe448b82cb6814972b465e92 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Nov 2019 14:32:19 +0100 Subject: [PATCH 19/25] fix(runtime-core) Introduce `Context::ExternalWithEnv`. Host functions use `Context::External` with a `*mut vm::FuncCtx` pointer, casted to `*mut vm::Ctx`. It creates a conflict with exports that also use `Context::External`. This patch introduces `Context::ExternalWithEnv` to create a specific path in the code for an external context with `*mut vm::FuncEnv`. This patch fixes all the `linking.wast` tests in the spectests. --- lib/runtime-core/src/backing.rs | 23 ++++++++++++++--------- lib/runtime-core/src/export.rs | 3 ++- lib/runtime-core/src/instance.rs | 2 +- lib/runtime-core/src/typed_func.rs | 6 +----- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index 2493e9d35bc..7449403fa62 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -591,16 +591,21 @@ fn import_functions( // ^^^^^^^^ `vm::FuncCtx` is purposely leaked. // It is dropped by the specific `Drop` // implementation of `ImportBacking`. - vmctx: NonNull::new(vmctx).expect("`vmctx` must not be null."), - func_env: match ctx { - Context::External(ctx) => { - NonNull::new(ctx).map(NonNull::cast) - // ^^^^^^^^^^^^^ - // `*mut vm::FuncEnv` was casted to - // `*mut vm::Ctx` to fit in - // `Context::External`. Cast it back. + vmctx: NonNull::new(match ctx { + Context::External(vmctx) => vmctx, + Context::ExternalWithEnv(vmctx_, _) => { + if vmctx_.is_null() { + vmctx + } else { + vmctx_ + } } - Context::Internal => None, + _ => vmctx, + }) + .expect("`vmctx` must not be null."), + func_env: match ctx { + Context::ExternalWithEnv(_, func_env) => Some(func_env), + _ => None, }, }))) .unwrap(), diff --git a/lib/runtime-core/src/export.rs b/lib/runtime-core/src/export.rs index 7960d76e699..88ec07e402c 100644 --- a/lib/runtime-core/src/export.rs +++ b/lib/runtime-core/src/export.rs @@ -3,11 +3,12 @@ use crate::{ module::ModuleInner, table::Table, types::FuncSig, vm, }; use indexmap::map::Iter as IndexMapIter; -use std::sync::Arc; +use std::{ptr::NonNull, sync::Arc}; #[derive(Debug, Copy, Clone)] pub enum Context { External(*mut vm::Ctx), + ExternalWithEnv(*mut vm::Ctx, NonNull), Internal, } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 5cf92a312f9..53a5681ff5d 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -413,6 +413,7 @@ impl InstanceInner { ctx: match ctx { Context::Internal => Context::External(self.vmctx), ctx @ Context::External(_) => ctx, + func_ctx @ Context::ExternalWithEnv(_, _) => func_ctx, }, signature, } @@ -463,7 +464,6 @@ impl InstanceInner { }; let signature = SigRegistry.lookup_signature_ref(&module.info.signatures[sig_index]); - // let signature = &module.info.signatures[sig_index]; (unsafe { FuncPointer::new(func_ptr) }, ctx, signature) } diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 8c6a7826d86..cce0581826a 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -724,11 +724,7 @@ where fn to_export(&self) -> Export { let func = unsafe { FuncPointer::new(self.func.as_ptr()) }; let ctx = match self.func_env { - Some(func_env) => Context::External(func_env.cast().as_ptr()), - // ^^^^^^ - // `Context::External` expects a `vm::Ctx`. - // Casting to `vm::FuncCtx` happens in the - // `backing` module. + Some(func_env) => Context::ExternalWithEnv(self.vmctx, func_env), None => Context::Internal, }; let signature = Arc::new(FuncSig::new(Args::types(), Rets::types())); From ba87af5b1a78a9b7e2f58fb8fdca75df0d4b172d Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 7 Nov 2019 20:44:17 +0100 Subject: [PATCH 20/25] feat(runtime-core) Ability for an export function to get a func env. --- lib/runtime-core/src/backing.rs | 4 ++-- lib/runtime-core/src/export.rs | 2 +- lib/runtime-core/src/instance.rs | 36 ++++++++++++++++++------------ lib/runtime-core/src/typed_func.rs | 5 +++-- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index 7449403fa62..dc000957e93 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -600,11 +600,11 @@ fn import_functions( vmctx_ } } - _ => vmctx, + Context::Internal => vmctx, }) .expect("`vmctx` must not be null."), func_env: match ctx { - Context::ExternalWithEnv(_, func_env) => Some(func_env), + Context::ExternalWithEnv(_, func_env) => func_env, _ => None, }, }))) diff --git a/lib/runtime-core/src/export.rs b/lib/runtime-core/src/export.rs index 88ec07e402c..54427cf6bd0 100644 --- a/lib/runtime-core/src/export.rs +++ b/lib/runtime-core/src/export.rs @@ -8,7 +8,7 @@ use std::{ptr::NonNull, sync::Arc}; #[derive(Debug, Copy, Clone)] pub enum Context { External(*mut vm::Ctx), - ExternalWithEnv(*mut vm::Ctx, NonNull), + ExternalWithEnv(*mut vm::Ctx, Option>), Internal, } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index 53a5681ff5d..ae5eef69579 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -133,7 +133,7 @@ impl Instance { .expect("wasm trampoline"); let start_func: Func<(), (), Wasm> = - unsafe { Func::from_raw_parts(wasm_trampoline, func_ptr, ctx_ptr) }; + unsafe { Func::from_raw_parts(wasm_trampoline, func_ptr, None, ctx_ptr) }; start_func.call()?; } @@ -214,20 +214,26 @@ impl Instance { .get_trampoline(&self.module.info, sig_index) .unwrap(); - let func_ptr = match func_index.local_or_import(&self.module.info) { - LocalOrImport::Local(local_func_index) => self - .module - .runnable_module - .get_func(&self.module.info, local_func_index) - .unwrap(), - LocalOrImport::Import(import_func_index) => NonNull::new( - self.inner.import_backing.vm_functions[import_func_index].func as *mut _, - ) - .unwrap(), + let (func_ptr, func_env) = match func_index.local_or_import(&self.module.info) { + LocalOrImport::Local(local_func_index) => ( + self.module + .runnable_module + .get_func(&self.module.info, local_func_index) + .unwrap(), + None, + ), + LocalOrImport::Import(import_func_index) => { + let imported_func = &self.inner.import_backing.vm_functions[import_func_index]; + + ( + NonNull::new(imported_func.func as *mut _).unwrap(), + unsafe { imported_func.func_ctx.as_ref() }.func_env, + ) + } }; let typed_func: Func = - unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, ctx) }; + unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, func_env, ctx) }; Ok(typed_func) } else { @@ -413,7 +419,7 @@ impl InstanceInner { ctx: match ctx { Context::Internal => Context::External(self.vmctx), ctx @ Context::External(_) => ctx, - func_ctx @ Context::ExternalWithEnv(_, _) => func_ctx, + ctx @ Context::ExternalWithEnv(_, _) => ctx, }, signature, } @@ -456,9 +462,11 @@ impl InstanceInner { ), LocalOrImport::Import(imported_func_index) => { let imported_func = &self.import_backing.vm_functions[imported_func_index]; + let func_ctx = unsafe { imported_func.func_ctx.as_ref() }; + ( imported_func.func as *const _, - Context::External(unsafe { imported_func.func_ctx.as_ref() }.vmctx.as_ptr()), + Context::ExternalWithEnv(func_ctx.vmctx.as_ptr(), func_ctx.func_env), ) } }; diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index cce0581826a..7bb7e63cccb 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -227,12 +227,13 @@ where pub(crate) unsafe fn from_raw_parts( inner: Wasm, func: NonNull, + func_env: Option>, vmctx: *mut vm::Ctx, ) -> Func<'a, Args, Rets, Wasm> { Func { inner, func, - func_env: None, + func_env, vmctx, _phantom: PhantomData, } @@ -724,7 +725,7 @@ where fn to_export(&self) -> Export { let func = unsafe { FuncPointer::new(self.func.as_ptr()) }; let ctx = match self.func_env { - Some(func_env) => Context::ExternalWithEnv(self.vmctx, func_env), + func_env @ Some(_) => Context::ExternalWithEnv(self.vmctx, func_env), None => Context::Internal, }; let signature = Arc::new(FuncSig::new(Args::types(), Rets::types())); From 98e4ef066a0354a3efdbfe2af0eeeb0101e8ff57 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 11 Nov 2019 21:45:46 +0100 Subject: [PATCH 21/25] feat(runtime-core) Feed imported functions with `vm::Ctx` again. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … and look for the associated `vm::FuncCtx`. This way, we don't break the rule: “all functions receive a vmctx pointer as first argument.”. --- lib/clif-backend/src/code.rs | 19 +++++++++++--- lib/runtime-core/src/typed_func.rs | 42 ++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/lib/clif-backend/src/code.rs b/lib/clif-backend/src/code.rs index d07b5ce6dda..157187e77b6 100644 --- a/lib/clif-backend/src/code.rs +++ b/lib/clif-backend/src/code.rs @@ -691,7 +691,9 @@ impl FuncEnvironment for FunctionEnvironment { } /// Generates a call IR with `callee` and `call_args` and inserts it at `pos` - /// TODO: add support for imported functions + /// + /// It's about generating code that calls a local or imported function; in + /// WebAssembly: `(call $foo)`. fn translate_call( &mut self, mut pos: FuncCursor, @@ -771,14 +773,23 @@ impl FuncEnvironment for FunctionEnvironment { readonly: true, }); + let imported_func_ctx_vmctx_addr = + pos.func.create_global_value(ir::GlobalValueData::Load { + base: imported_func_ctx_addr, + offset: (0 as i32).into(), + global_type: ptr_type, + readonly: true, + }); + let imported_func_addr = pos.ins().global_value(ptr_type, imported_func_addr); - let imported_func_ctx_addr = - pos.ins().global_value(ptr_type, imported_func_ctx_addr); + let imported_func_ctx_vmctx_addr = pos + .ins() + .global_value(ptr_type, imported_func_ctx_vmctx_addr); let sig_ref = pos.func.dfg.ext_funcs[callee].signature; let mut args = Vec::with_capacity(call_args.len() + 1); - args.push(imported_func_ctx_addr); + args.push(imported_func_ctx_vmctx_addr); args.extend(call_args.iter().cloned()); Ok(pos diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 7bb7e63cccb..0402856162c 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -502,7 +502,7 @@ macro_rules! impl_traits { // able to unwind through this function. #[cfg_attr(nightly, unwind(allowed))] extern fn wrap<$( $x, )* Rets, Trap, FN>( - func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* + vmctx: &vm::Ctx $( , $x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct where $( $x: WasmExternType, )* @@ -510,6 +510,25 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, { + // Get the pointer to this `wrap` function. + let self_pointer = wrap::<$( $x, )* Rets, Trap, FN> as *const vm::Func; + + // Get the collection of imported functions. + let vm_imported_functions = unsafe { &(*vmctx.import_backing).vm_functions }; + + // Retrieve the `vm::FuncCtx`. + let mut func_ctx: NonNull = vm_imported_functions + .iter() + .find_map(|(_, imported_func)| { + if imported_func.func == self_pointer { + Some(imported_func.func_ctx) + } else { + None + } + }) + .expect("Import backing is not well-formed, cannot find `func_ctx`."); + let func_ctx = unsafe { func_ctx.as_mut() }; + // Extract `vm::Ctx` from `vm::FuncCtx`. The // pointer is always non-null. let vmctx = unsafe { func_ctx.vmctx.as_mut() }; @@ -598,7 +617,7 @@ macro_rules! impl_traits { // able to unwind through this function. #[cfg_attr(nightly, unwind(allowed))] extern fn wrap<$( $x, )* Rets, Trap, FN>( - func_ctx: &mut vm::FuncCtx $( , $x: <$x as WasmExternType>::Native )* + vmctx: &vm::Ctx $( , $x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct where $( $x: WasmExternType, )* @@ -606,6 +625,25 @@ macro_rules! impl_traits { Trap: TrapEarly, FN: Fn($( $x, )*) -> Trap, { + // Get the pointer to this `wrap` function. + let self_pointer = wrap::<$( $x, )* Rets, Trap, FN> as *const vm::Func; + + // Get the collection of imported functions. + let vm_imported_functions = unsafe { &(*vmctx.import_backing).vm_functions }; + + // Retrieve the `vm::FuncCtx`. + let mut func_ctx: NonNull = vm_imported_functions + .iter() + .find_map(|(_, imported_func)| { + if imported_func.func == self_pointer { + Some(imported_func.func_ctx) + } else { + None + } + }) + .expect("Import backing is not well-formed, cannot find `func_ctx`."); + let func_ctx = unsafe { func_ctx.as_mut() }; + // Extract `vm::Ctx` from `vm::FuncCtx`. The // pointer is always non-null. let vmctx = unsafe { func_ctx.vmctx.as_mut() }; From 11f34a92854196b867b6c9c99324dbcdc6ad14bb Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 11 Nov 2019 23:12:40 +0100 Subject: [PATCH 22/25] feat(clif-backend,singlepass-backend) Feed imported functions with `FuncCtx.vmctx`. --- lib/clif-backend/src/code.rs | 2 +- lib/runtime-core/src/vm.rs | 33 +++++++++++++++++++++-- lib/singlepass-backend/src/codegen_x64.rs | 6 +++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/clif-backend/src/code.rs b/lib/clif-backend/src/code.rs index 157187e77b6..1bd1958e3de 100644 --- a/lib/clif-backend/src/code.rs +++ b/lib/clif-backend/src/code.rs @@ -776,7 +776,7 @@ impl FuncEnvironment for FunctionEnvironment { let imported_func_ctx_vmctx_addr = pos.func.create_global_value(ir::GlobalValueData::Load { base: imported_func_ctx_addr, - offset: (0 as i32).into(), + offset: (vm::FuncCtx::offset_vmctx() as i32).into(), global_type: ptr_type, readonly: true, }); diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 1c9a8b7add4..6f9ff5c47ac 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -518,7 +518,7 @@ pub struct FuncEnv { /// only. #[derive(Debug)] #[repr(C)] -pub(crate) struct FuncCtx { +pub struct FuncCtx { /// The `Ctx` pointer. pub(crate) vmctx: NonNull, @@ -529,6 +529,20 @@ pub(crate) struct FuncCtx { pub(crate) func_env: Option>, } +impl FuncCtx { + pub fn offset_vmctx() -> u8 { + 0 * (mem::size_of::() as u8) + } + + pub fn offset_func_env() -> u8 { + 1 * (mem::size_of::() as u8) + } + + pub fn size() -> u8 { + mem::size_of::() as u8 + } +} + /// An imported function is a function pointer associated to a /// function context. #[derive(Debug, Clone)] @@ -687,7 +701,9 @@ impl Anyfunc { #[cfg(test)] mod vm_offset_tests { - use super::{Anyfunc, Ctx, ImportedFunc, InternalCtx, LocalGlobal, LocalMemory, LocalTable}; + use super::{ + Anyfunc, Ctx, FuncCtx, ImportedFunc, InternalCtx, LocalGlobal, LocalMemory, LocalTable, + }; #[test] fn vmctx() { @@ -764,6 +780,19 @@ mod vm_offset_tests { ); } + #[test] + fn func_ctx() { + assert_eq!( + FuncCtx::offset_vmctx() as usize, + offset_of!(FuncCtx => vmctx).get_byte_offset(), + ); + + assert_eq!( + FuncCtx::offset_func_env() as usize, + offset_of!(FuncCtx => func_env).get_byte_offset(), + ); + } + #[test] fn imported_func() { assert_eq!( diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index 3ee60babf4f..7a71a289fe2 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -558,6 +558,7 @@ impl ModuleCodeGenerator let imported_func = vm::ImportedFunc::size() as usize * id; let imported_func_addr = imported_func + vm::ImportedFunc::offset_func() as usize; let imported_func_ctx_addr = imported_func + vm::ImportedFunc::offset_func_ctx() as usize; + let imported_func_ctx_vmctx_addr = vm::FuncCtx::offset_vmctx() as usize; a.emit_mov( Size::S64, @@ -569,6 +570,11 @@ impl ModuleCodeGenerator Location::Memory(GPR::RAX, imported_func_ctx_addr as i32), Location::GPR(GPR::RDI), ); + a.emit_mov( + Size::S64, + Location::Memory(GPR::RDI, imported_func_ctx_vmctx_addr as i32), + Location::GPR(GPR::RDI), + ); a.emit_mov( Size::S64, Location::Memory(GPR::RAX, imported_func_addr as i32), From 06c6b3c8e8872c6257154f416128d342c6bcf243 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 11 Nov 2019 23:49:10 +0100 Subject: [PATCH 23/25] feat(runtime-core) More ABI compatible definition of `Func` and `FuncEnv`. --- lib/runtime-core/src/vm.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 6f9ff5c47ac..27b84fd0379 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -501,18 +501,14 @@ impl Ctx { /// Represents a function pointer. It is mostly used in the /// `typed_func` module within the `wrap` functions, to wrap imported /// functions. -#[repr(C)] -pub struct Func { - _private: [u8; 0], -} +#[repr(transparent)] +pub struct Func(pub(self) *mut c_void); /// Represents a function environment pointer, like a captured /// environment of a closure. It is mostly used in the `typed_func` /// module within the `wrap` functions, to wrap imported functions. -#[repr(C)] -pub struct FuncEnv { - _private: [u8; 0], -} +#[repr(transparent)] +pub struct FuncEnv(pub(self) *mut c_void); /// Represents a function context. It is used by imported functions /// only. From bb81614be4187767bb6e0d56c184fa8ca4fcdcac Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 12 Nov 2019 00:46:28 +0100 Subject: [PATCH 24/25] feat(llvm-backend) Update `ImportedFunc` structure. `vm::ImportedFunc` in `runtime-core` has changed. Update LLVM accordingly. --- lib/llvm-backend/src/intrinsics.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/llvm-backend/src/intrinsics.rs b/lib/llvm-backend/src/intrinsics.rs index 4c34b6d584a..58de337b082 100644 --- a/lib/llvm-backend/src/intrinsics.rs +++ b/lib/llvm-backend/src/intrinsics.rs @@ -210,8 +210,13 @@ impl Intrinsics { context.struct_type(&[i8_ptr_ty_basic, i64_ty_basic, i8_ptr_ty_basic], false); let local_table_ty = local_memory_ty; let local_global_ty = i64_ty; - let imported_func_ty = - context.struct_type(&[i8_ptr_ty_basic, ctx_ptr_ty.as_basic_type_enum()], false); + let func_ctx_ty = + context.struct_type(&[ctx_ptr_ty.as_basic_type_enum(), i8_ptr_ty_basic], false); + let func_ctx_ptr_ty = func_ctx_ty.ptr_type(AddressSpace::Generic); + let imported_func_ty = context.struct_type( + &[i8_ptr_ty_basic, func_ctx_ptr_ty.as_basic_type_enum()], + false, + ); let sigindex_ty = i32_ty; let rt_intrinsics_ty = i8_ty; let stack_lower_bound_ty = i8_ty; @@ -1118,16 +1123,20 @@ impl<'a> CtxType<'a> { "imported_func_ptr", ) }; - let (func_ptr_ptr, ctx_ptr_ptr) = unsafe { + let (func_ptr_ptr, func_ctx_ptr_ptr) = unsafe { ( cache_builder.build_struct_gep(imported_func_ptr, 0, "func_ptr_ptr"), - cache_builder.build_struct_gep(imported_func_ptr, 1, "ctx_ptr_ptr"), + cache_builder.build_struct_gep(imported_func_ptr, 1, "func_ctx_ptr_ptr"), ) }; let func_ptr = cache_builder .build_load(func_ptr_ptr, "func_ptr") .into_pointer_value(); + let func_ctx_ptr = cache_builder + .build_load(func_ctx_ptr_ptr, "func_ctx_ptr") + .into_pointer_value(); + let ctx_ptr_ptr = unsafe { cache_builder.build_struct_gep(func_ctx_ptr, 0, "ctx_ptr") }; let ctx_ptr = cache_builder .build_load(ctx_ptr_ptr, "ctx_ptr") .into_pointer_value(); From f002f03e5d2269221b65d2eaca42084dc71a45d6 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 12 Nov 2019 00:56:34 +0100 Subject: [PATCH 25/25] chore(changelog) Fix CS. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb6714f6e7..efb2b1beac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#925](https://github.com/wasmerio/wasmer/pull/925) Host functions can be closures with a captured environment. - [#917](https://github.com/wasmerio/wasmer/pull/917) Host functions (aka imported functions) may not have `&mut vm::Ctx` as first argument, i.e. the presence of the `&mut vm::Ctx` argument is optional. - [#915](https://github.com/wasmerio/wasmer/pull/915) All backends share the same definition of `Trampoline` (defined in `wasmer-runtime-core`). + ## 0.10.0 - 2019-11-11 Special thanks to [@newpavlov](https://github.com/newpavlov) and [@Maxgy](https://github.com/Maxgy) for their contributions!