Skip to content

Commit

Permalink
Try #1217:
Browse files Browse the repository at this point in the history
  • Loading branch information
bors[bot] authored Feb 24, 2020
2 parents ad04e07 + b67acbc commit 7d5d5b9
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 9 deletions.
31 changes: 29 additions & 2 deletions lib/runtime-core-tests/tests/imports.rs
Original file line number Diff line number Diff line change
@@ -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::{ErasedFunc, Func},
types::{FuncSig, MemoryDescriptor, Type, Value},
units::Pages,
vm, Instance,
};
use wasmer_runtime_core_tests::{get_compiler, wat2wasm};

Expand Down Expand Up @@ -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)))
Expand All @@ -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)
Expand Down Expand Up @@ -142,6 +154,16 @@ fn imported_functions_forms(test: &dyn Fn(&Instance)) {
Ok(n + 1)
}),

"callback_closure_polymorphic" => ErasedFunc::new_polymorphic(
Arc::new(FuncSig::new(vec![Type::I32], vec![Type::I32])),
|_, params| -> Vec<Value> {
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<i32, ()> {
let shift_ = shift + memory.view::<i32>()[0].get();
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 18 additions & 2 deletions lib/runtime-core/src/loader.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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,
)
Expand All @@ -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)]
Expand Down
51 changes: 51 additions & 0 deletions lib/runtime-core/src/trampoline_x64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -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<NonNull<u8>> {
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.
Expand Down Expand Up @@ -219,6 +265,11 @@ impl TrampolineBufferBuilder {
idx
}

/// Appends to the global trampoline buffer.
pub fn append_global(self) -> Option<NonNull<u8>> {
TRAMPOLINES.append(&self.code)
}

/// Consumes the builder and builds the trampoline buffer.
pub fn build(self) -> TrampolineBuffer {
get_context(); // ensure lazy initialization is completed
Expand Down
149 changes: 146 additions & 3 deletions lib/runtime-core/src/typed_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,69 @@ where
}
}

/// Represents a type-erased function provided by either the host or the WebAssembly program.
#[allow(dead_code)]
pub struct ErasedFunc<'a> {
inner: Box<dyn Kind>,

/// The function pointer.
func: NonNull<vm::Func>,

/// The function environment.
func_env: Option<NonNull<vm::FuncEnv>>,

/// 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<FuncSig>,

_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,

/// The function pointer.
func: NonNull<vm::Func>,

/// The function environment.
func_env: Option<NonNull<vm::FuncEnv>>,

/// The famous `vm::Ctx`.
vmctx: *mut vm::Ctx,

_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<Func<'a, Args, Rets, Inner>> 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,
Expand Down Expand Up @@ -229,7 +280,7 @@ where
Rets: WasmTypeList,
{
/// Creates a new `Func`.
pub fn new<F, Kind>(func: F) -> Func<'a, Args, Rets, Host>
pub fn new<F, Kind>(func: F) -> Self
where
Kind: HostFunctionKind,
F: HostFunction<Kind, Args, Rets>,
Expand All @@ -246,6 +297,83 @@ where
}
}

impl<'a> ErasedFunc<'a> {
/// Creates a polymorphic function.
#[allow(unused_variables)]
#[cfg(all(unix, target_arch = "x86_64"))]
pub fn new_polymorphic<F>(signature: Arc<FuncSig>, func: F) -> Self
where
F: Fn(&mut vm::Ctx, &[crate::types::Value]) -> Vec<crate::types::Value> + 'static,
{
use crate::trampoline_x64::*;
use crate::types::Value;
use std::convert::TryFrom;

struct PolymorphicContext {
arg_types: Vec<Type>,
func: Box<dyn Fn(&mut vm::Ctx, &[Value]) -> Vec<Value>>,
}
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<Value> = 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");

ErasedFunc {
inner: Box::new(Host(())),
func: ptr.cast::<vm::Func>(),
func_env: None,
vmctx: ptr::null_mut(),
signature,
_phantom: PhantomData,
}
}
}

impl<'a, Args, Rets, Inner> Func<'a, Args, Rets, Inner>
where
Args: WasmTypeList,
Expand Down Expand Up @@ -674,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,
Expand All @@ -686,12 +830,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: Arc::new(FuncSig::new(Args::types(), Rets::types())),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions lib/runtime-core/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 7d5d5b9

Please sign in to comment.