Redesign the interface between the runtime and the backends.

This removes the ProtectedCaller and FuncResolver traits, simplifying call implementations and improving dynamic call throughput.
This commit is contained in:
Lachlan Sneff
2019-04-11 18:01:54 -07:00
parent 11b6a5d02d
commit d8c6f76846
8 changed files with 230 additions and 165 deletions

1
Cargo.lock generated
View File

@ -2363,6 +2363,7 @@ dependencies = [
"serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_bytes 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde_bytes 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmparser 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -17,6 +17,7 @@ indexmap = "1.0.2"
errno = "0.2.4" errno = "0.2.4"
libc = "0.2.49" libc = "0.2.49"
hex = "0.3.2" hex = "0.3.2"
smallvec = "0.6.9"
# Dependencies for caching. # Dependencies for caching.
[dependencies.serde] [dependencies.serde]

View File

@ -1,10 +1,8 @@
use crate::{ use crate::{
backing::ImportBacking,
error::CompileResult, error::CompileResult,
error::RuntimeResult,
module::ModuleInner, module::ModuleInner,
typed_func::Wasm, typed_func::Wasm,
types::{FuncIndex, LocalFuncIndex, SigIndex, Value}, types::{LocalFuncIndex, SigIndex},
vm, vm,
}; };
@ -67,59 +65,25 @@ pub trait Compiler {
unsafe fn from_cache(&self, cache: Artifact, _: Token) -> Result<ModuleInner, CacheError>; unsafe fn from_cache(&self, cache: Artifact, _: Token) -> Result<ModuleInner, CacheError>;
} }
/// The functionality exposed by this trait is expected to be used
/// for calling functions exported by a webassembly module from
/// host code only.
pub trait ProtectedCaller: Send + Sync {
/// This calls the exported function designated by `local_func_index`.
/// Important to note, this supports calling imported functions that are
/// then exported.
///
/// It's invalid to attempt to call a local function that isn't exported and
/// the implementation is expected to check for that. The implementation
/// is also expected to check for correct parameter types and correct
/// parameter number.
///
/// The `returns` parameter is filled with dummy values when passed in and upon function
/// return, will be filled with the return values of the wasm function, as long as the
/// call completed successfully.
///
/// The existance of the Token parameter ensures that this can only be called from
/// within the runtime crate.
///
/// TODO(lachlan): Now that `get_wasm_trampoline` exists, `ProtectedCaller::call`
/// can be removed. That should speed up calls a little bit, since sanity checks
/// would only occur once.
fn call(
&self,
module: &ModuleInner,
func_index: FuncIndex,
params: &[Value],
import_backing: &ImportBacking,
vmctx: *mut vm::Ctx,
_: Token,
) -> RuntimeResult<Vec<Value>>;
/// A wasm trampoline contains the necesarry data to dynamically call an exported wasm function.
/// Given a particular signature index, we are returned a trampoline that is matched with that
/// signature and an invoke function that can call the trampoline.
fn get_wasm_trampoline(&self, module: &ModuleInner, sig_index: SigIndex) -> Option<Wasm>;
fn get_early_trapper(&self) -> Box<dyn UserTrapper>;
}
pub trait UserTrapper { pub trait UserTrapper {
unsafe fn do_early_trap(&self, data: Box<dyn Any>) -> !; unsafe fn do_early_trap(&self, data: Box<dyn Any>) -> !;
} }
pub trait FuncResolver: Send + Sync { pub trait RunnableModule: Send + Sync {
/// This returns a pointer to the function designated by the `local_func_index` /// This returns a pointer to the function designated by the `local_func_index`
/// parameter. /// parameter.
fn get( fn get_func(
&self, &self,
module: &ModuleInner, info: &ModuleInfo,
local_func_index: LocalFuncIndex, local_func_index: LocalFuncIndex,
) -> Option<NonNull<vm::Func>>; ) -> Option<NonNull<vm::Func>>;
/// A wasm trampoline contains the necesarry data to dynamically call an exported wasm function.
/// Given a particular signature index, we are returned a trampoline that is matched with that
/// signature and an invoke function that can call the trampoline.
fn get_trampoline(&self, info: &ModuleInfo, sig_index: SigIndex) -> Option<Wasm>;
fn get_early_trapper(&self) -> Box<dyn UserTrapper>;
} }
pub trait CacheGen: Send + Sync { pub trait CacheGen: Send + Sync {

View File

@ -72,8 +72,8 @@ impl LocalBacking {
(0..module.info.func_assoc.len() - module.info.imported_functions.len()) (0..module.info.func_assoc.len() - module.info.imported_functions.len())
.map(|index| { .map(|index| {
module module
.func_resolver .runnable_module
.get(module, LocalFuncIndex::new(index)) .get_func(&module.info, LocalFuncIndex::new(index))
.unwrap() .unwrap()
.as_ptr() as *const _ .as_ptr() as *const _
}) })
@ -216,8 +216,8 @@ impl LocalBacking {
let (func, ctx) = match func_index.local_or_import(&module.info) { let (func, ctx) = match func_index.local_or_import(&module.info) {
LocalOrImport::Local(local_func_index) => ( LocalOrImport::Local(local_func_index) => (
module module
.func_resolver .runnable_module
.get(module, local_func_index) .get_func(&module.info, local_func_index)
.unwrap() .unwrap()
.as_ptr() .as_ptr()
as *const vm::Func, as *const vm::Func,
@ -255,8 +255,8 @@ impl LocalBacking {
let (func, ctx) = match func_index.local_or_import(&module.info) { let (func, ctx) = match func_index.local_or_import(&module.info) {
LocalOrImport::Local(local_func_index) => ( LocalOrImport::Local(local_func_index) => (
module module
.func_resolver .runnable_module
.get(module, local_func_index) .get_func(&module.info, local_func_index)
.unwrap() .unwrap()
.as_ptr() .as_ptr()
as *const vm::Func, as *const vm::Func,

View File

@ -1,18 +1,19 @@
use crate::{ use crate::{
backend::Token, backend::{RunnableModule, Token},
backing::{ImportBacking, LocalBacking}, backing::{ImportBacking, LocalBacking},
error::{CallError, CallResult, ResolveError, ResolveResult, Result}, error::{CallError, CallResult, ResolveError, ResolveResult, Result, RuntimeError},
export::{Context, Export, ExportIter, FuncPointer}, export::{Context, Export, ExportIter, FuncPointer},
global::Global, global::Global,
import::{ImportObject, LikeNamespace}, import::{ImportObject, LikeNamespace},
memory::Memory, memory::Memory,
module::{ExportIndex, Module, ModuleInner}, module::{ExportIndex, Module, ModuleInfo, ModuleInner},
sig_registry::SigRegistry, sig_registry::SigRegistry,
table::Table, table::Table,
typed_func::{Func, Wasm, WasmTypeList}, typed_func::{Func, Wasm, WasmTrapInfo, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value}, types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Type, Value},
vm, vm,
}; };
use smallvec::{smallvec, SmallVec};
use std::{mem, ptr::NonNull, sync::Arc}; use std::{mem, ptr::NonNull, sync::Arc};
pub(crate) struct InstanceInner { pub(crate) struct InstanceInner {
@ -82,7 +83,45 @@ impl Instance {
}; };
if let Some(start_index) = instance.module.info.start_func { if let Some(start_index) = instance.module.info.start_func {
instance.call_with_index(start_index, &[])?; // We know that the start function takes no arguments and returns no values.
// Therefore, we can call it without doing any signature checking, etc.
let func_ptr = match start_index.local_or_import(&instance.module.info) {
LocalOrImport::Local(local_func_index) => instance
.module
.runnable_module
.get_func(&instance.module.info, local_func_index)
.unwrap(),
LocalOrImport::Import(import_func_index) => NonNull::new(
instance.inner.import_backing.vm_functions[import_func_index].func as *mut _,
)
.unwrap(),
};
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
}
};
let sig_index = *instance
.module
.info
.func_assoc
.get(start_index)
.expect("broken invariant, incorrect func index");
let wasm_trampoline = instance
.module
.runnable_module
.get_trampoline(&instance.module.info, sig_index)
.expect("wasm trampoline");
let start_func: Func<(), (), Wasm> =
unsafe { Func::from_raw_parts(wasm_trampoline, func_ptr, ctx_ptr) };
start_func.call()?;
} }
Ok(instance) Ok(instance)
@ -147,15 +186,15 @@ impl Instance {
let func_wasm_inner = self let func_wasm_inner = self
.module .module
.protected_caller .runnable_module
.get_wasm_trampoline(&self.module, sig_index) .get_trampoline(&self.module.info, sig_index)
.unwrap(); .unwrap();
let func_ptr = match func_index.local_or_import(&self.module.info) { let func_ptr = match func_index.local_or_import(&self.module.info) {
LocalOrImport::Local(local_func_index) => self LocalOrImport::Local(local_func_index) => self
.module .module
.func_resolver .runnable_module
.get(&self.module, local_func_index) .get_func(&self.module.info, local_func_index)
.unwrap(), .unwrap(),
LocalOrImport::Import(import_func_index) => NonNull::new( LocalOrImport::Import(import_func_index) => NonNull::new(
self.inner.import_backing.vm_functions[import_func_index].func as *mut _, self.inner.import_backing.vm_functions[import_func_index].func as *mut _,
@ -245,7 +284,7 @@ impl Instance {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn call(&self, name: &str, args: &[Value]) -> CallResult<Vec<Value>> { pub fn call(&self, name: &str, params: &[Value]) -> CallResult<Vec<Value>> {
let export_index = let export_index =
self.module self.module
.info .info
@ -264,7 +303,19 @@ impl Instance {
.into()); .into());
}; };
self.call_with_index(func_index, args) let mut results = Vec::new();
call_func_with_index(
&self.module.info,
&*self.module.runnable_module,
&self.inner.import_backing,
self.inner.vmctx,
func_index,
params,
&mut results,
)?;
Ok(results)
} }
/// Returns an immutable reference to the /// Returns an immutable reference to the
@ -295,45 +346,6 @@ impl Instance {
} }
} }
impl Instance {
fn call_with_index(&self, func_index: FuncIndex, args: &[Value]) -> CallResult<Vec<Value>> {
let sig_index = *self
.module
.info
.func_assoc
.get(func_index)
.expect("broken invariant, incorrect func index");
let signature = &self.module.info.signatures[sig_index];
if !signature.check_param_value_types(args) {
Err(ResolveError::Signature {
expected: signature.clone(),
found: args.iter().map(|val| val.ty()).collect(),
})?
}
let vmctx = 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
}
};
let token = Token::generate();
let returns = self.module.protected_caller.call(
&self.module,
func_index,
args,
&self.inner.import_backing,
vmctx,
token,
)?;
Ok(returns)
}
}
impl InstanceInner { impl InstanceInner {
pub(crate) fn get_export_from_index( pub(crate) fn get_export_from_index(
&self, &self,
@ -382,8 +394,8 @@ impl InstanceInner {
let (func_ptr, ctx) = match func_index.local_or_import(&module.info) { let (func_ptr, ctx) = match func_index.local_or_import(&module.info) {
LocalOrImport::Local(local_func_index) => ( LocalOrImport::Local(local_func_index) => (
module module
.func_resolver .runnable_module
.get(&module, local_func_index) .get_func(&module.info, local_func_index)
.expect("broken invariant, func resolver not synced with module.exports") .expect("broken invariant, func resolver not synced with module.exports")
.cast() .cast()
.as_ptr() as *const _, .as_ptr() as *const _,
@ -452,6 +464,128 @@ impl LikeNamespace for Instance {
} }
} }
#[must_use]
fn call_func_with_index(
info: &ModuleInfo,
runnable: &dyn RunnableModule,
import_backing: &ImportBacking,
local_ctx: *mut vm::Ctx,
func_index: FuncIndex,
args: &[Value],
rets: &mut Vec<Value>,
) -> CallResult<()> {
rets.clear();
let sig_index = *info
.func_assoc
.get(func_index)
.expect("broken invariant, incorrect func index");
let signature = &info.signatures[sig_index];
let num_results = signature.returns().len();
rets.reserve(num_results);
if !signature.check_param_value_types(args) {
Err(ResolveError::Signature {
expected: signature.clone(),
found: args.iter().map(|val| val.ty()).collect(),
})?
}
let func_ptr = match func_index.local_or_import(info) {
LocalOrImport::Local(local_func_index) => {
runnable.get_func(info, local_func_index).unwrap()
}
LocalOrImport::Import(import_func_index) => {
NonNull::new(import_backing.vm_functions[import_func_index].func as *mut _).unwrap()
}
};
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
}
};
let raw_args: SmallVec<[u64; 8]> = args
.iter()
.map(|v| match v {
Value::I32(i) => *i as u64,
Value::I64(i) => *i as u64,
Value::F32(f) => f.to_bits() as u64,
Value::F64(f) => f.to_bits(),
})
.collect();
let Wasm {
trampoline,
invoke,
invoke_env,
} = runnable
.get_trampoline(info, sig_index)
.expect("wasm trampoline");
let run_wasm = |result_space: *mut u64| unsafe {
let mut trap_info = WasmTrapInfo::Unknown;
let success = invoke(
trampoline,
ctx_ptr,
func_ptr,
raw_args.as_ptr(),
result_space,
&mut trap_info,
invoke_env,
);
if success {
Ok(())
} else {
Err(RuntimeError::Trap {
msg: trap_info.to_string().into(),
})
}
};
let raw_to_value = |raw, ty| match ty {
Type::I32 => Value::I32(raw as i32),
Type::I64 => Value::I64(raw as i64),
Type::F32 => Value::F32(f32::from_bits(raw as u32)),
Type::F64 => Value::F64(f64::from_bits(raw)),
};
match signature.returns() {
&[] => {
run_wasm(0 as *mut u64)?;
Ok(())
}
&[ty] => {
let mut result = 0u64;
run_wasm(&mut result)?;
rets.push(raw_to_value(result, ty));
Ok(())
}
result_tys @ _ => {
let mut results: SmallVec<[u64; 8]> = smallvec![0; num_results];
run_wasm(results.as_mut_ptr())?;
rets.extend(
results
.iter()
.zip(result_tys.iter())
.map(|(&raw, &ty)| raw_to_value(raw, ty)),
);
Ok(())
}
}
}
/// A representation of an exported WebAssembly function. /// A representation of an exported WebAssembly function.
pub struct DynFunc<'a> { pub struct DynFunc<'a> {
pub(crate) signature: Arc<FuncSig>, pub(crate) signature: Arc<FuncSig>,
@ -484,32 +618,19 @@ impl<'a> DynFunc<'a> {
/// # } /// # }
/// ``` /// ```
pub fn call(&self, params: &[Value]) -> CallResult<Vec<Value>> { pub fn call(&self, params: &[Value]) -> CallResult<Vec<Value>> {
if !self.signature.check_param_value_types(params) { let mut results = Vec::new();
Err(ResolveError::Signature {
expected: (*self.signature).clone(),
found: params.iter().map(|val| val.ty()).collect(),
})?
}
let vmctx = match self.func_index.local_or_import(&self.module.info) { call_func_with_index(
LocalOrImport::Local(_) => self.instance_inner.vmctx, &self.module.info,
LocalOrImport::Import(imported_func_index) => { &*self.module.runnable_module,
self.instance_inner.import_backing.vm_functions[imported_func_index].vmctx &self.instance_inner.import_backing,
} self.instance_inner.vmctx,
};
let token = Token::generate();
let returns = self.module.protected_caller.call(
&self.module,
self.func_index, self.func_index,
params, params,
&self.instance_inner.import_backing, &mut results,
vmctx,
token,
)?; )?;
Ok(returns) Ok(results)
} }
pub fn signature(&self) -> &FuncSig { pub fn signature(&self) -> &FuncSig {
@ -520,8 +641,8 @@ impl<'a> DynFunc<'a> {
match self.func_index.local_or_import(&self.module.info) { match self.func_index.local_or_import(&self.module.info) {
LocalOrImport::Local(local_func_index) => self LocalOrImport::Local(local_func_index) => self
.module .module
.func_resolver .runnable_module
.get(self.module, local_func_index) .get_func(&self.module.info, local_func_index)
.unwrap() .unwrap()
.as_ptr(), .as_ptr(),
LocalOrImport::Import(import_func_index) => { LocalOrImport::Import(import_func_index) => {

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
backend::{Backend, FuncResolver, ProtectedCaller}, backend::{Backend, RunnableModule},
cache::{Artifact, Error as CacheError}, cache::{Artifact, Error as CacheError},
error, error,
import::ImportObject, import::ImportObject,
@ -22,9 +22,7 @@ use std::sync::Arc;
/// This is used to instantiate a new WebAssembly module. /// This is used to instantiate a new WebAssembly module.
#[doc(hidden)] #[doc(hidden)]
pub struct ModuleInner { pub struct ModuleInner {
pub func_resolver: Box<dyn FuncResolver>, pub runnable_module: Box<dyn RunnableModule>,
pub protected_caller: Box<dyn ProtectedCaller>,
pub cache_gen: Box<dyn CacheGen>, pub cache_gen: Box<dyn CacheGen>,
pub info: ModuleInfo, pub info: ModuleInfo,
@ -96,7 +94,7 @@ impl Module {
pub(crate) fn new(inner: Arc<ModuleInner>) -> Self { pub(crate) fn new(inner: Arc<ModuleInner>) -> Self {
unsafe { unsafe {
EARLY_TRAPPER EARLY_TRAPPER
.with(|ucell| *ucell.get() = Some(inner.protected_caller.get_early_trapper())); .with(|ucell| *ucell.get() = Some(inner.runnable_module.get_early_trapper()));
} }
Module { inner } Module { inner }
} }

View File

@ -71,9 +71,9 @@ pub type Invoke = unsafe extern "C" fn(
/// as well as the environment that the invoke function may or may not require. /// as well as the environment that the invoke function may or may not require.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct Wasm { pub struct Wasm {
trampoline: Trampoline, pub(crate) trampoline: Trampoline,
invoke: Invoke, pub(crate) invoke: Invoke,
invoke_env: Option<NonNull<c_void>>, pub(crate) invoke_env: Option<NonNull<c_void>>,
} }
impl Wasm { impl Wasm {

View File

@ -544,42 +544,23 @@ mod vm_ctx_tests {
fn generate_module() -> ModuleInner { fn generate_module() -> ModuleInner {
use super::Func; use super::Func;
use crate::backend::{ use crate::backend::{sys::Memory, Backend, CacheGen, RunnableModule, UserTrapper};
sys::Memory, Backend, CacheGen, FuncResolver, ProtectedCaller, Token, UserTrapper,
};
use crate::cache::Error as CacheError; use crate::cache::Error as CacheError;
use crate::error::RuntimeResult;
use crate::typed_func::Wasm; use crate::typed_func::Wasm;
use crate::types::{FuncIndex, LocalFuncIndex, SigIndex, Value}; use crate::types::{LocalFuncIndex, SigIndex};
use hashbrown::HashMap; use hashbrown::HashMap;
use std::ptr::NonNull; use std::ptr::NonNull;
struct Placeholder; struct Placeholder;
impl FuncResolver for Placeholder { impl RunnableModule for Placeholder {
fn get( fn get_func(
&self, &self,
_module: &ModuleInner, _module: &ModuleInfo,
_local_func_index: LocalFuncIndex, _local_func_index: LocalFuncIndex,
) -> Option<NonNull<Func>> { ) -> Option<NonNull<Func>> {
None None
} }
}
impl ProtectedCaller for Placeholder { fn get_trampoline(&self, _module: &ModuleInfo, _sig_index: SigIndex) -> Option<Wasm> {
fn call(
&self,
_module: &ModuleInner,
_func_index: FuncIndex,
_params: &[Value],
_import_backing: &ImportBacking,
_vmctx: *mut Ctx,
_: Token,
) -> RuntimeResult<Vec<Value>> {
Ok(vec![])
}
fn get_wasm_trampoline(
&self,
_module: &ModuleInner,
_sig_index: SigIndex,
) -> Option<Wasm> {
unimplemented!() unimplemented!()
} }
fn get_early_trapper(&self) -> Box<dyn UserTrapper> { fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
@ -596,8 +577,7 @@ mod vm_ctx_tests {
} }
ModuleInner { ModuleInner {
func_resolver: Box::new(Placeholder), runnable_module: Box::new(Placeholder),
protected_caller: Box::new(Placeholder),
cache_gen: Box::new(Placeholder), cache_gen: Box::new(Placeholder),
info: ModuleInfo { info: ModuleInfo {
memories: Map::new(), memories: Map::new(),