From 2fe6e6f03971e2ade7df97d01306173205863cdd Mon Sep 17 00:00:00 2001 From: losfair Date: Sat, 15 Feb 2020 01:31:33 +0800 Subject: [PATCH 01/21] Global trampoline buffer. --- lib/runtime-core/src/trampoline_x64.rs | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs index 3d07484c715..c549498f3a3 100644 --- a/lib/runtime-core/src/trampoline_x64.rs +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -9,6 +9,8 @@ use crate::loader::CodeMemory; use crate::vm::Ctx; use std::fmt; +use std::ptr::NonNull; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::{mem, slice}; lazy_static! { @@ -29,6 +31,50 @@ lazy_static! { mem::transmute(ptr) } }; + + static ref TRAMPOLINES: TrampBuffer = TrampBuffer::new(); +} + +struct TrampBuffer { + buffer: CodeMemory, + len: AtomicUsize, +} + +impl TrampBuffer { + /// Creates a trampoline buffer. + fn new() -> TrampBuffer { + // Pre-allocate 64 MiB of virtual memory for code. + let mem = CodeMemory::new(64 * 1048576); + mem.make_writable_executable(); + TrampBuffer { + buffer: mem, + len: AtomicUsize::new(0), + } + } + + /// Bump allocation. Copies `buf` to the end of this code memory. + /// + /// FIXME: Proper storage recycling. + fn append(&self, buf: &[u8]) -> Option> { + let begin = self.len.fetch_add(buf.len(), Ordering::SeqCst); + let end = begin + buf.len(); + + // Length overflowed. Revert and return None. + if end > self.buffer.len() { + self.len.fetch_sub(buf.len(), Ordering::SeqCst); + return None; + } + + // Now we have unique ownership to `self.buffer[begin..end]`. + let slice = unsafe { + std::slice::from_raw_parts_mut( + self.buffer.get_backing_ptr().offset(begin as _), + buf.len(), + ) + }; + slice.copy_from_slice(buf); + Some(NonNull::new(slice.as_mut_ptr()).unwrap()) + } } /// An opaque type for pointers to a callable memory location. @@ -219,6 +265,11 @@ impl TrampolineBufferBuilder { idx } + /// Appends to the global trampoline buffer. + pub fn append_global(self) -> Option> { + TRAMPOLINES.append(&self.code) + } + /// Consumes the builder and builds the trampoline buffer. pub fn build(self) -> TrampolineBuffer { get_context(); // ensure lazy initialization is completed From 12373bb87289f8e68b14bd2c2bbc8887bea922e9 Mon Sep 17 00:00:00 2001 From: losfair Date: Sat, 15 Feb 2020 01:31:49 +0800 Subject: [PATCH 02/21] Func::new_polymorphic --- lib/runtime-core-tests/tests/imports.rs | 31 ++++++++++- lib/runtime-core/src/loader.rs | 20 ++++++- lib/runtime-core/src/typed_func.rs | 74 ++++++++++++++++++++++++- lib/runtime-core/src/vm.rs | 4 +- 4 files changed, 122 insertions(+), 7 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index b461ad2b758..42a09d84460 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -1,6 +1,13 @@ +use std::sync::Arc; use wasmer_runtime_core::{ - compile_with, error::RuntimeError, imports, memory::Memory, typed_func::Func, - types::MemoryDescriptor, units::Pages, vm, Instance, + compile_with, + error::RuntimeError, + imports, + memory::Memory, + typed_func::Func, + types::{FuncSig, MemoryDescriptor, Type, Value}, + units::Pages, + vm, Instance, }; use wasmer_runtime_core_tests::{get_compiler, wat2wasm}; @@ -68,6 +75,7 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) { (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_polymorphic" (func $callback_closure_polymorphic (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))) @@ -86,6 +94,10 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) { get_local 0 call $callback_closure) + (func (export "function_closure_polymorphic") (type $type) + get_local 0 + call $callback_closure_polymorphic) + (func (export "function_closure_with_env") (type $type) get_local 0 call $callback_closure_with_env) @@ -142,6 +154,16 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) { Ok(n + 1) }), + "callback_closure_polymorphic" => Func::::new_polymorphic( + Arc::new(FuncSig::new(vec![Type::I32], vec![Type::I32])), + |_, params| -> Vec { + match params[0] { + Value::I32(x) => vec![Value::I32(x + 1)], + _ => unreachable!() + } + } + ), + // 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(); @@ -236,6 +258,11 @@ macro_rules! test { test!(test_fn, function_fn, Ok(2)); test!(test_closure, function_closure, Ok(2)); +test!( + test_closure_polymorphic, + function_closure_polymorphic, + Ok(2) +); test!( test_closure_with_env, function_closure_with_env, diff --git a/lib/runtime-core/src/loader.rs b/lib/runtime-core/src/loader.rs index f516643d063..ea1ca0130ac 100644 --- a/lib/runtime-core/src/loader.rs +++ b/lib/runtime-core/src/loader.rs @@ -1,7 +1,9 @@ //! The loader module functions are used to load an instance. use crate::{backend::RunnableModule, module::ModuleInfo, types::Type, types::Value, vm::Ctx}; #[cfg(unix)] -use libc::{mmap, mprotect, munmap, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE}; +use libc::{ + mmap, mprotect, munmap, MAP_ANON, MAP_NORESERVE, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE, +}; use std::{ fmt::Debug, ops::{Deref, DerefMut}, @@ -169,7 +171,7 @@ impl CodeMemory { std::ptr::null_mut(), size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, + MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0, ) @@ -196,6 +198,20 @@ impl CodeMemory { panic!("cannot set code memory to writable"); } } + + /// Makes this code memory both writable and executable. + /// + /// Avoid using this if a combination `make_executable` and `make_writable` can be used. + pub fn make_writable_executable(&self) { + if unsafe { mprotect(self.ptr as _, self.size, PROT_READ | PROT_WRITE | PROT_EXEC) } != 0 { + panic!("cannot set code memory to writable and executable"); + } + } + + /// Returns the backing pointer of this code memory. + pub fn get_backing_ptr(&self) -> *mut u8 { + self.ptr + } } #[cfg(unix)] diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 513ede7fbc0..2edcf6644b3 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -4,7 +4,7 @@ use crate::{ error::RuntimeError, export::{Context, Export, FuncPointer}, import::IsExport, - types::{FuncSig, NativeWasmType, Type, WasmExternType}, + types::{FuncSig, NativeWasmType, Type, Value, WasmExternType}, vm, }; use std::{ @@ -240,6 +240,78 @@ where _phantom: PhantomData, } } + + /// Creates a polymorphic function. + #[allow(unused_variables)] + #[cfg(all(unix, target_arch = "x86_64"))] + pub fn new_polymorphic(signature: Arc, func: F) -> Func<'a, Args, Rets, Host> + where + F: Fn(&mut vm::Ctx, &[Value]) -> Vec + 'static, + { + use crate::trampoline_x64::*; + use std::convert::TryFrom; + + struct PolymorphicContext { + arg_types: Vec, + func: Box Vec>, + } + unsafe extern "C" fn enter_host_polymorphic( + ctx: *const CallContext, + args: *const u64, + ) -> u64 { + let ctx = &*(ctx as *const PolymorphicContext); + let vmctx = &mut *(*args.offset(0) as *mut vm::Ctx); + let args: Vec = ctx + .arg_types + .iter() + .enumerate() + .map(|(i, t)| { + let i = i + 1; // skip vmctx + match *t { + Type::I32 => Value::I32(*args.offset(i as _) as i32), + Type::I64 => Value::I64(*args.offset(i as _) as i64), + Type::F32 => Value::F32(f32::from_bits(*args.offset(i as _) as u32)), + Type::F64 => Value::F64(f64::from_bits(*args.offset(i as _) as u64)), + Type::V128 => { + panic!("enter_host_polymorphic: 128-bit types are not supported") + } + } + }) + .collect(); + let rets = (ctx.func)(vmctx, &args); + if rets.len() == 0 { + 0 + } else if rets.len() == 1 { + u64::try_from(rets[0].to_u128()).expect( + "128-bit return value from polymorphic host functions is not yet supported", + ) + } else { + panic!( + "multiple return values from polymorphic host functions is not yet supported" + ); + } + } + let mut builder = TrampolineBufferBuilder::new(); + let ctx = Box::new(PolymorphicContext { + arg_types: signature.params().to_vec(), + func: Box::new(func), + }); + builder.add_callinfo_trampoline( + enter_host_polymorphic, + Box::into_raw(ctx) as *const _, + (signature.params().len() + 1) as u32, // +vmctx + ); + let ptr = builder + .append_global() + .expect("cannot bump-allocate global trampoline memory"); + Func { + inner: Host(()), + func: ptr.cast::(), + func_env: None, + vmctx: ptr::null_mut(), + _phantom: PhantomData, + } + } } impl<'a, Args, Rets, Inner> Func<'a, Args, Rets, Inner> diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 2a39bdec10f..a25ee24ff6b 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -545,13 +545,13 @@ impl Ctx { /// `typed_func` module within the `wrap` functions, to wrap imported /// functions. #[repr(transparent)] -pub struct Func(pub(self) *mut c_void); +pub struct Func(*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(transparent)] -pub struct FuncEnv(pub(self) *mut c_void); +pub struct FuncEnv(*mut c_void); /// Represents a function context. It is used by imported functions /// only. From 5f4561e5efdee87a3a26e520fcde77c8231cbe04 Mon Sep 17 00:00:00 2001 From: losfair Date: Sun, 16 Feb 2020 00:28:43 +0800 Subject: [PATCH 03/21] Fix compilation error on Aarch64. --- lib/runtime-core/src/typed_func.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 2edcf6644b3..84fbe8addd7 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -4,7 +4,7 @@ use crate::{ error::RuntimeError, export::{Context, Export, FuncPointer}, import::IsExport, - types::{FuncSig, NativeWasmType, Type, Value, WasmExternType}, + types::{FuncSig, NativeWasmType, Type, WasmExternType}, vm, }; use std::{ @@ -246,9 +246,10 @@ where #[cfg(all(unix, target_arch = "x86_64"))] pub fn new_polymorphic(signature: Arc, func: F) -> Func<'a, Args, Rets, Host> where - F: Fn(&mut vm::Ctx, &[Value]) -> Vec + 'static, + F: Fn(&mut vm::Ctx, &[crate::types::Value]) -> Vec + 'static, { use crate::trampoline_x64::*; + use crate::types::Value; use std::convert::TryFrom; struct PolymorphicContext { From ad20a008e095a471d85a3dccebfa5b7d9ecae4af Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 17 Feb 2020 15:30:25 +0100 Subject: [PATCH 04/21] fix(runtime-core) Use explicit `dyn` for trait objects. --- lib/runtime-core/src/typed_func.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 84fbe8addd7..eca73f61b86 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -254,7 +254,7 @@ where struct PolymorphicContext { arg_types: Vec, - func: Box Vec>, + func: Box Vec>, } unsafe extern "C" fn enter_host_polymorphic( ctx: *const CallContext, From 2ee1e80f3b15f1c266bdfeeb828e5c1aed2919a3 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 17 Feb 2020 15:43:14 +0100 Subject: [PATCH 05/21] feat(runtime-core) Allow dynamic signature for polymorphic host functions. This patch adds a new field in `Func`: `signature`. It contains the signature of the host function. For non-polymorphic host functions, the signature is computed from the `Args` and `Rets` implementation parameters at compile-time. For polymorphic host functions though, to be fully dynamic, the signature given to `new_polymorphic` is used in `Func` as the correct signature. --- lib/runtime-core-tests/tests/imports.rs | 2 +- lib/runtime-core/src/typed_func.rs | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index 42a09d84460..ce3cea2c4a9 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -154,7 +154,7 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) { Ok(n + 1) }), - "callback_closure_polymorphic" => Func::::new_polymorphic( + "callback_closure_polymorphic" => Func::new_polymorphic( Arc::new(FuncSig::new(vec![Type::I32], vec![Type::I32])), |_, params| -> Vec { match params[0] { diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index eca73f61b86..4abad5a1741 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -189,9 +189,21 @@ where /// Represents a function that can be used by WebAssembly. pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { inner: Inner, + + /// The function pointer. func: NonNull, + + /// The function environment. func_env: Option>, + + /// The famous `vm::Ctx`. vmctx: *mut vm::Ctx, + + /// The signature is usually infered from `Args` and `Rets`. In + /// case of polymorphic function, the signature is only known at + /// runtime. + signature: Arc, + _phantom: PhantomData<(&'a (), Args, Rets)>, } @@ -214,6 +226,7 @@ where func, func_env, vmctx, + signature: Arc::new(FuncSig::new(Args::types(), Rets::types())), _phantom: PhantomData, } } @@ -225,7 +238,7 @@ where Rets: WasmTypeList, { /// Creates a new `Func`. - pub fn new(func: F) -> Func<'a, Args, Rets, Host> + pub fn new(func: F) -> Self where Kind: ExternalFunctionKind, F: ExternalFunction, @@ -237,14 +250,17 @@ where func, func_env, vmctx: ptr::null_mut(), + signature: Arc::new(FuncSig::new(Args::types(), Rets::types())), _phantom: PhantomData, } } +} +impl<'a> Func<'a, (), (), Host> { /// Creates a polymorphic function. #[allow(unused_variables)] #[cfg(all(unix, target_arch = "x86_64"))] - pub fn new_polymorphic(signature: Arc, func: F) -> Func<'a, Args, Rets, Host> + pub fn new_polymorphic(signature: Arc, func: F) -> Self where F: Fn(&mut vm::Ctx, &[crate::types::Value]) -> Vec + 'static, { @@ -305,11 +321,13 @@ where let ptr = builder .append_global() .expect("cannot bump-allocate global trampoline memory"); + Func { inner: Host(()), func: ptr.cast::(), func_env: None, vmctx: ptr::null_mut(), + signature, _phantom: PhantomData, } } @@ -751,12 +769,11 @@ where func_env @ Some(_) => Context::ExternalWithEnv(self.vmctx, func_env), None => Context::Internal, }; - let signature = Arc::new(FuncSig::new(Args::types(), Rets::types())); Export::Function { func, ctx, - signature, + signature: self.signature.clone(), } } } From b67acbc0e3828a8b99ebf956839f1731dd7b8af0 Mon Sep 17 00:00:00 2001 From: losfair Date: Tue, 25 Feb 2020 01:19:19 +0800 Subject: [PATCH 06/21] Add `ErasedFunc` for type-erased functions. --- lib/runtime-core-tests/tests/imports.rs | 4 +- lib/runtime-core/src/typed_func.rs | 75 +++++++++++++++++++++---- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index ce3cea2c4a9..17c82097ad9 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -4,7 +4,7 @@ use wasmer_runtime_core::{ error::RuntimeError, imports, memory::Memory, - typed_func::Func, + typed_func::{ErasedFunc, Func}, types::{FuncSig, MemoryDescriptor, Type, Value}, units::Pages, vm, Instance, @@ -154,7 +154,7 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) { Ok(n + 1) }), - "callback_closure_polymorphic" => Func::new_polymorphic( + "callback_closure_polymorphic" => ErasedFunc::new_polymorphic( Arc::new(FuncSig::new(vec![Type::I32], vec![Type::I32])), |_, params| -> Vec { match params[0] { diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 39c8b45e93b..6a3558f6d5d 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -190,6 +190,32 @@ where } } +/// Represents a type-erased function provided by either the host or the WebAssembly program. +#[allow(dead_code)] +pub struct ErasedFunc<'a> { + inner: Box, + + /// The function pointer. + func: NonNull, + + /// The function environment. + func_env: Option>, + + /// The famous `vm::Ctx`. + vmctx: *mut vm::Ctx, + + /// The runtime signature of this function. + /// + /// When converted from a `Func`, this is determined by the static `Args` and `Rets` type parameters. + /// otherwise the signature is dynamically assigned during `ErasedFunc` creation, usually when creating + /// a polymorphic host function. + signature: Arc, + + _phantom: PhantomData<&'a ()>, +} + +unsafe impl<'a> Send for ErasedFunc<'a> {} + /// Represents a function that can be used by WebAssembly. pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { inner: Inner, @@ -203,17 +229,30 @@ pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { /// The famous `vm::Ctx`. vmctx: *mut vm::Ctx, - /// The signature is usually infered from `Args` and `Rets`. In - /// case of polymorphic function, the signature is only known at - /// runtime. - signature: Arc, - _phantom: PhantomData<(&'a (), Args, Rets)>, } unsafe impl<'a, Args, Rets> Send for Func<'a, Args, Rets, Wasm> {} unsafe impl<'a, Args, Rets> Send for Func<'a, Args, Rets, Host> {} +impl<'a, Args, Rets, Inner> From> for ErasedFunc<'a> +where + Args: WasmTypeList, + Rets: WasmTypeList, + Inner: Kind + 'static, +{ + fn from(that: Func<'a, Args, Rets, Inner>) -> ErasedFunc<'a> { + ErasedFunc { + inner: Box::new(that.inner), + func: that.func, + func_env: that.func_env, + vmctx: that.vmctx, + signature: Arc::new(FuncSig::new(Args::types(), Rets::types())), + _phantom: PhantomData, + } + } +} + impl<'a, Args, Rets> Func<'a, Args, Rets, Wasm> where Args: WasmTypeList, @@ -230,7 +269,6 @@ where func, func_env, vmctx, - signature: Arc::new(FuncSig::new(Args::types(), Rets::types())), _phantom: PhantomData, } } @@ -254,13 +292,12 @@ where func, func_env, vmctx: ptr::null_mut(), - signature: Arc::new(FuncSig::new(Args::types(), Rets::types())), _phantom: PhantomData, } } } -impl<'a> Func<'a, (), (), Host> { +impl<'a> ErasedFunc<'a> { /// Creates a polymorphic function. #[allow(unused_variables)] #[cfg(all(unix, target_arch = "x86_64"))] @@ -326,8 +363,8 @@ impl<'a> Func<'a, (), (), Host> { .append_global() .expect("cannot bump-allocate global trampoline memory"); - Func { - inner: Host(()), + ErasedFunc { + inner: Box::new(Host(())), func: ptr.cast::(), func_env: None, vmctx: ptr::null_mut(), @@ -765,6 +802,22 @@ impl_traits!([C] S24, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T impl_traits!([C] S25, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y); impl_traits!([C] S26, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); +impl<'a> IsExport for ErasedFunc<'a> { + fn to_export(&self) -> Export { + let func = unsafe { FuncPointer::new(self.func.as_ptr()) }; + let ctx = match self.func_env { + func_env @ Some(_) => Context::ExternalWithEnv(self.vmctx, func_env), + None => Context::Internal, + }; + + Export::Function { + func, + ctx, + signature: self.signature.clone(), + } + } +} + impl<'a, Args, Rets, Inner> IsExport for Func<'a, Args, Rets, Inner> where Args: WasmTypeList, @@ -781,7 +834,7 @@ where Export::Function { func, ctx, - signature: self.signature.clone(), + signature: Arc::new(FuncSig::new(Args::types(), Rets::types())), } } } From b7c9c1843a6c939092ecafbc351b3faf5447e45c Mon Sep 17 00:00:00 2001 From: losfair Date: Wed, 26 Feb 2020 01:44:50 +0800 Subject: [PATCH 07/21] Add dynamic executable memory allocation & tests to trampolines. --- lib/runtime-core/src/trampoline_x64.rs | 183 +++++++++++++++++++++---- 1 file changed, 160 insertions(+), 23 deletions(-) diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs index c549498f3a3..b15f431b570 100644 --- a/lib/runtime-core/src/trampoline_x64.rs +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -8,9 +8,10 @@ use crate::loader::CodeMemory; use crate::vm::Ctx; +use std::collections::BTreeMap; use std::fmt; use std::ptr::NonNull; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Mutex; use std::{mem, slice}; lazy_static! { @@ -32,43 +33,88 @@ lazy_static! { } }; - static ref TRAMPOLINES: TrampBuffer = TrampBuffer::new(); + static ref TRAMPOLINES: TrampBuffer = TrampBuffer::new(64 * 1048576); } -struct TrampBuffer { +/// The global trampoline buffer. +pub(self) struct TrampBuffer { + /// A fixed-(virtual)-size executable+writable buffer for storing trampolines. buffer: CodeMemory, - len: AtomicUsize, + + /// Allocation state. + alloc: Mutex, +} + +/// The allocation state of a `TrampBuffer`. +struct AllocState { + /// Records all allocated blocks in `buffer`. + blocks: BTreeMap, } impl TrampBuffer { - /// Creates a trampoline buffer. - fn new() -> TrampBuffer { + /// Creates a trampoline buffer with a given (virtual) size. + pub(self) fn new(size: usize) -> TrampBuffer { // Pre-allocate 64 MiB of virtual memory for code. - let mem = CodeMemory::new(64 * 1048576); + let mem = CodeMemory::new(size); mem.make_writable_executable(); TrampBuffer { buffer: mem, - len: AtomicUsize::new(0), + alloc: Mutex::new(AllocState { + blocks: BTreeMap::new(), + }), } } - /// Bump allocation. Copies `buf` to the end of this code memory. + /// Removes a previously-`insert`ed trampoline. + pub(self) unsafe fn remove(&self, start: NonNull) { + let start = start.as_ptr() as usize - self.buffer.get_backing_ptr() as usize; + let mut alloc = self.alloc.lock().unwrap(); + alloc + .blocks + .remove(&start) + .expect("TrampBuffer::remove(): Attempting to remove a non-existent allocation."); + } + + /// Allocates a region of executable memory and copies `buf` to the end of this region. /// - /// FIXME: Proper storage recycling. - fn append(&self, buf: &[u8]) -> Option> { - let begin = self.len.fetch_add(buf.len(), Ordering::SeqCst); - let end = begin + buf.len(); - - // Length overflowed. Revert and return None. - if end > self.buffer.len() { - self.len.fetch_sub(buf.len(), Ordering::SeqCst); - return None; + /// Returns `None` if no memory is available. + pub(self) fn insert(&self, buf: &[u8]) -> Option> { + // First, assume an available start position... + let mut assumed_start: usize = 0; + + let mut alloc = self.alloc.lock().unwrap(); + let mut found = false; + + // Then, try invalidating that assumption... + for (&start, &end) in &alloc.blocks { + if start - assumed_start < buf.len() { + // Unavailable. Move to next free block. + assumed_start = end; + } else { + // This free block can be used. + found = true; + break; + } } - // Now we have unique ownership to `self.buffer[begin..end]`. + if !found { + // No previous free blocks were found. Try allocating at the end. + if self.buffer.len() - assumed_start < buf.len() { + // No more free space. Cannot allocate. + return None; + } else { + // Extend towards the end. + } + } + + // Now we know `assumed_start` is valid. + let start = assumed_start; + alloc.blocks.insert(start, start + buf.len()); + + // We have unique ownership to `self.buffer[start..start + buf.len()]`. let slice = unsafe { std::slice::from_raw_parts_mut( - self.buffer.get_backing_ptr().offset(begin as _), + self.buffer.get_backing_ptr().offset(start as _), buf.len(), ) }; @@ -265,9 +311,19 @@ impl TrampolineBufferBuilder { idx } - /// Appends to the global trampoline buffer. - pub fn append_global(self) -> Option> { - TRAMPOLINES.append(&self.code) + /// Inserts to the global trampoline buffer. + pub fn insert_global(self) -> Option> { + TRAMPOLINES.insert(&self.code) + } + + /// Removes from the global trampoline buffer. + pub unsafe fn remove_global(ptr: NonNull) { + TRAMPOLINES.remove(ptr); + } + + /// Gets the current (non-executable) code in this builder. + pub fn code(&self) -> &[u8] { + &self.code } /// Consumes the builder and builds the trampoline buffer. @@ -343,4 +399,85 @@ mod tests { }; assert_eq!(ret, 136); } + + #[test] + fn test_many_global_trampolines() { + unsafe extern "C" fn inner(n: *const CallContext, args: *const u64) -> u64 { + let n = n as usize; + let mut result: u64 = 0; + for i in 0..n { + result += *args.offset(i as _); + } + result + } + + // Use the smallest possible buffer size (page size) to check memory releasing logic. + let buffer = TrampBuffer::new(4096); + + // Validate the previous trampoline instead of the current one to ensure that no overwrite happened. + let mut prev: Option<(NonNull, u64)> = None; + + for i in 0..5000usize { + let mut builder = TrampolineBufferBuilder::new(); + let n = i % 8; + builder.add_callinfo_trampoline(inner, n as _, n as _); + let ptr = buffer + .insert(builder.code()) + .expect("cannot insert new code into global buffer"); + + if let Some((ptr, expected)) = prev.take() { + use std::mem::transmute; + + // Test different argument counts. + unsafe { + match expected { + 0 => { + let f = transmute::<_, extern "C" fn() -> u64>(ptr); + assert_eq!(f(), 0); + } + 1 => { + let f = transmute::<_, extern "C" fn(u64) -> u64>(ptr); + assert_eq!(f(1), 1); + } + 3 => { + let f = transmute::<_, extern "C" fn(u64, u64) -> u64>(ptr); + assert_eq!(f(1, 2), 3); + } + 6 => { + let f = transmute::<_, extern "C" fn(u64, u64, u64) -> u64>(ptr); + assert_eq!(f(1, 2, 3), 6); + } + 10 => { + let f = transmute::<_, extern "C" fn(u64, u64, u64, u64) -> u64>(ptr); + assert_eq!(f(1, 2, 3, 4), 10); + } + 15 => { + let f = + transmute::<_, extern "C" fn(u64, u64, u64, u64, u64) -> u64>(ptr); + assert_eq!(f(1, 2, 3, 4, 5), 15); + } + 21 => { + let f = transmute::< + _, + extern "C" fn(u64, u64, u64, u64, u64, u64) -> u64, + >(ptr); + assert_eq!(f(1, 2, 3, 4, 5, 6), 21); + } + 28 => { + let f = transmute::< + _, + extern "C" fn(u64, u64, u64, u64, u64, u64, u64) -> u64, + >(ptr); + assert_eq!(f(1, 2, 3, 4, 5, 6, 7), 28); + } + _ => unreachable!(), + } + buffer.remove(ptr); + } + } + + let expected = (0..=n as u64).fold(0u64, |a, b| a + b); + prev = Some((ptr, expected)) + } + } } From 80f824e7080ca6339de9ec5c391a17a00d85e86d Mon Sep 17 00:00:00 2001 From: losfair Date: Wed, 26 Feb 2020 01:45:11 +0800 Subject: [PATCH 08/21] Auto-release trampolines. --- lib/runtime-core/src/typed_func.rs | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 6a3558f6d5d..152302bf4a5 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -360,11 +360,25 @@ impl<'a> ErasedFunc<'a> { (signature.params().len() + 1) as u32, // +vmctx ); let ptr = builder - .append_global() + .insert_global() .expect("cannot bump-allocate global trampoline memory"); + struct AutoRelease { + ptr: NonNull, + } + + impl Drop for AutoRelease { + fn drop(&mut self) { + unsafe { + TrampolineBufferBuilder::remove_global(self.ptr); + } + } + } + + impl Kind for AutoRelease {} + ErasedFunc { - inner: Box::new(Host(())), + inner: Box::new(AutoRelease { ptr }), func: ptr.cast::(), func_env: None, vmctx: ptr::null_mut(), @@ -941,4 +955,18 @@ mod tests { }, }; } + + #[test] + fn test_many_new_polymorphics() { + use crate::types::{FuncSig, Type}; + + // Check that generating a lot (1M) of polymorphic functions doesn't use up the executable buffer. + for _ in 0..1000000 { + let arglist = vec![Type::I32; 100]; + ErasedFunc::new_polymorphic( + Arc::new(FuncSig::new(arglist, vec![Type::I32])), + |_, _| unreachable!(), + ); + } + } } From 96d9e399139ee49ce5a28d42c2bfae661babe069 Mon Sep 17 00:00:00 2001 From: losfair Date: Wed, 26 Feb 2020 13:40:02 +0800 Subject: [PATCH 09/21] Specify imports instead of using a `*`. --- Cargo.lock | 21 +++++++++++++++------ lib/runtime-core/src/typed_func.rs | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82c04e1fb5a..a8faaeaf733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,7 +240,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "log", - "smallvec", + "smallvec 1.2.0", "target-lexicon", "thiserror", ] @@ -926,7 +926,7 @@ dependencies = [ "cloudabi", "libc", "redox_syscall", - "smallvec", + "smallvec 1.2.0", "winapi", ] @@ -1414,6 +1414,15 @@ dependencies = [ "serde", ] +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +dependencies = [ + "maybe-uninit", +] + [[package]] name = "smallvec" version = "1.2.0" @@ -1764,7 +1773,7 @@ checksum = "6d2e13201ef9ef527ad30a6bf1b08e3e024a40cf2731f393d80375dc88506207" dependencies = [ "cranelift-codegen", "log", - "smallvec", + "smallvec 1.2.0", "target-lexicon", ] @@ -1846,7 +1855,7 @@ dependencies = [ "regex", "rustc_version", "semver", - "smallvec", + "smallvec 0.6.13", "wabt", "wasmer-runtime-core", "wasmparser", @@ -1933,7 +1942,7 @@ dependencies = [ "serde-bench", "serde_bytes", "serde_derive", - "smallvec", + "smallvec 0.6.13", "wasmparser", "winapi", ] @@ -1962,7 +1971,7 @@ dependencies = [ "nix", "serde", "serde_derive", - "smallvec", + "smallvec 0.6.13", "wasmer-runtime-core", ] diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 152302bf4a5..d8c36188f90 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -305,7 +305,7 @@ impl<'a> ErasedFunc<'a> { where F: Fn(&mut vm::Ctx, &[crate::types::Value]) -> Vec + 'static, { - use crate::trampoline_x64::*; + use crate::trampoline_x64::{CallContext, TrampolineBufferBuilder}; use crate::types::Value; use std::convert::TryFrom; From a0ea1af71ff2c8eeeefd4786b51b57e65d5abf3c Mon Sep 17 00:00:00 2001 From: losfair Date: Wed, 26 Feb 2020 16:54:33 +0800 Subject: [PATCH 10/21] Remove pub(self). --- lib/runtime-core/src/trampoline_x64.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs index b15f431b570..14606861238 100644 --- a/lib/runtime-core/src/trampoline_x64.rs +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -37,7 +37,7 @@ lazy_static! { } /// The global trampoline buffer. -pub(self) struct TrampBuffer { +struct TrampBuffer { /// A fixed-(virtual)-size executable+writable buffer for storing trampolines. buffer: CodeMemory, @@ -53,7 +53,7 @@ struct AllocState { impl TrampBuffer { /// Creates a trampoline buffer with a given (virtual) size. - pub(self) fn new(size: usize) -> TrampBuffer { + fn new(size: usize) -> TrampBuffer { // Pre-allocate 64 MiB of virtual memory for code. let mem = CodeMemory::new(size); mem.make_writable_executable(); @@ -66,7 +66,7 @@ impl TrampBuffer { } /// Removes a previously-`insert`ed trampoline. - pub(self) unsafe fn remove(&self, start: NonNull) { + unsafe fn remove(&self, start: NonNull) { let start = start.as_ptr() as usize - self.buffer.get_backing_ptr() as usize; let mut alloc = self.alloc.lock().unwrap(); alloc @@ -78,7 +78,7 @@ impl TrampBuffer { /// Allocates a region of executable memory and copies `buf` to the end of this region. /// /// Returns `None` if no memory is available. - pub(self) fn insert(&self, buf: &[u8]) -> Option> { + fn insert(&self, buf: &[u8]) -> Option> { // First, assume an available start position... let mut assumed_start: usize = 0; From 262d431b49f1176a035e8ffdc718f0bd4904c58b Mon Sep 17 00:00:00 2001 From: losfair Date: Wed, 26 Feb 2020 16:59:31 +0800 Subject: [PATCH 11/21] Remove unneeded allow(dead_code). --- lib/runtime-core/src/typed_func.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index d8c36188f90..50780955dba 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -191,9 +191,8 @@ where } /// Represents a type-erased function provided by either the host or the WebAssembly program. -#[allow(dead_code)] pub struct ErasedFunc<'a> { - inner: Box, + _inner: Box, /// The function pointer. func: NonNull, @@ -243,7 +242,7 @@ where { fn from(that: Func<'a, Args, Rets, Inner>) -> ErasedFunc<'a> { ErasedFunc { - inner: Box::new(that.inner), + _inner: Box::new(that.inner), func: that.func, func_env: that.func_env, vmctx: that.vmctx, @@ -378,7 +377,7 @@ impl<'a> ErasedFunc<'a> { impl Kind for AutoRelease {} ErasedFunc { - inner: Box::new(AutoRelease { ptr }), + _inner: Box::new(AutoRelease { ptr }), func: ptr.cast::(), func_env: None, vmctx: ptr::null_mut(), From 292e42addcb946ec7ca0da6987cf21f5801a36e4 Mon Sep 17 00:00:00 2001 From: Heyang Zhou Date: Wed, 26 Feb 2020 17:01:16 +0800 Subject: [PATCH 12/21] Update lib/runtime-core/src/typed_func.rs Co-Authored-By: Ivan Enderlin --- lib/runtime-core/src/typed_func.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 50780955dba..ec1f580b4b9 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -330,7 +330,7 @@ impl<'a> ErasedFunc<'a> { Type::F32 => Value::F32(f32::from_bits(*args.offset(i as _) as u32)), Type::F64 => Value::F64(f64::from_bits(*args.offset(i as _) as u64)), Type::V128 => { - panic!("enter_host_polymorphic: 128-bit types are not supported") + todo!("enter_host_polymorphic: 128-bit types are not supported") } } }) From a438a644b657e4c9bfc8fd5113f79788d9787cde Mon Sep 17 00:00:00 2001 From: Heyang Zhou Date: Wed, 26 Feb 2020 17:01:36 +0800 Subject: [PATCH 13/21] fold() -> sum() Co-Authored-By: Ivan Enderlin --- lib/runtime-core/src/trampoline_x64.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs index 14606861238..94bffb3cf49 100644 --- a/lib/runtime-core/src/trampoline_x64.rs +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -476,7 +476,7 @@ mod tests { } } - let expected = (0..=n as u64).fold(0u64, |a, b| a + b); + let expected = (0..=n as u64).sum(); prev = Some((ptr, expected)) } } From b0877b26e5776079681135a444bc1a0a8a9f3cb6 Mon Sep 17 00:00:00 2001 From: losfair Date: Wed, 26 Feb 2020 17:07:56 +0800 Subject: [PATCH 14/21] Add safety notice for `TrampolineBufferBuilder::remove_global`. --- lib/runtime-core/src/trampoline_x64.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs index 14606861238..ccff1cae143 100644 --- a/lib/runtime-core/src/trampoline_x64.rs +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -66,6 +66,8 @@ impl TrampBuffer { } /// Removes a previously-`insert`ed trampoline. + /// + /// For safety, refer to the public interface `TrampolineBufferBuilder::remove_global`. unsafe fn remove(&self, start: NonNull) { let start = start.as_ptr() as usize - self.buffer.get_backing_ptr() as usize; let mut alloc = self.alloc.lock().unwrap(); @@ -311,12 +313,18 @@ impl TrampolineBufferBuilder { idx } - /// Inserts to the global trampoline buffer. + /// Inserts this trampoline to the global trampoline buffer. pub fn insert_global(self) -> Option> { TRAMPOLINES.insert(&self.code) } - /// Removes from the global trampoline buffer. + /// Removes the trampoline pointed to by `ptr` from the global trampoline buffer. Panics if `ptr` + /// does not point to any trampoline. + /// + /// # Safety + /// + /// Calling this function invalidates the trampoline `ptr` points to and recycles its memory. You + /// should ensure that `ptr` isn't used after calling `remove_global`. pub unsafe fn remove_global(ptr: NonNull) { TRAMPOLINES.remove(ptr); } From 72e6a85ea5f6fb8f1e85c4d05f395db7e6785430 Mon Sep 17 00:00:00 2001 From: losfair Date: Wed, 26 Feb 2020 23:42:48 +0800 Subject: [PATCH 15/21] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6871ecb3d68..102fc0b0941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## 0.14.1 - 2020-02-24 +- [#1217](https://github.com/wasmerio/wasmer/pull/1217) Polymorphic host functions based on dynamic trampoline generation. - [#1245](https://github.com/wasmerio/wasmer/pull/1245) Use Ubuntu 16.04 in CI so that we use an earlier version of GLIBC. - [#1234](https://github.com/wasmerio/wasmer/pull/1234) Check for unused excluded spectest failures. - [#1232](https://github.com/wasmerio/wasmer/pull/1232) `wasmer-interface-types` has a WAT decoder. From 31a72e59fbd96263277a5827b3576351b6698ed0 Mon Sep 17 00:00:00 2001 From: losfair Date: Fri, 28 Feb 2020 11:41:36 +0800 Subject: [PATCH 16/21] Rename ErasedFunc to DynamicFunc and fix leaky `PolymorphicContext`. --- lib/runtime-core-tests/tests/imports.rs | 16 +++++------ lib/runtime-core/src/typed_func.rs | 35 ++++++++++++++----------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs index 17c82097ad9..1af995368eb 100644 --- a/lib/runtime-core-tests/tests/imports.rs +++ b/lib/runtime-core-tests/tests/imports.rs @@ -4,7 +4,7 @@ use wasmer_runtime_core::{ error::RuntimeError, imports, memory::Memory, - typed_func::{ErasedFunc, Func}, + typed_func::{DynamicFunc, Func}, types::{FuncSig, MemoryDescriptor, Type, Value}, units::Pages, vm, Instance, @@ -75,7 +75,7 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) { (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_polymorphic" (func $callback_closure_polymorphic (type $type))) + (import "env" "callback_closure_dynamic" (func $callback_closure_dynamic (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))) @@ -94,9 +94,9 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) { get_local 0 call $callback_closure) - (func (export "function_closure_polymorphic") (type $type) + (func (export "function_closure_dynamic") (type $type) get_local 0 - call $callback_closure_polymorphic) + call $callback_closure_dynamic) (func (export "function_closure_with_env") (type $type) get_local 0 @@ -154,7 +154,7 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) { Ok(n + 1) }), - "callback_closure_polymorphic" => ErasedFunc::new_polymorphic( + "callback_closure_dynamic" => DynamicFunc::new( Arc::new(FuncSig::new(vec![Type::I32], vec![Type::I32])), |_, params| -> Vec { match params[0] { @@ -258,11 +258,7 @@ macro_rules! test { test!(test_fn, function_fn, Ok(2)); test!(test_closure, function_closure, Ok(2)); -test!( - test_closure_polymorphic, - function_closure_polymorphic, - Ok(2) -); +test!(test_closure_dynamic, function_closure_dynamic, Ok(2)); test!( test_closure_with_env, function_closure_with_env, diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index ec1f580b4b9..b8d3b0bd065 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -191,7 +191,7 @@ where } /// Represents a type-erased function provided by either the host or the WebAssembly program. -pub struct ErasedFunc<'a> { +pub struct DynamicFunc<'a> { _inner: Box, /// The function pointer. @@ -206,14 +206,14 @@ pub struct ErasedFunc<'a> { /// The runtime signature of this function. /// /// When converted from a `Func`, this is determined by the static `Args` and `Rets` type parameters. - /// otherwise the signature is dynamically assigned during `ErasedFunc` creation, usually when creating + /// otherwise the signature is dynamically assigned during `DynamicFunc` creation, usually when creating /// a polymorphic host function. signature: Arc, _phantom: PhantomData<&'a ()>, } -unsafe impl<'a> Send for ErasedFunc<'a> {} +unsafe impl<'a> Send for DynamicFunc<'a> {} /// Represents a function that can be used by WebAssembly. pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { @@ -234,14 +234,14 @@ pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { unsafe impl<'a, Args, Rets> Send for Func<'a, Args, Rets, Wasm> {} unsafe impl<'a, Args, Rets> Send for Func<'a, Args, Rets, Host> {} -impl<'a, Args, Rets, Inner> From> for ErasedFunc<'a> +impl<'a, Args, Rets, Inner> From> for DynamicFunc<'a> where Args: WasmTypeList, Rets: WasmTypeList, Inner: Kind + 'static, { - fn from(that: Func<'a, Args, Rets, Inner>) -> ErasedFunc<'a> { - ErasedFunc { + fn from(that: Func<'a, Args, Rets, Inner>) -> DynamicFunc<'a> { + DynamicFunc { _inner: Box::new(that.inner), func: that.func, func_env: that.func_env, @@ -296,11 +296,11 @@ where } } -impl<'a> ErasedFunc<'a> { - /// Creates a polymorphic function. +impl<'a> DynamicFunc<'a> { + /// Creates a dynamic function that is polymorphic over its argument and return types. #[allow(unused_variables)] #[cfg(all(unix, target_arch = "x86_64"))] - pub fn new_polymorphic(signature: Arc, func: F) -> Self + pub fn new(signature: Arc, func: F) -> Self where F: Fn(&mut vm::Ctx, &[crate::types::Value]) -> Vec + 'static, { @@ -349,13 +349,14 @@ impl<'a> ErasedFunc<'a> { } } let mut builder = TrampolineBufferBuilder::new(); - let ctx = Box::new(PolymorphicContext { + let ctx: Box = Box::new(PolymorphicContext { arg_types: signature.params().to_vec(), func: Box::new(func), }); + let ctx = Box::into_raw(ctx); builder.add_callinfo_trampoline( enter_host_polymorphic, - Box::into_raw(ctx) as *const _, + ctx as *const _, (signature.params().len() + 1) as u32, // +vmctx ); let ptr = builder @@ -364,20 +365,22 @@ impl<'a> ErasedFunc<'a> { struct AutoRelease { ptr: NonNull, + ctx: *mut PolymorphicContext, } impl Drop for AutoRelease { fn drop(&mut self) { unsafe { TrampolineBufferBuilder::remove_global(self.ptr); + Box::from_raw(self.ctx); } } } impl Kind for AutoRelease {} - ErasedFunc { - _inner: Box::new(AutoRelease { ptr }), + DynamicFunc { + _inner: Box::new(AutoRelease { ptr, ctx }), func: ptr.cast::(), func_env: None, vmctx: ptr::null_mut(), @@ -815,7 +818,7 @@ impl_traits!([C] S24, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T impl_traits!([C] S25, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y); impl_traits!([C] S26, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); -impl<'a> IsExport for ErasedFunc<'a> { +impl<'a> IsExport for DynamicFunc<'a> { fn to_export(&self) -> Export { let func = unsafe { FuncPointer::new(self.func.as_ptr()) }; let ctx = match self.func_env { @@ -956,13 +959,13 @@ mod tests { } #[test] - fn test_many_new_polymorphics() { + fn test_many_new_dynamics() { use crate::types::{FuncSig, Type}; // Check that generating a lot (1M) of polymorphic functions doesn't use up the executable buffer. for _ in 0..1000000 { let arglist = vec![Type::I32; 100]; - ErasedFunc::new_polymorphic( + DynamicFunc::new( Arc::new(FuncSig::new(arglist, vec![Type::I32])), |_, _| unreachable!(), ); From 2ddf9ad4c833250185e78de554576b9a9afbd963 Mon Sep 17 00:00:00 2001 From: losfair Date: Fri, 28 Feb 2020 22:16:29 +0800 Subject: [PATCH 17/21] Disallow "fat" closures. --- lib/runtime-core/src/typed_func.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index b8d3b0bd065..0b29d54eadb 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -348,6 +348,12 @@ impl<'a> DynamicFunc<'a> { ); } } + + // Disable "fat" closures for possible future changes. + if mem::size_of::() != 0 { + unimplemented!("DynamicFunc with captured environment is not yet supported"); + } + let mut builder = TrampolineBufferBuilder::new(); let ctx: Box = Box::new(PolymorphicContext { arg_types: signature.params().to_vec(), From 84179dbd5e807f97823913787f7a256503c285e7 Mon Sep 17 00:00:00 2001 From: losfair Date: Sat, 29 Feb 2020 11:12:26 +0800 Subject: [PATCH 18/21] Fix changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e09f1dc30b..a4c5da26fee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## **[Unreleased]** +- [#1217](https://github.com/wasmerio/wasmer/pull/1217) Polymorphic host functions based on dynamic trampoline generation. - [#1212](https://github.com/wasmerio/wasmer/pull/1212) Add support for GDB JIT debugging: - Add `--generate-debug-info` and `-g` flags to `wasmer run` to generate debug information during compilation. The debug info is passed via the GDB JIT interface to a debugger to allow source-level debugging of Wasm files. Currently only available on clif-backend. - Break public middleware APIs: there is now a `source_loc` parameter that should be passed through if applicable. @@ -9,7 +10,6 @@ ## 0.14.1 - 2020-02-24 -- [#1217](https://github.com/wasmerio/wasmer/pull/1217) Polymorphic host functions based on dynamic trampoline generation. - [#1245](https://github.com/wasmerio/wasmer/pull/1245) Use Ubuntu 16.04 in CI so that we use an earlier version of GLIBC. - [#1234](https://github.com/wasmerio/wasmer/pull/1234) Check for unused excluded spectest failures. - [#1232](https://github.com/wasmerio/wasmer/pull/1232) `wasmer-interface-types` has a WAT decoder. From 4012645aeea76614b2b4e072da78fdd893cfb916 Mon Sep 17 00:00:00 2001 From: losfair Date: Sat, 29 Feb 2020 11:13:34 +0800 Subject: [PATCH 19/21] Fix `CodeMemory` doc comments. --- lib/runtime-core/src/loader.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/runtime-core/src/loader.rs b/lib/runtime-core/src/loader.rs index ea1ca0130ac..738c8dfef83 100644 --- a/lib/runtime-core/src/loader.rs +++ b/lib/runtime-core/src/loader.rs @@ -140,12 +140,12 @@ impl CodeMemory { unimplemented!("CodeMemory::new"); } - /// Makes this code memory executable. + /// Makes this code memory executable and not writable. pub fn make_executable(&self) { unimplemented!("CodeMemory::make_executable"); } - /// Makes this code memory writable. + /// Makes this code memory writable and not executable. pub fn make_writable(&self) { unimplemented!("CodeMemory::make_writable"); } @@ -185,14 +185,14 @@ impl CodeMemory { } } - /// Makes this code memory executable. + /// Makes this code memory executable and not writable. pub fn make_executable(&self) { if unsafe { mprotect(self.ptr as _, self.size, PROT_READ | PROT_EXEC) } != 0 { panic!("cannot set code memory to executable"); } } - /// Makes this code memory writable. + /// Makes this code memory writable and not executable. pub fn make_writable(&self) { if unsafe { mprotect(self.ptr as _, self.size, PROT_READ | PROT_WRITE) } != 0 { panic!("cannot set code memory to writable"); From d443ad8d4069adc79326f7c84b212027d488413f Mon Sep 17 00:00:00 2001 From: losfair Date: Sat, 29 Feb 2020 11:15:09 +0800 Subject: [PATCH 20/21] Remove outdated comment. --- lib/runtime-core/src/trampoline_x64.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs index 1fe5d674bc5..bf821ca1539 100644 --- a/lib/runtime-core/src/trampoline_x64.rs +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -54,7 +54,6 @@ struct AllocState { impl TrampBuffer { /// Creates a trampoline buffer with a given (virtual) size. fn new(size: usize) -> TrampBuffer { - // Pre-allocate 64 MiB of virtual memory for code. let mem = CodeMemory::new(size); mem.make_writable_executable(); TrampBuffer { From d9e744d9dcae54710a5bdf3988c9badb63abeef4 Mon Sep 17 00:00:00 2001 From: losfair Date: Wed, 4 Mar 2020 01:56:48 +0800 Subject: [PATCH 21/21] Resolve review comments. --- lib/runtime-core/src/trampoline_x64.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs index bf821ca1539..04809832151 100644 --- a/lib/runtime-core/src/trampoline_x64.rs +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -48,6 +48,8 @@ struct TrampBuffer { /// The allocation state of a `TrampBuffer`. struct AllocState { /// Records all allocated blocks in `buffer`. + /// + /// Maps the start address of each block to its end address. blocks: BTreeMap, } @@ -103,8 +105,6 @@ impl TrampBuffer { if self.buffer.len() - assumed_start < buf.len() { // No more free space. Cannot allocate. return None; - } else { - // Extend towards the end. } }