Merge branch 'master' into docs/better-runtime-docs

# Conflicts:
#	lib/runtime-core/src/vm.rs
This commit is contained in:
Syrus
2019-05-13 11:08:00 -07:00
268 changed files with 19736 additions and 4915 deletions

View File

@ -1,9 +1,8 @@
use crate::{
backing::ImportBacking,
error::CompileResult,
error::RuntimeResult,
module::ModuleInner,
types::{FuncIndex, LocalFuncIndex, Value},
typed_func::Wasm,
types::{LocalFuncIndex, SigIndex},
vm,
};
@ -14,6 +13,8 @@ use crate::{
};
use std::{any::Any, ptr::NonNull};
use hashbrown::HashMap;
pub mod sys {
pub use crate::sys::*;
}
@ -22,6 +23,7 @@ pub use crate::sig_registry::SigRegistry;
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub enum Backend {
Cranelift,
Singlepass,
LLVM,
}
@ -37,64 +39,49 @@ impl Token {
}
}
/// Configuration data for the compiler
pub struct CompilerConfig {
/// Symbol information generated from emscripten; used for more detailed debug messages
pub symbol_map: Option<HashMap<u32, String>>,
}
impl Default for CompilerConfig {
fn default() -> CompilerConfig {
CompilerConfig { symbol_map: None }
}
}
pub trait Compiler {
/// Compiles a `Module` from WebAssembly binary format.
/// The `CompileToken` parameter ensures that this can only
/// be called from inside the runtime.
fn compile(&self, wasm: &[u8], _: Token) -> CompileResult<ModuleInner>;
fn compile(
&self,
wasm: &[u8],
comp_conf: CompilerConfig,
_: Token,
) -> CompileResult<ModuleInner>;
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.
fn call(
pub trait RunnableModule: Send + Sync {
/// This returns a pointer to the function designated by the `local_func_index`
/// parameter.
fn get_func(
&self,
module: &ModuleInner,
func_index: FuncIndex,
params: &[Value],
import_backing: &ImportBacking,
vmctx: *mut vm::Ctx,
_: Token,
) -> RuntimeResult<Vec<Value>>;
info: &ModuleInfo,
local_func_index: LocalFuncIndex,
) -> Option<NonNull<vm::Func>>;
fn get_early_trapper(&self) -> Box<dyn UserTrapper>;
}
/// 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>;
pub trait UserTrapper {
unsafe fn do_early_trap(&self, data: Box<dyn Any>) -> !;
}
pub trait FuncResolver: Send + Sync {
/// This returns a pointer to the function designated by the `local_func_index`
/// parameter.
fn get(
&self,
module: &ModuleInner,
local_func_index: LocalFuncIndex,
) -> Option<NonNull<vm::Func>>;
}
pub trait CacheGen: Send + Sync {
fn generate_cache(
&self,
module: &ModuleInner,
) -> Result<(Box<ModuleInfo>, Box<[u8]>, Memory), CacheError>;
fn generate_cache(&self) -> Result<(Box<[u8]>, Memory), CacheError>;
}

View File

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

View File

@ -207,3 +207,7 @@ pub trait Cache {
fn load(&self, key: WasmHash) -> Result<Module, Self::LoadError>;
fn store(&mut self, key: WasmHash, module: Module) -> Result<(), Self::StoreError>;
}
/// A unique ID generated from the version of Wasmer for use with cache versioning
pub const WASMER_VERSION_HASH: &'static str =
include_str!(concat!(env!("OUT_DIR"), "/wasmer_version_hash.txt"));

View File

@ -0,0 +1,264 @@
use crate::{
backend::RunnableModule,
backend::{Backend, CacheGen, Compiler, CompilerConfig, Token},
cache::{Artifact, Error as CacheError},
error::{CompileError, CompileResult},
module::{ModuleInfo, ModuleInner},
structures::Map,
types::{FuncIndex, FuncSig, SigIndex},
};
use smallvec::SmallVec;
use std::fmt;
use std::fmt::Debug;
use std::marker::PhantomData;
use wasmparser::{Operator, Type as WpType};
#[derive(Debug)]
pub enum Event<'a, 'b> {
Internal(InternalEvent),
Wasm(&'b Operator<'a>),
}
pub enum InternalEvent {
FunctionBegin(u32),
FunctionEnd,
Breakpoint(Box<Fn(BkptInfo) + Send + Sync + 'static>),
SetInternal(u32),
GetInternal(u32),
}
impl fmt::Debug for InternalEvent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InternalEvent::FunctionBegin(_) => write!(f, "FunctionBegin"),
InternalEvent::FunctionEnd => write!(f, "FunctionEnd"),
InternalEvent::Breakpoint(_) => write!(f, "Breakpoint"),
InternalEvent::SetInternal(_) => write!(f, "SetInternal"),
InternalEvent::GetInternal(_) => write!(f, "GetInternal"),
}
}
}
pub struct BkptInfo {}
pub trait ModuleCodeGenerator<FCG: FunctionCodeGenerator<E>, RM: RunnableModule, E: Debug> {
fn new() -> Self;
fn backend_id() -> Backend;
fn check_precondition(&mut self, module_info: &ModuleInfo) -> Result<(), E>;
/// Creates a new function and returns the function-scope code generator for it.
fn next_function(&mut self) -> Result<&mut FCG, E>;
fn finalize(self, module_info: &ModuleInfo) -> Result<(RM, Box<dyn CacheGen>), E>;
fn feed_signatures(&mut self, signatures: Map<SigIndex, FuncSig>) -> Result<(), E>;
/// Sets function signatures.
fn feed_function_signatures(&mut self, assoc: Map<FuncIndex, SigIndex>) -> Result<(), E>;
/// Adds an import function.
fn feed_import_function(&mut self) -> Result<(), E>;
unsafe fn from_cache(cache: Artifact, _: Token) -> Result<ModuleInner, CacheError>;
}
pub struct StreamingCompiler<
MCG: ModuleCodeGenerator<FCG, RM, E>,
FCG: FunctionCodeGenerator<E>,
RM: RunnableModule + 'static,
E: Debug,
CGEN: Fn() -> MiddlewareChain,
> {
middleware_chain_generator: CGEN,
_phantom_mcg: PhantomData<MCG>,
_phantom_fcg: PhantomData<FCG>,
_phantom_rm: PhantomData<RM>,
_phantom_e: PhantomData<E>,
}
pub struct SimpleStreamingCompilerGen<
MCG: ModuleCodeGenerator<FCG, RM, E>,
FCG: FunctionCodeGenerator<E>,
RM: RunnableModule + 'static,
E: Debug,
> {
_phantom_mcg: PhantomData<MCG>,
_phantom_fcg: PhantomData<FCG>,
_phantom_rm: PhantomData<RM>,
_phantom_e: PhantomData<E>,
}
impl<
MCG: ModuleCodeGenerator<FCG, RM, E>,
FCG: FunctionCodeGenerator<E>,
RM: RunnableModule + 'static,
E: Debug,
> SimpleStreamingCompilerGen<MCG, FCG, RM, E>
{
pub fn new() -> StreamingCompiler<MCG, FCG, RM, E, impl Fn() -> MiddlewareChain> {
StreamingCompiler::new(|| MiddlewareChain::new())
}
}
impl<
MCG: ModuleCodeGenerator<FCG, RM, E>,
FCG: FunctionCodeGenerator<E>,
RM: RunnableModule + 'static,
E: Debug,
CGEN: Fn() -> MiddlewareChain,
> StreamingCompiler<MCG, FCG, RM, E, CGEN>
{
pub fn new(chain_gen: CGEN) -> Self {
Self {
middleware_chain_generator: chain_gen,
_phantom_mcg: PhantomData,
_phantom_fcg: PhantomData,
_phantom_rm: PhantomData,
_phantom_e: PhantomData,
}
}
}
impl<
MCG: ModuleCodeGenerator<FCG, RM, E>,
FCG: FunctionCodeGenerator<E>,
RM: RunnableModule + 'static,
E: Debug,
CGEN: Fn() -> MiddlewareChain,
> Compiler for StreamingCompiler<MCG, FCG, RM, E, CGEN>
{
fn compile(
&self,
wasm: &[u8],
compiler_config: CompilerConfig,
_: Token,
) -> CompileResult<ModuleInner> {
let mut mcg = MCG::new();
let mut chain = (self.middleware_chain_generator)();
let info = crate::parse::read_module(
wasm,
MCG::backend_id(),
&mut mcg,
&mut chain,
&compiler_config,
)?;
let (exec_context, cache_gen) =
mcg.finalize(&info)
.map_err(|x| CompileError::InternalError {
msg: format!("{:?}", x),
})?;
Ok(ModuleInner {
cache_gen,
runnable_module: Box::new(exec_context),
info,
})
}
unsafe fn from_cache(
&self,
artifact: Artifact,
token: Token,
) -> Result<ModuleInner, CacheError> {
MCG::from_cache(artifact, token)
}
}
pub struct EventSink<'a, 'b> {
buffer: SmallVec<[Event<'a, 'b>; 2]>,
}
impl<'a, 'b> EventSink<'a, 'b> {
pub fn push(&mut self, ev: Event<'a, 'b>) {
self.buffer.push(ev);
}
}
pub struct MiddlewareChain {
chain: Vec<Box<GenericFunctionMiddleware>>,
}
impl MiddlewareChain {
pub fn new() -> MiddlewareChain {
MiddlewareChain { chain: vec![] }
}
pub fn push<M: FunctionMiddleware + 'static>(&mut self, m: M) {
self.chain.push(Box::new(m));
}
pub(crate) fn run<E: Debug, FCG: FunctionCodeGenerator<E>>(
&mut self,
fcg: Option<&mut FCG>,
ev: Event,
module_info: &ModuleInfo,
) -> Result<(), String> {
let mut sink = EventSink {
buffer: SmallVec::new(),
};
sink.push(ev);
for m in &mut self.chain {
let prev: SmallVec<[Event; 2]> = sink.buffer.drain().collect();
for ev in prev {
m.feed_event(ev, module_info, &mut sink)?;
}
}
if let Some(fcg) = fcg {
for ev in sink.buffer {
fcg.feed_event(ev, module_info)
.map_err(|x| format!("{:?}", x))?;
}
}
Ok(())
}
}
pub trait FunctionMiddleware {
type Error: Debug;
fn feed_event<'a, 'b: 'a>(
&mut self,
op: Event<'a, 'b>,
module_info: &ModuleInfo,
sink: &mut EventSink<'a, 'b>,
) -> Result<(), Self::Error>;
}
pub(crate) trait GenericFunctionMiddleware {
fn feed_event<'a, 'b: 'a>(
&mut self,
op: Event<'a, 'b>,
module_info: &ModuleInfo,
sink: &mut EventSink<'a, 'b>,
) -> Result<(), String>;
}
impl<E: Debug, T: FunctionMiddleware<Error = E>> GenericFunctionMiddleware for T {
fn feed_event<'a, 'b: 'a>(
&mut self,
op: Event<'a, 'b>,
module_info: &ModuleInfo,
sink: &mut EventSink<'a, 'b>,
) -> Result<(), String> {
<Self as FunctionMiddleware>::feed_event(self, op, module_info, sink)
.map_err(|x| format!("{:?}", x))
}
}
/// The function-scope code generator trait.
pub trait FunctionCodeGenerator<E: Debug> {
/// Sets the return type.
fn feed_return(&mut self, ty: WpType) -> Result<(), E>;
/// Adds a parameter to the function.
fn feed_param(&mut self, ty: WpType) -> Result<(), E>;
/// Adds `n` locals to the function.
fn feed_local(&mut self, ty: WpType, n: usize) -> Result<(), E>;
/// Called before the first call to `feed_opcode`.
fn begin_body(&mut self, module_info: &ModuleInfo) -> Result<(), E>;
/// Called for each operator.
fn feed_event(&mut self, op: Event, module_info: &ModuleInfo) -> Result<(), E>;
/// Finalizes the function.
fn finalize(&mut self) -> Result<(), E>;
}

View File

@ -1,4 +1,4 @@
use crate::types::{FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type, Value};
use crate::types::{FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type};
use core::borrow::Borrow;
use std::any::Any;
@ -8,6 +8,7 @@ pub type LinkResult<T> = std::result::Result<T, Vec<LinkError>>;
pub type RuntimeResult<T> = std::result::Result<T, RuntimeError>;
pub type CallResult<T> = std::result::Result<T, CallError>;
pub type ResolveResult<T> = std::result::Result<T, ResolveError>;
pub type ParseResult<T> = std::result::Result<T, ParseError>;
/// This is returned when the chosen compiler is unable to
/// successfully compile the provided webassembly module into
@ -118,11 +119,9 @@ impl std::error::Error for LinkError {}
/// The main way to do this is `Instance.call`.
///
/// Comparing two `RuntimeError`s always evaluates to false.
#[derive(Debug)]
pub enum RuntimeError {
Trap { msg: Box<str> },
Exception { data: Box<[Value]> },
Panic { data: Box<dyn Any> },
Error { data: Box<dyn Any> },
}
impl PartialEq for RuntimeError {
@ -137,14 +136,25 @@ impl std::fmt::Display for RuntimeError {
RuntimeError::Trap { ref msg } => {
write!(f, "WebAssembly trap occured during runtime: {}", msg)
}
RuntimeError::Exception { ref data } => {
write!(f, "Uncaught WebAssembly exception: {:?}", data)
RuntimeError::Error { data } => {
if let Some(s) = data.downcast_ref::<String>() {
write!(f, "\"{}\"", s)
} else if let Some(s) = data.downcast_ref::<&str>() {
write!(f, "\"{}\"", s)
} else {
write!(f, "unknown error")
}
}
RuntimeError::Panic { data: _ } => write!(f, "User-defined \"panic\""),
}
}
}
impl std::fmt::Debug for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl std::error::Error for RuntimeError {}
/// This error type is produced by resolving a wasm function
@ -196,7 +206,6 @@ impl std::error::Error for ResolveError {}
/// be the `CallError::Runtime(RuntimeError)` variant.
///
/// Comparing two `CallError`s always evaluates to false.
#[derive(Debug)]
pub enum CallError {
Resolve(ResolveError),
Runtime(RuntimeError),
@ -217,6 +226,15 @@ impl std::fmt::Display for CallError {
}
}
impl std::fmt::Debug for CallError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
CallError::Resolve(resolve_err) => write!(f, "ResolveError: {:?}", resolve_err),
CallError::Runtime(runtime_err) => write!(f, "RuntimeError: {:?}", runtime_err),
}
}
}
impl std::error::Error for CallError {}
/// This error type is produced when creating something,
@ -445,3 +463,14 @@ impl Into<GrowError> for MemoryProtectionError {
GrowError::CouldNotProtectMemory(self)
}
}
#[derive(Debug)]
pub enum ParseError {
BinaryReadError,
}
impl From<wasmparser::BinaryReaderError> for ParseError {
fn from(_: wasmparser::BinaryReaderError) -> Self {
ParseError::BinaryReadError
}
}

View File

@ -40,13 +40,13 @@ impl FuncPointer {
}
pub struct ExportIter<'a> {
inner: &'a mut InstanceInner,
inner: &'a InstanceInner,
iter: hash_map::Iter<'a, String, ExportIndex>,
module: &'a ModuleInner,
}
impl<'a> ExportIter<'a> {
pub(crate) fn new(module: &'a ModuleInner, inner: &'a mut InstanceInner) -> Self {
pub(crate) fn new(module: &'a ModuleInner, inner: &'a InstanceInner) -> Self {
Self {
inner,
iter: module.info.exports.iter(),

View File

@ -1,12 +1,16 @@
use crate::export::Export;
use hashbrown::{hash_map::Entry, HashMap};
use std::collections::VecDeque;
use std::{
cell::{Ref, RefCell},
ffi::c_void,
rc::Rc,
};
pub trait LikeNamespace {
fn get_export(&self, name: &str) -> Option<Export>;
fn get_exports(&self) -> Vec<(String, Export)>;
fn maybe_insert(&mut self, name: &str, export: Export) -> Option<()>;
}
pub trait IsExport {
@ -42,6 +46,7 @@ impl IsExport for Export {
/// ```
pub struct ImportObject {
map: Rc<RefCell<HashMap<String, Box<dyn LikeNamespace>>>>,
state_creator: Option<Rc<Fn() -> (*mut c_void, fn(*mut c_void))>>,
}
impl ImportObject {
@ -49,9 +54,24 @@ impl ImportObject {
pub fn new() -> Self {
Self {
map: Rc::new(RefCell::new(HashMap::new())),
state_creator: None,
}
}
pub fn new_with_data<F>(state_creator: F) -> Self
where
F: Fn() -> (*mut c_void, fn(*mut c_void)) + 'static,
{
Self {
map: Rc::new(RefCell::new(HashMap::new())),
state_creator: Some(Rc::new(state_creator)),
}
}
pub(crate) fn call_state_creator(&self) -> Option<(*mut c_void, fn(*mut c_void))> {
self.state_creator.as_ref().map(|state_gen| state_gen())
}
/// Register anything that implements `LikeNamespace` as a namespace.
///
/// # Usage:
@ -95,6 +115,54 @@ impl ImportObject {
pub fn clone_ref(&self) -> Self {
Self {
map: Rc::clone(&self.map),
state_creator: self.state_creator.clone(),
}
}
fn get_objects(&self) -> VecDeque<(String, String, Export)> {
let mut out = VecDeque::new();
for (name, ns) in self.map.borrow().iter() {
for (id, exp) in ns.get_exports() {
out.push_back((name.clone(), id, exp));
}
}
out
}
}
pub struct ImportObjectIterator {
elements: VecDeque<(String, String, Export)>,
}
impl Iterator for ImportObjectIterator {
type Item = (String, String, Export);
fn next(&mut self) -> Option<Self::Item> {
self.elements.pop_front()
}
}
impl IntoIterator for ImportObject {
type IntoIter = ImportObjectIterator;
type Item = (String, String, Export);
fn into_iter(self) -> Self::IntoIter {
ImportObjectIterator {
elements: self.get_objects(),
}
}
}
impl Extend<(String, String, Export)> for ImportObject {
fn extend<T: IntoIterator<Item = (String, String, Export)>>(&mut self, iter: T) {
let mut map = self.map.borrow_mut();
for (ns, id, exp) in iter.into_iter() {
if let Some(like_ns) = map.get_mut(&ns) {
like_ns.maybe_insert(&id, exp);
} else {
let mut new_ns = Namespace::new();
new_ns.insert(id, exp);
map.insert(ns, Box::new(new_ns));
}
}
}
}
@ -123,4 +191,76 @@ impl LikeNamespace for Namespace {
fn get_export(&self, name: &str) -> Option<Export> {
self.map.get(name).map(|is_export| is_export.to_export())
}
fn get_exports(&self) -> Vec<(String, Export)> {
self.map
.iter()
.map(|(k, v)| (k.clone(), v.to_export()))
.collect()
}
fn maybe_insert(&mut self, name: &str, export: Export) -> Option<()> {
self.map.insert(name.to_owned(), Box::new(export));
Some(())
}
}
#[cfg(test)]
mod test {
use crate::export::Export;
use crate::global::Global;
use crate::types::Value;
#[test]
fn extending_works() {
let mut imports1 = imports! {
"dog" => {
"happy" => Global::new(Value::I32(0)),
},
};
let imports2 = imports! {
"dog" => {
"small" => Global::new(Value::I32(2)),
},
"cat" => {
"small" => Global::new(Value::I32(3)),
},
};
imports1.extend(imports2);
let cat_ns = imports1.get_namespace("cat").unwrap();
assert!(cat_ns.get_export("small").is_some());
let dog_ns = imports1.get_namespace("dog").unwrap();
assert!(dog_ns.get_export("happy").is_some());
assert!(dog_ns.get_export("small").is_some());
}
#[test]
fn extending_conflict_overwrites() {
let mut imports1 = imports! {
"dog" => {
"happy" => Global::new(Value::I32(0)),
},
};
let imports2 = imports! {
"dog" => {
"happy" => Global::new(Value::I32(4)),
},
};
imports1.extend(imports2);
let dog_ns = imports1.get_namespace("dog").unwrap();
assert!(
if let Export::Global(happy_dog_global) = dog_ns.get_export("happy").unwrap() {
happy_dog_global.get() == Value::I32(4)
} else {
false
}
);
}
}

View File

@ -1,19 +1,20 @@
use crate::{
backend::Token,
backend::RunnableModule,
backing::{ImportBacking, LocalBacking},
error::{CallError, CallResult, ResolveError, ResolveResult, Result},
error::{CallError, CallResult, ResolveError, ResolveResult, Result, RuntimeError},
export::{Context, Export, ExportIter, FuncPointer},
global::Global,
import::{ImportObject, LikeNamespace},
memory::Memory,
module::{ExportIndex, Module, ModuleInner},
module::{ExportIndex, Module, ModuleInfo, ModuleInner},
sig_registry::SigRegistry,
table::Table,
typed_func::{Func, Safe, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value},
typed_func::{Func, Wasm, WasmTrapInfo, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Type, Value},
vm,
};
use std::{mem, sync::Arc};
use smallvec::{smallvec, SmallVec};
use std::{mem, ptr::NonNull, sync::Arc};
pub(crate) struct InstanceInner {
#[allow(dead_code)]
@ -63,7 +64,16 @@ impl Instance {
// Initialize the vm::Ctx in-place after the backing
// has been boxed.
unsafe {
*inner.vmctx = vm::Ctx::new(&mut inner.backing, &mut inner.import_backing, &module)
*inner.vmctx = match imports.call_state_creator() {
Some((data, dtor)) => vm::Ctx::new_with_data(
&mut inner.backing,
&mut inner.import_backing,
&module,
data,
dtor,
),
None => vm::Ctx::new(&mut inner.backing, &mut inner.import_backing, &module),
};
};
let instance = Instance {
@ -73,7 +83,45 @@ impl Instance {
};
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)
@ -98,7 +146,7 @@ impl Instance {
/// # Ok(())
/// # }
/// ```
pub fn func<Args, Rets>(&self, name: &str) -> ResolveResult<Func<Args, Rets, Safe>>
pub fn func<Args, Rets>(&self, name: &str) -> ResolveResult<Func<Args, Rets, Wasm>>
where
Args: WasmTypeList,
Rets: WasmTypeList,
@ -136,20 +184,26 @@ impl Instance {
}
};
let func_wasm_inner = self
.module
.runnable_module
.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
.func_resolver
.get(&self.module, local_func_index)
.unwrap()
.as_ptr(),
LocalOrImport::Import(import_func_index) => {
self.inner.import_backing.vm_functions[import_func_index].func
}
.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 typed_func: Func<Args, Rets, Safe> =
unsafe { Func::new_from_ptr(func_ptr as _, ctx) };
let typed_func: Func<Args, Rets, Wasm> =
unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, ctx) };
Ok(typed_func)
} else {
@ -230,7 +284,7 @@ impl Instance {
/// # 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 =
self.module
.info
@ -249,7 +303,19 @@ impl Instance {
.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
@ -270,8 +336,8 @@ impl Instance {
/// Returns an iterator over all of the items
/// exported from this instance.
pub fn exports(&mut self) -> ExportIter {
ExportIter::new(&self.module, &mut self.inner)
pub fn exports(&self) -> ExportIter {
ExportIter::new(&self.module, &self.inner)
}
/// The module used to instantiate this Instance.
@ -280,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 {
pub(crate) fn get_export_from_index(
&self,
@ -367,8 +394,8 @@ impl InstanceInner {
let (func_ptr, ctx) = match func_index.local_or_import(&module.info) {
LocalOrImport::Local(local_func_index) => (
module
.func_resolver
.get(&module, local_func_index)
.runnable_module
.get_func(&module.info, local_func_index)
.expect("broken invariant, func resolver not synced with module.exports")
.cast()
.as_ptr() as *const _,
@ -427,6 +454,142 @@ impl LikeNamespace for Instance {
Some(self.inner.get_export_from_index(&self.module, export_index))
}
fn get_exports(&self) -> Vec<(String, Export)> {
unimplemented!("Use the exports method instead");
}
fn maybe_insert(&mut self, _name: &str, _export: Export) -> Option<()> {
None
}
}
#[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 mut user_error = None;
let success = invoke(
trampoline,
ctx_ptr,
func_ptr,
raw_args.as_ptr(),
result_space,
&mut trap_info,
&mut user_error,
invoke_env,
);
if success {
Ok(())
} else {
if let Some(data) = user_error {
Err(RuntimeError::Error { data })
} 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.
@ -461,32 +624,19 @@ impl<'a> DynFunc<'a> {
/// # }
/// ```
pub fn call(&self, params: &[Value]) -> CallResult<Vec<Value>> {
if !self.signature.check_param_value_types(params) {
Err(ResolveError::Signature {
expected: (*self.signature).clone(),
found: params.iter().map(|val| val.ty()).collect(),
})?
}
let mut results = Vec::new();
let vmctx = match self.func_index.local_or_import(&self.module.info) {
LocalOrImport::Local(_) => self.instance_inner.vmctx,
LocalOrImport::Import(imported_func_index) => {
self.instance_inner.import_backing.vm_functions[imported_func_index].vmctx
}
};
let token = Token::generate();
let returns = self.module.protected_caller.call(
&self.module,
call_func_with_index(
&self.module.info,
&*self.module.runnable_module,
&self.instance_inner.import_backing,
self.instance_inner.vmctx,
self.func_index,
params,
&self.instance_inner.import_backing,
vmctx,
token,
&mut results,
)?;
Ok(returns)
Ok(results)
}
pub fn signature(&self) -> &FuncSig {
@ -497,8 +647,8 @@ impl<'a> DynFunc<'a> {
match self.func_index.local_or_import(&self.module.info) {
LocalOrImport::Local(local_func_index) => self
.module
.func_resolver
.get(self.module, local_func_index)
.runnable_module
.get_func(&self.module.info, local_func_index)
.unwrap()
.as_ptr(),
LocalOrImport::Import(import_func_index) => {
@ -507,10 +657,3 @@ impl<'a> DynFunc<'a> {
}
}
}
#[doc(hidden)]
impl Instance {
pub fn memory_offset_addr(&self, _: u32, _: usize) -> *const u8 {
unimplemented!()
}
}

View File

@ -1,3 +1,6 @@
#![deny(unused_imports, unused_variables, unused_unsafe, unreachable_patterns)]
#![cfg_attr(nightly, feature(unwind_attributes))]
#[cfg(test)]
#[macro_use]
extern crate field_offset;
@ -12,6 +15,7 @@ pub mod backend;
mod backing;
pub mod cache;
pub mod codegen;
pub mod error;
pub mod export;
pub mod global;
@ -19,11 +23,12 @@ pub mod import;
pub mod instance;
pub mod memory;
pub mod module;
pub mod parse;
mod sig_registry;
pub mod structures;
mod sys;
pub mod table;
mod typed_func;
pub mod typed_func;
pub mod types;
pub mod units;
pub mod vm;
@ -36,7 +41,7 @@ pub use self::error::Result;
#[doc(inline)]
pub use self::import::IsExport;
#[doc(inline)]
pub use self::instance::Instance;
pub use self::instance::{DynFunc, Instance};
#[doc(inline)]
pub use self::module::Module;
#[doc(inline)]
@ -68,7 +73,24 @@ pub fn compile_with(
) -> CompileResult<module::Module> {
let token = backend::Token::generate();
compiler
.compile(wasm, token)
.compile(wasm, Default::default(), token)
.map(|mut inner| {
let inner_info: &mut crate::module::ModuleInfo = &mut inner.info;
inner_info.import_custom_sections(wasm).unwrap();
module::Module::new(Arc::new(inner))
})
}
/// The same as `compile_with` but changes the compiler behavior
/// with the values in the `CompilerConfig`
pub fn compile_with_config(
wasm: &[u8],
compiler: &dyn backend::Compiler,
compiler_config: backend::CompilerConfig,
) -> CompileResult<module::Module> {
let token = backend::Token::generate();
compiler
.compile(wasm, compiler_config, token)
.map(|inner| module::Module::new(Arc::new(inner)))
}
@ -76,13 +98,18 @@ pub fn compile_with(
/// WebAssembly specification. Returns `true` if validation
/// succeeded, `false` if validation failed.
pub fn validate(wasm: &[u8]) -> bool {
validate_and_report_errors(wasm).is_ok()
}
/// The same as `validate` but with an Error message on failure
pub fn validate_and_report_errors(wasm: &[u8]) -> ::std::result::Result<(), String> {
use wasmparser::WasmDecoder;
let mut parser = wasmparser::ValidatingParser::new(wasm, None);
loop {
let state = parser.read();
match *state {
wasmparser::ParserState::EndWasm => break true,
wasmparser::ParserState::Error(_) => break false,
wasmparser::ParserState::EndWasm => break Ok(()),
wasmparser::ParserState::Error(e) => break Err(format!("{}", e)),
_ => {}
}
}

View File

@ -1,8 +1,14 @@
#[macro_export]
#[cfg(feature = "debug")]
macro_rules! debug {
($fmt:expr) => (println!(concat!("wasmer-runtime(:{})::", $fmt), line!()));
($fmt:expr, $($arg:tt)*) => (println!(concat!("wasmer-runtime(:{})::", $fmt, "\n"), line!(), $($arg)*));
($fmt:expr) => (println!(concat!("[{}] wasmer-runtime(:{}) ", $fmt), {
let time = ::std::time::SystemTime::now().duration_since(::std::time::UNIX_EPOCH).expect("Can't get time");
format!("{}.{:03}", time.as_secs(), time.subsec_millis())
}, line!()));
($fmt:expr, $($arg:tt)*) => (println!(concat!("[{}] wasmer-runtime(:{}) ", $fmt, "\n"), {
let time = ::std::time::SystemTime::now().duration_since(::std::time::UNIX_EPOCH).expect("Can't get time");
format!("{}.{:03}", time.as_secs(), time.subsec_millis())
}, line!(), $($arg)*));
}
#[macro_export]
@ -38,6 +44,13 @@ macro_rules! func {
/// },
/// };
///
/// let imports_with_state = imports! {
/// || (0 as _, |_a| {}),
/// "env" => {
/// "foo" => func!(foo),
/// },
/// };
///
/// fn foo(_: &mut Ctx, n: i32) -> i32 {
/// n
/// }
@ -57,6 +70,21 @@ macro_rules! imports {
import_object.register($ns_name, ns);
})*
import_object
}};
($state_gen:expr, $( $ns_name:expr => $ns:tt, )* ) => {{
use $crate::{
import::{ImportObject, Namespace},
};
let mut import_object = ImportObject::new_with_data($state_gen);
$({
let ns = $crate::__imports_internal!($ns);
import_object.register($ns_name, ns);
})*
import_object
}};
}
@ -75,3 +103,15 @@ macro_rules! __imports_internal {
$ns
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! namespace {
( $( $imp_name:expr => $import_item:expr, )* ) => {{
let mut ns = $crate::import::Namespace::new();
$(
ns.insert($imp_name, $import_item);
)*
ns
}};
}

View File

@ -52,6 +52,7 @@ macro_rules! intcast {
}
intcast! { u8 i8 u16 i16 u32 i32 u64 i64 }
#[repr(transparent)]
pub struct Atomic<T> {
v: UnsafeCell<Wrapping<T>>,
}

View File

@ -284,6 +284,7 @@ impl Clone for UnsharedMemory {
}
pub struct SharedMemory {
#[allow(dead_code)]
desc: MemoryDescriptor,
}

View File

@ -1,10 +1,9 @@
use crate::{
backend::{Backend, FuncResolver, ProtectedCaller},
backend::{Backend, RunnableModule},
cache::{Artifact, Error as CacheError},
error,
import::ImportObject,
structures::{Map, TypedIndex},
typed_func::EARLY_TRAPPER,
types::{
FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex,
ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer,
@ -22,9 +21,7 @@ use std::sync::Arc;
/// This is used to instantiate a new WebAssembly module.
#[doc(hidden)]
pub struct ModuleInner {
pub func_resolver: Box<dyn FuncResolver>,
pub protected_caller: Box<dyn ProtectedCaller>,
pub runnable_module: Box<dyn RunnableModule>,
pub cache_gen: Box<dyn CacheGen>,
pub info: ModuleInfo,
@ -56,6 +53,29 @@ pub struct ModuleInfo {
pub namespace_table: StringTable<NamespaceIndex>,
pub name_table: StringTable<NameIndex>,
/// Symbol information from emscripten
pub em_symbol_map: Option<HashMap<u32, String>>,
pub custom_sections: HashMap<String, Vec<u8>>,
}
impl ModuleInfo {
pub fn import_custom_sections(&mut self, wasm: &[u8]) -> crate::error::ParseResult<()> {
let mut parser = wasmparser::ModuleReader::new(wasm)?;
while !parser.eof() {
let section = parser.read()?;
if let wasmparser::SectionCode::Custom { name, kind: _ } = section.code {
let mut reader = section.get_binary_reader();
let len = reader.bytes_remaining();
let bytes = reader.read_bytes(len)?;
let data = bytes.to_vec();
let name = name.to_string();
self.custom_sections.insert(name, data);
}
}
Ok(())
}
}
/// A compiled WebAssembly module.
@ -71,10 +91,6 @@ pub struct Module {
impl Module {
pub(crate) fn new(inner: Arc<ModuleInner>) -> Self {
unsafe {
EARLY_TRAPPER
.with(|ucell| *ucell.get() = Some(inner.protected_caller.get_early_trapper()));
}
Module { inner }
}
@ -105,8 +121,12 @@ impl Module {
}
pub fn cache(&self) -> Result<Artifact, CacheError> {
let (info, backend_metadata, code) = self.inner.cache_gen.generate_cache(&self.inner)?;
Ok(Artifact::from_parts(info, backend_metadata, code))
let (backend_metadata, code) = self.inner.cache_gen.generate_cache()?;
Ok(Artifact::from_parts(
Box::new(self.inner.info.clone()),
backend_metadata,
code,
))
}
pub fn info(&self) -> &ModuleInfo {

View File

@ -0,0 +1,426 @@
use crate::codegen::*;
use crate::{
backend::{Backend, CompilerConfig, RunnableModule},
error::CompileError,
module::{
DataInitializer, ExportIndex, ImportName, ModuleInfo, StringTable, StringTableBuilder,
TableInitializer,
},
structures::{Map, TypedIndex},
types::{
ElementType, FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit,
ImportedGlobalIndex, Initializer, MemoryDescriptor, MemoryIndex, SigIndex, TableDescriptor,
TableIndex, Type, Value,
},
units::Pages,
};
use hashbrown::HashMap;
use std::fmt::Debug;
use wasmparser::{
BinaryReaderError, ExternalKind, FuncType, ImportSectionEntryType, Operator, Type as WpType,
WasmDecoder,
};
#[derive(Debug)]
pub enum LoadError {
Parse(BinaryReaderError),
Codegen(String),
}
impl From<LoadError> for CompileError {
fn from(other: LoadError) -> CompileError {
CompileError::InternalError {
msg: format!("{:?}", other),
}
}
}
impl From<BinaryReaderError> for LoadError {
fn from(other: BinaryReaderError) -> LoadError {
LoadError::Parse(other)
}
}
pub fn read_module<
MCG: ModuleCodeGenerator<FCG, RM, E>,
FCG: FunctionCodeGenerator<E>,
RM: RunnableModule,
E: Debug,
>(
wasm: &[u8],
backend: Backend,
mcg: &mut MCG,
middlewares: &mut MiddlewareChain,
compiler_config: &CompilerConfig,
) -> Result<ModuleInfo, LoadError> {
let mut info = ModuleInfo {
memories: Map::new(),
globals: Map::new(),
tables: Map::new(),
imported_functions: Map::new(),
imported_memories: Map::new(),
imported_tables: Map::new(),
imported_globals: Map::new(),
exports: Default::default(),
data_initializers: Vec::new(),
elem_initializers: Vec::new(),
start_func: None,
func_assoc: Map::new(),
signatures: Map::new(),
backend: backend,
namespace_table: StringTable::new(),
name_table: StringTable::new(),
em_symbol_map: compiler_config.symbol_map.clone(),
custom_sections: HashMap::new(),
};
let mut parser = wasmparser::ValidatingParser::new(
wasm,
Some(wasmparser::ValidatingParserConfig {
operator_config: wasmparser::OperatorValidatorConfig {
enable_threads: false,
enable_reference_types: false,
enable_simd: false,
enable_bulk_memory: false,
},
mutable_global_imports: false,
}),
);
let mut namespace_builder = Some(StringTableBuilder::new());
let mut name_builder = Some(StringTableBuilder::new());
let mut func_count: usize = ::std::usize::MAX;
loop {
use wasmparser::ParserState;
let state = parser.read();
match *state {
ParserState::EndWasm => break Ok(info),
ParserState::Error(err) => Err(LoadError::Parse(err))?,
ParserState::TypeSectionEntry(ref ty) => {
info.signatures.push(func_type_to_func_sig(ty)?);
}
ParserState::ImportSectionEntry { module, field, ty } => {
let namespace_index = namespace_builder.as_mut().unwrap().register(module);
let name_index = name_builder.as_mut().unwrap().register(field);
let import_name = ImportName {
namespace_index,
name_index,
};
match ty {
ImportSectionEntryType::Function(sigindex) => {
let sigindex = SigIndex::new(sigindex as usize);
info.imported_functions.push(import_name);
info.func_assoc.push(sigindex);
mcg.feed_import_function()
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
}
ImportSectionEntryType::Table(table_ty) => {
assert_eq!(table_ty.element_type, WpType::AnyFunc);
let table_desc = TableDescriptor {
element: ElementType::Anyfunc,
minimum: table_ty.limits.initial,
maximum: table_ty.limits.maximum,
};
info.imported_tables.push((import_name, table_desc));
}
ImportSectionEntryType::Memory(memory_ty) => {
let mem_desc = MemoryDescriptor {
minimum: Pages(memory_ty.limits.initial),
maximum: memory_ty.limits.maximum.map(|max| Pages(max)),
shared: memory_ty.shared,
};
info.imported_memories.push((import_name, mem_desc));
}
ImportSectionEntryType::Global(global_ty) => {
let global_desc = GlobalDescriptor {
mutable: global_ty.mutable,
ty: wp_type_to_type(global_ty.content_type)?,
};
info.imported_globals.push((import_name, global_desc));
}
}
}
ParserState::FunctionSectionEntry(sigindex) => {
let sigindex = SigIndex::new(sigindex as usize);
info.func_assoc.push(sigindex);
}
ParserState::TableSectionEntry(table_ty) => {
let table_desc = TableDescriptor {
element: ElementType::Anyfunc,
minimum: table_ty.limits.initial,
maximum: table_ty.limits.maximum,
};
info.tables.push(table_desc);
}
ParserState::MemorySectionEntry(memory_ty) => {
let mem_desc = MemoryDescriptor {
minimum: Pages(memory_ty.limits.initial),
maximum: memory_ty.limits.maximum.map(|max| Pages(max)),
shared: memory_ty.shared,
};
info.memories.push(mem_desc);
}
ParserState::ExportSectionEntry { field, kind, index } => {
let export_index = match kind {
ExternalKind::Function => ExportIndex::Func(FuncIndex::new(index as usize)),
ExternalKind::Table => ExportIndex::Table(TableIndex::new(index as usize)),
ExternalKind::Memory => ExportIndex::Memory(MemoryIndex::new(index as usize)),
ExternalKind::Global => ExportIndex::Global(GlobalIndex::new(index as usize)),
};
info.exports.insert(field.to_string(), export_index);
}
ParserState::StartSectionEntry(start_index) => {
info.start_func = Some(FuncIndex::new(start_index as usize));
}
ParserState::BeginFunctionBody { .. } => {
let id = func_count.wrapping_add(1);
func_count = id;
if func_count == 0 {
info.namespace_table = namespace_builder.take().unwrap().finish();
info.name_table = name_builder.take().unwrap().finish();
mcg.feed_signatures(info.signatures.clone())
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
mcg.feed_function_signatures(info.func_assoc.clone())
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
mcg.check_precondition(&info)
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
}
let fcg = mcg
.next_function()
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
middlewares
.run(
Some(fcg),
Event::Internal(InternalEvent::FunctionBegin(id as u32)),
&info,
)
.map_err(|x| LoadError::Codegen(x))?;
let sig = info
.signatures
.get(
*info
.func_assoc
.get(FuncIndex::new(id as usize + info.imported_functions.len()))
.unwrap(),
)
.unwrap();
for ret in sig.returns() {
fcg.feed_return(type_to_wp_type(*ret))
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
}
for param in sig.params() {
fcg.feed_param(type_to_wp_type(*param))
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
}
let mut body_begun = false;
loop {
let state = parser.read();
match *state {
ParserState::Error(err) => return Err(LoadError::Parse(err)),
ParserState::FunctionBodyLocals { ref locals } => {
for &(count, ty) in locals.iter() {
fcg.feed_local(ty, count as usize)
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
}
}
ParserState::CodeOperator(ref op) => {
if !body_begun {
body_begun = true;
fcg.begin_body(&info)
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
}
middlewares
.run(Some(fcg), Event::Wasm(op), &info)
.map_err(|x| LoadError::Codegen(x))?;
}
ParserState::EndFunctionBody => break,
_ => unreachable!(),
}
}
middlewares
.run(
Some(fcg),
Event::Internal(InternalEvent::FunctionEnd),
&info,
)
.map_err(|x| LoadError::Codegen(x))?;
fcg.finalize()
.map_err(|x| LoadError::Codegen(format!("{:?}", x)))?;
}
ParserState::BeginActiveElementSectionEntry(table_index) => {
let table_index = TableIndex::new(table_index as usize);
let mut elements: Option<Vec<FuncIndex>> = None;
let mut base: Option<Initializer> = None;
loop {
let state = parser.read();
match *state {
ParserState::Error(err) => return Err(LoadError::Parse(err)),
ParserState::InitExpressionOperator(ref op) => {
base = Some(eval_init_expr(op)?)
}
ParserState::ElementSectionEntryBody(ref _elements) => {
elements = Some(
_elements
.iter()
.cloned()
.map(|index| FuncIndex::new(index as usize))
.collect(),
);
}
ParserState::BeginInitExpressionBody
| ParserState::EndInitExpressionBody => {}
ParserState::EndElementSectionEntry => break,
_ => unreachable!(),
}
}
let table_init = TableInitializer {
table_index,
base: base.unwrap(),
elements: elements.unwrap(),
};
info.elem_initializers.push(table_init);
}
ParserState::BeginActiveDataSectionEntry(memory_index) => {
let memory_index = MemoryIndex::new(memory_index as usize);
let mut base: Option<Initializer> = None;
let mut data: Vec<u8> = vec![];
loop {
let state = parser.read();
match *state {
ParserState::Error(err) => return Err(LoadError::Parse(err)),
ParserState::InitExpressionOperator(ref op) => {
base = Some(eval_init_expr(op)?)
}
ParserState::DataSectionEntryBodyChunk(chunk) => {
data.extend_from_slice(chunk);
}
ParserState::BeginInitExpressionBody
| ParserState::EndInitExpressionBody => {}
ParserState::BeginDataSectionEntryBody(_)
| ParserState::EndDataSectionEntryBody => {}
ParserState::EndDataSectionEntry => break,
_ => unreachable!(),
}
}
let data_init = DataInitializer {
memory_index,
base: base.unwrap(),
data,
};
info.data_initializers.push(data_init);
}
ParserState::BeginGlobalSectionEntry(ty) => {
let init = loop {
let state = parser.read();
match *state {
ParserState::Error(err) => return Err(LoadError::Parse(err)),
ParserState::InitExpressionOperator(ref op) => {
break eval_init_expr(op)?;
}
ParserState::BeginInitExpressionBody => {}
_ => unreachable!(),
}
};
let desc = GlobalDescriptor {
mutable: ty.mutable,
ty: wp_type_to_type(ty.content_type)?,
};
let global_init = GlobalInit { desc, init };
info.globals.push(global_init);
}
_ => {}
}
}
}
pub fn wp_type_to_type(ty: WpType) -> Result<Type, BinaryReaderError> {
Ok(match ty {
WpType::I32 => Type::I32,
WpType::I64 => Type::I64,
WpType::F32 => Type::F32,
WpType::F64 => Type::F64,
WpType::V128 => {
return Err(BinaryReaderError {
message: "the wasmer llvm backend does not yet support the simd extension",
offset: -1isize as usize,
});
}
_ => panic!("broken invariant, invalid type"),
})
}
pub fn type_to_wp_type(ty: Type) -> WpType {
match ty {
Type::I32 => WpType::I32,
Type::I64 => WpType::I64,
Type::F32 => WpType::F32,
Type::F64 => WpType::F64,
}
}
fn func_type_to_func_sig(func_ty: &FuncType) -> Result<FuncSig, BinaryReaderError> {
assert_eq!(func_ty.form, WpType::Func);
Ok(FuncSig::new(
func_ty
.params
.iter()
.cloned()
.map(wp_type_to_type)
.collect::<Result<Vec<_>, _>>()?,
func_ty
.returns
.iter()
.cloned()
.map(wp_type_to_type)
.collect::<Result<Vec<_>, _>>()?,
))
}
fn eval_init_expr(op: &Operator) -> Result<Initializer, BinaryReaderError> {
Ok(match *op {
Operator::GetGlobal { global_index } => {
Initializer::GetGlobal(ImportedGlobalIndex::new(global_index as usize))
}
Operator::I32Const { value } => Initializer::Const(Value::I32(value)),
Operator::I64Const { value } => Initializer::Const(Value::I64(value)),
Operator::F32Const { value } => {
Initializer::Const(Value::F32(f32::from_bits(value.bits())))
}
Operator::F64Const { value } => {
Initializer::Const(Value::F64(f64::from_bits(value.bits())))
}
_ => {
return Err(BinaryReaderError {
message: "init expr evaluation failed: unsupported opcode",
offset: -1isize as usize,
});
}
})
}

View File

@ -33,7 +33,13 @@ impl Memory {
let protect = protection.to_protect_const();
let ptr = unsafe { VirtualAlloc(ptr::null_mut(), size, MEM_RESERVE, protect) };
let flags = if protection == Protect::None {
MEM_RESERVE
} else {
MEM_RESERVE | MEM_COMMIT
};
let ptr = unsafe { VirtualAlloc(ptr::null_mut(), size, flags, protect) };
if ptr.is_null() {
Err("unable to allocate memory".to_string())
@ -229,3 +235,25 @@ fn round_up_to_page_size(size: usize, page_size: usize) -> usize {
fn round_down_to_page_size(size: usize, page_size: usize) -> usize {
size & !(page_size - 1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clone() {
// these should work
let _ = Memory::with_size_protect(200_000, Protect::Read)
.unwrap()
.clone();
let _ = Memory::with_size_protect(200_000, Protect::ReadWrite)
.unwrap()
.clone();
let _ = Memory::with_size_protect(200_000, Protect::ReadExec)
.unwrap()
.clone();
// this would cause segmentation fault as uncommited memory with no access
//let _ = Memory::with_size_protect(200_000, Protect::None).unwrap().clone();
}
}

View File

@ -1,29 +1,111 @@
use crate::{
backend::UserTrapper,
error::RuntimeError,
export::{Context, Export, FuncPointer},
import::IsExport,
types::{FuncSig, Type, WasmExternType},
vm::Ctx,
types::{FuncSig, NativeWasmType, Type, WasmExternType},
vm::{self, Ctx},
};
use std::{
any::Any,
convert::Infallible,
ffi::c_void,
fmt,
marker::PhantomData,
mem, panic,
ptr::{self, NonNull},
sync::Arc,
};
use std::{any::Any, cell::UnsafeCell, marker::PhantomData, mem, panic, ptr, sync::Arc};
thread_local! {
pub static EARLY_TRAPPER: UnsafeCell<Option<Box<dyn UserTrapper>>> = UnsafeCell::new(None);
#[repr(C)]
pub enum WasmTrapInfo {
Unreachable = 0,
IncorrectCallIndirectSignature = 1,
MemoryOutOfBounds = 2,
CallIndirectOOB = 3,
IllegalArithmetic = 4,
Unknown,
}
pub trait Safeness {}
pub struct Safe;
pub struct Unsafe;
impl Safeness for Safe {}
impl Safeness for Unsafe {}
impl fmt::Display for WasmTrapInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
WasmTrapInfo::Unreachable => "unreachable",
WasmTrapInfo::IncorrectCallIndirectSignature => {
"incorrect `call_indirect` signature"
}
WasmTrapInfo::MemoryOutOfBounds => "memory out-of-bounds access",
WasmTrapInfo::CallIndirectOOB => "`call_indirect` out-of-bounds",
WasmTrapInfo::IllegalArithmetic => "illegal arithmetic operation",
WasmTrapInfo::Unknown => "unknown",
}
)
}
}
/// This is just an empty trait to constrict that types that
/// can be put into the third/fourth (depending if you include lifetimes)
/// of the `Func` struct.
pub trait Kind {}
pub type Trampoline = unsafe extern "C" fn(*mut Ctx, NonNull<vm::Func>, *const u64, *mut u64);
pub type Invoke = unsafe extern "C" fn(
Trampoline,
*mut Ctx,
NonNull<vm::Func>,
*const u64,
*mut u64,
*mut WasmTrapInfo,
*mut Option<Box<dyn Any>>,
Option<NonNull<c_void>>,
) -> bool;
/// TODO(lachlan): Naming TBD.
/// This contains the trampoline and invoke functions for a specific signature,
/// as well as the environment that the invoke function may or may not require.
#[derive(Copy, Clone)]
pub struct Wasm {
pub(crate) trampoline: Trampoline,
pub(crate) invoke: Invoke,
pub(crate) invoke_env: Option<NonNull<c_void>>,
}
impl Wasm {
pub unsafe fn from_raw_parts(
trampoline: Trampoline,
invoke: Invoke,
invoke_env: Option<NonNull<c_void>>,
) -> Self {
Self {
trampoline,
invoke,
invoke_env,
}
}
}
/// This type, as part of the `Func` type signature, represents a function that is created
/// by the host.
pub struct Host(());
impl Kind for Wasm {}
impl Kind for Host {}
pub trait WasmTypeList {
type CStruct;
type RetArray: AsMut<[u64]>;
fn from_ret_array(array: Self::RetArray) -> Self;
fn empty_ret_array() -> Self::RetArray;
fn from_c_struct(c_struct: Self::CStruct) -> Self;
fn into_c_struct(self) -> Self::CStruct;
fn types() -> &'static [Type];
unsafe fn call<Rets>(self, f: *const (), ctx: *mut Ctx) -> Rets
unsafe fn call<Rets>(
self,
f: NonNull<vm::Func>,
wasm: Wasm,
ctx: *mut Ctx,
) -> Result<Rets, RuntimeError>
where
Rets: WasmTypeList;
}
@ -33,21 +115,23 @@ where
Args: WasmTypeList,
Rets: WasmTypeList,
{
fn to_raw(&self) -> *const ();
fn to_raw(&self) -> NonNull<vm::Func>;
}
pub trait TrapEarly<Rets>
where
Rets: WasmTypeList,
{
fn report(self) -> Result<Rets, Box<dyn Any>>;
type Error: 'static;
fn report(self) -> Result<Rets, Self::Error>;
}
impl<Rets> TrapEarly<Rets> for Rets
where
Rets: WasmTypeList,
{
fn report(self) -> Result<Rets, Box<dyn Any>> {
type Error = Infallible;
fn report(self) -> Result<Rets, Infallible> {
Ok(self)
}
}
@ -55,10 +139,11 @@ where
impl<Rets, E> TrapEarly<Rets> for Result<Rets, E>
where
Rets: WasmTypeList,
E: Any,
E: 'static,
{
fn report(self) -> Result<Rets, Box<dyn Any>> {
self.map_err(|err| Box::new(err) as Box<dyn Any>)
type Error = E;
fn report(self) -> Result<Rets, E> {
self
}
}
@ -71,19 +156,25 @@ where
// Func::new(f)
// }
pub struct Func<'a, Args = (), Rets = (), Safety: Safeness = Safe> {
f: *const (),
pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> {
inner: Inner,
f: NonNull<vm::Func>,
ctx: *mut Ctx,
_phantom: PhantomData<(&'a (), Safety, Args, Rets)>,
_phantom: PhantomData<(&'a (), Args, Rets)>,
}
impl<'a, Args, Rets> Func<'a, Args, Rets, Safe>
impl<'a, Args, Rets> Func<'a, Args, Rets, Wasm>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
pub(crate) unsafe fn new_from_ptr(f: *const (), ctx: *mut Ctx) -> Func<'a, Args, Rets, Safe> {
pub(crate) unsafe fn from_raw_parts(
inner: Wasm,
f: NonNull<vm::Func>,
ctx: *mut Ctx,
) -> Func<'a, Args, Rets, Wasm> {
Func {
inner,
f,
ctx,
_phantom: PhantomData,
@ -91,16 +182,17 @@ where
}
}
impl<'a, Args, Rets> Func<'a, Args, Rets, Unsafe>
impl<'a, Args, Rets> Func<'a, Args, Rets, Host>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
pub fn new<F>(f: F) -> Func<'a, Args, Rets, Unsafe>
pub fn new<F>(f: F) -> Func<'a, Args, Rets, Host>
where
F: ExternalFunction<Args, Rets>,
{
Func {
inner: Host(()),
f: f.to_raw(),
ctx: ptr::null_mut(),
_phantom: PhantomData,
@ -108,11 +200,11 @@ where
}
}
impl<'a, Args, Rets, Safety> Func<'a, Args, Rets, Safety>
impl<'a, Args, Rets, Inner> Func<'a, Args, Rets, Inner>
where
Args: WasmTypeList,
Rets: WasmTypeList,
Safety: Safeness,
Inner: Kind,
{
pub fn params(&self) -> &'static [Type] {
Args::types()
@ -122,110 +214,205 @@ where
}
}
impl WasmTypeList for Infallible {
type CStruct = Infallible;
type RetArray = [u64; 0];
fn from_ret_array(_: Self::RetArray) -> Self {
unreachable!()
}
fn empty_ret_array() -> Self::RetArray {
unreachable!()
}
fn from_c_struct(_: Self::CStruct) -> Self {
unreachable!()
}
fn into_c_struct(self) -> Self::CStruct {
unreachable!()
}
fn types() -> &'static [Type] {
&[]
}
#[allow(non_snake_case)]
unsafe fn call<Rets: WasmTypeList>(
self,
_: NonNull<vm::Func>,
_: Wasm,
_: *mut Ctx,
) -> Result<Rets, RuntimeError> {
unreachable!()
}
}
impl<A: WasmExternType> WasmTypeList for (A,) {
type CStruct = S1<A>;
type RetArray = [u64; 1];
fn from_ret_array(array: Self::RetArray) -> Self {
(WasmExternType::from_native(NativeWasmType::from_binary(
array[0],
)),)
}
fn empty_ret_array() -> Self::RetArray {
[0u64]
}
fn from_c_struct(c_struct: Self::CStruct) -> Self {
let S1(a) = c_struct;
(a,)
(WasmExternType::from_native(a),)
}
fn into_c_struct(self) -> Self::CStruct {
#[allow(unused_parens, non_snake_case)]
let (a,) = self;
S1(a)
S1(WasmExternType::to_native(a))
}
fn types() -> &'static [Type] {
&[A::TYPE]
&[A::Native::TYPE]
}
#[allow(non_snake_case)]
unsafe fn call<Rets: WasmTypeList>(self, f: *const (), ctx: *mut Ctx) -> Rets {
let f: extern "C" fn(*mut Ctx, A) -> Rets = mem::transmute(f);
unsafe fn call<Rets: WasmTypeList>(
self,
f: NonNull<vm::Func>,
wasm: Wasm,
ctx: *mut Ctx,
) -> Result<Rets, RuntimeError> {
let (a,) = self;
f(ctx, a)
let args = [a.to_native().to_binary()];
let mut rets = Rets::empty_ret_array();
let mut trap = WasmTrapInfo::Unknown;
let mut user_error = None;
if (wasm.invoke)(
wasm.trampoline,
ctx,
f,
args.as_ptr(),
rets.as_mut().as_mut_ptr(),
&mut trap,
&mut user_error,
wasm.invoke_env,
) {
Ok(Rets::from_ret_array(rets))
} else {
if let Some(data) = user_error {
Err(RuntimeError::Error { data })
} else {
Err(RuntimeError::Trap {
msg: trap.to_string().into(),
})
}
}
}
}
impl<'a, A: WasmExternType, Rets> Func<'a, (A,), Rets, Safe>
impl<'a, A: WasmExternType, Rets> Func<'a, (A,), Rets, Wasm>
where
Rets: WasmTypeList,
{
pub fn call(&self, a: A) -> Result<Rets, RuntimeError> {
Ok(unsafe { <A as WasmTypeList>::call(a, self.f, self.ctx) })
unsafe { <A as WasmTypeList>::call(a, self.f, self.inner, self.ctx) }
}
}
macro_rules! impl_traits {
( [$repr:ident] $struct_name:ident, $( $x:ident ),* ) => {
#[repr($repr)]
pub struct $struct_name <$( $x ),*> ( $( $x ),* );
pub struct $struct_name <$( $x: WasmExternType ),*> ( $( <$x as WasmExternType>::Native ),* );
impl< $( $x: WasmExternType, )* > WasmTypeList for ( $( $x ),* ) {
type CStruct = $struct_name<$( $x ),*>;
type RetArray = [u64; count_idents!( $( $x ),* )];
fn from_ret_array(array: Self::RetArray) -> Self {
#[allow(non_snake_case)]
let [ $( $x ),* ] = array;
( $( WasmExternType::from_native(NativeWasmType::from_binary($x)) ),* )
}
fn empty_ret_array() -> Self::RetArray {
[0; count_idents!( $( $x ),* )]
}
fn from_c_struct(c_struct: Self::CStruct) -> Self {
#[allow(non_snake_case)]
let $struct_name ( $( $x ),* ) = c_struct;
( $( $x ),* )
( $( WasmExternType::from_native($x) ),* )
}
fn into_c_struct(self) -> Self::CStruct {
#[allow(unused_parens, non_snake_case)]
let ( $( $x ),* ) = self;
$struct_name ( $( $x ),* )
$struct_name ( $( WasmExternType::to_native($x) ),* )
}
fn types() -> &'static [Type] {
&[$( $x::TYPE, )*]
&[$( $x::Native::TYPE, )*]
}
#[allow(non_snake_case)]
unsafe fn call<Rets: WasmTypeList>(self, f: *const (), ctx: *mut Ctx) -> Rets {
let f: extern fn(*mut Ctx $( ,$x )*) -> Rets::CStruct = mem::transmute(f);
unsafe fn call<Rets: WasmTypeList>(self, f: NonNull<vm::Func>, wasm: Wasm, ctx: *mut Ctx) -> Result<Rets, RuntimeError> {
#[allow(unused_parens)]
let ( $( $x ),* ) = self;
let c_struct = f(ctx $( ,$x )*);
Rets::from_c_struct(c_struct)
let args = [ $( $x.to_native().to_binary()),* ];
let mut rets = Rets::empty_ret_array();
let mut trap = WasmTrapInfo::Unknown;
let mut user_error = None;
if (wasm.invoke)(wasm.trampoline, ctx, f, args.as_ptr(), rets.as_mut().as_mut_ptr(), &mut trap, &mut user_error, wasm.invoke_env) {
Ok(Rets::from_ret_array(rets))
} else {
if let Some(data) = user_error {
Err(RuntimeError::Error { data })
} else {
Err(RuntimeError::Trap { msg: trap.to_string().into() })
}
}
}
}
impl< $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( &mut Ctx $( ,$x )* ) -> Trap> ExternalFunction<($( $x ),*), Rets> for FN {
#[allow(non_snake_case)]
fn to_raw(&self) -> *const () {
fn to_raw(&self) -> NonNull<vm::Func> {
assert_eq!(mem::size_of::<Self>(), 0, "you cannot use a closure that captures state for `Func`.");
extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( &mut Ctx $( ,$x )* ) -> Trap>( ctx: &mut Ctx $( ,$x: $x )* ) -> Rets::CStruct {
/// This is required for the llvm backend to be able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( &mut Ctx $( ,$x )* ) -> Trap>( ctx: &mut Ctx $( ,$x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct {
let f: FN = unsafe { mem::transmute_copy(&()) };
let err = match panic::catch_unwind(panic::AssertUnwindSafe(|| {
f( ctx $( ,$x )* ).report()
f( ctx $( ,WasmExternType::from_native($x) )* ).report()
})) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => err,
Ok(Err(err)) => {
let b: Box<_> = err.into();
b as Box<dyn Any>
},
Err(err) => err,
};
unsafe {
if let Some(early_trapper) = &*EARLY_TRAPPER.with(|ucell| ucell.get()) {
early_trapper.do_early_trap(err)
} else {
eprintln!("panic handling not setup");
std::process::exit(1)
}
(&*ctx.module).runnable_module.do_early_trap(err)
}
}
wrap::<$( $x, )* Rets, Trap, Self> as *const ()
NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap()
}
}
impl<'a, $( $x: WasmExternType, )* Rets> Func<'a, ( $( $x ),* ), Rets, Safe>
impl<'a, $( $x: WasmExternType, )* Rets> Func<'a, ( $( $x ),* ), Rets, Wasm>
where
Rets: WasmTypeList,
{
#[allow(non_snake_case)]
pub fn call(&self, $( $x: $x, )* ) -> Result<Rets, RuntimeError> {
#[allow(unused_parens)]
Ok(unsafe { <( $( $x ),* ) as WasmTypeList>::call(( $($x),* ), self.f, self.ctx) })
unsafe { <( $( $x ),* ) as WasmTypeList>::call(( $($x),* ), self.f, self.inner, self.ctx) }
}
}
};
}
macro_rules! count_idents {
( $($idents:ident),* ) => {{
#[allow(dead_code, non_camel_case_types)]
enum Idents { $($idents,)* __CountIdentsLast }
const COUNT: usize = Idents::__CountIdentsLast as usize;
COUNT
}};
}
impl_traits!([C] S0,);
impl_traits!([transparent] S1, A);
impl_traits!([C] S2, A, B);
@ -240,14 +427,14 @@ impl_traits!([C] S10, A, B, C, D, E, F, G, H, I, J);
impl_traits!([C] S11, A, B, C, D, E, F, G, H, I, J, K);
impl_traits!([C] S12, A, B, C, D, E, F, G, H, I, J, K, L);
impl<'a, Args, Rets, Safety> IsExport for Func<'a, Args, Rets, Safety>
impl<'a, Args, Rets, Inner> IsExport for Func<'a, Args, Rets, Inner>
where
Args: WasmTypeList,
Rets: WasmTypeList,
Safety: Safeness,
Inner: Kind,
{
fn to_export(&self) -> Export {
let func = unsafe { FuncPointer::new(self.f as _) };
let func = unsafe { FuncPointer::new(self.f.as_ptr()) };
let ctx = Context::Internal;
let signature = Arc::new(FuncSig::new(Args::types(), Rets::types()));

View File

@ -1,5 +1,5 @@
use crate::{memory::MemoryType, module::ModuleInfo, structures::TypedIndex, units::Pages};
use std::{borrow::Cow, mem};
use std::borrow::Cow;
/// Represents a WebAssembly type.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -71,29 +71,150 @@ impl From<f64> for Value {
}
}
pub unsafe trait WasmExternType: Copy + Clone
pub unsafe trait NativeWasmType: Copy + Into<Value>
where
Self: Sized,
{
const TYPE: Type;
fn from_binary(bits: u64) -> Self;
fn to_binary(self) -> u64;
}
unsafe impl NativeWasmType for i32 {
const TYPE: Type = Type::I32;
fn from_binary(bits: u64) -> Self {
bits as _
}
fn to_binary(self) -> u64 {
self as _
}
}
unsafe impl NativeWasmType for i64 {
const TYPE: Type = Type::I64;
fn from_binary(bits: u64) -> Self {
bits as _
}
fn to_binary(self) -> u64 {
self as _
}
}
unsafe impl NativeWasmType for f32 {
const TYPE: Type = Type::F32;
fn from_binary(bits: u64) -> Self {
f32::from_bits(bits as u32)
}
fn to_binary(self) -> u64 {
self.to_bits() as _
}
}
unsafe impl NativeWasmType for f64 {
const TYPE: Type = Type::F64;
fn from_binary(bits: u64) -> Self {
f64::from_bits(bits)
}
fn to_binary(self) -> u64 {
self.to_bits()
}
}
pub unsafe trait WasmExternType: Copy
where
Self: Sized,
{
type Native: NativeWasmType;
fn from_native(native: Self::Native) -> Self;
fn to_native(self) -> Self::Native;
}
unsafe impl WasmExternType for i8 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for u8 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for i16 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for u16 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for i32 {
const TYPE: Type = Type::I32;
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native
}
fn to_native(self) -> Self::Native {
self
}
}
unsafe impl WasmExternType for u32 {
const TYPE: Type = Type::I32;
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for i64 {
const TYPE: Type = Type::I64;
type Native = i64;
fn from_native(native: Self::Native) -> Self {
native
}
fn to_native(self) -> Self::Native {
self
}
}
unsafe impl WasmExternType for u64 {
const TYPE: Type = Type::I64;
type Native = i64;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for f32 {
const TYPE: Type = Type::F32;
type Native = f32;
fn from_native(native: Self::Native) -> Self {
native
}
fn to_native(self) -> Self::Native {
self
}
}
unsafe impl WasmExternType for f64 {
const TYPE: Type = Type::F64;
type Native = f64;
fn from_native(native: Self::Native) -> Self {
native
}
fn to_native(self) -> Self::Native {
self
}
}
// pub trait IntegerAtomic
@ -113,34 +234,15 @@ unsafe impl WasmExternType for f64 {
// fn swap(&self, other: Self::Primitive) -> Self::Primitive;
// }
pub enum ValueError {
BufferTooSmall,
}
pub trait ValueType: Copy
pub unsafe trait ValueType: Copy
where
Self: Sized,
{
fn into_le(self, buffer: &mut [u8]);
fn from_le(buffer: &[u8]) -> Result<Self, ValueError>;
}
macro_rules! convert_value_impl {
($t:ty) => {
impl ValueType for $t {
fn into_le(self, buffer: &mut [u8]) {
buffer[..mem::size_of::<Self>()].copy_from_slice(&self.to_le_bytes());
}
fn from_le(buffer: &[u8]) -> Result<Self, ValueError> {
if buffer.len() >= mem::size_of::<Self>() {
let mut array = [0u8; mem::size_of::<Self>()];
array.copy_from_slice(&buffer[..mem::size_of::<Self>()]);
Ok(Self::from_le_bytes(array))
} else {
Err(ValueError::BufferTooSmall)
}
}
}
unsafe impl ValueType for $t {}
};
( $($t:ty),* ) => {
$(
@ -149,25 +251,7 @@ macro_rules! convert_value_impl {
};
}
convert_value_impl!(u8, i8, u16, i16, u32, i32, u64, i64);
impl ValueType for f32 {
fn into_le(self, buffer: &mut [u8]) {
self.to_bits().into_le(buffer);
}
fn from_le(buffer: &[u8]) -> Result<Self, ValueError> {
Ok(f32::from_bits(<u32 as ValueType>::from_le(buffer)?))
}
}
impl ValueType for f64 {
fn into_le(self, buffer: &mut [u8]) {
self.to_bits().into_le(buffer);
}
fn from_le(buffer: &[u8]) -> Result<Self, ValueError> {
Ok(f64::from_bits(<u64 as ValueType>::from_le(buffer)?))
}
}
convert_value_impl!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64);
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementType {
@ -207,6 +291,7 @@ pub enum Initializer {
GetGlobal(ImportedGlobalIndex),
}
/// Describes the mutability and type of a Global
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlobalDescriptor {
pub mutable: bool,
@ -316,7 +401,7 @@ pub trait LocalImport {
macro_rules! define_map_index {
($ty:ident) => {
#[derive(Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $ty (u32);
impl TypedIndex for $ty {
#[doc(hidden)]
@ -431,3 +516,58 @@ where
}
}
}
#[cfg(test)]
mod tests {
use crate::types::NativeWasmType;
use crate::types::WasmExternType;
#[test]
fn test_native_types_round_trip() {
assert_eq!(
42i32,
i32::from_native(i32::from_binary((42i32).to_native().to_binary()))
);
assert_eq!(
-42i32,
i32::from_native(i32::from_binary((-42i32).to_native().to_binary()))
);
use std::i64;
let xi64 = i64::MAX;
assert_eq!(
xi64,
i64::from_native(i64::from_binary((xi64).to_native().to_binary()))
);
let yi64 = i64::MIN;
assert_eq!(
yi64,
i64::from_native(i64::from_binary((yi64).to_native().to_binary()))
);
assert_eq!(
16.5f32,
f32::from_native(f32::from_binary((16.5f32).to_native().to_binary()))
);
assert_eq!(
-16.5f32,
f32::from_native(f32::from_binary((-16.5f32).to_native().to_binary()))
);
use std::f64;
let xf64: f64 = f64::MAX;
assert_eq!(
xf64,
f64::from_native(f64::from_binary((xf64).to_native().to_binary()))
);
let yf64: f64 = f64::MIN;
assert_eq!(
yf64,
f64::from_native(f64::from_binary((yf64).to_native().to_binary()))
);
}
}

View File

@ -4,8 +4,10 @@ use std::{
ops::{Add, Sub},
};
const WASM_PAGE_SIZE: usize = 65_536;
const WASM_MAX_PAGES: usize = 65_536;
pub const WASM_PAGE_SIZE: usize = 65_536;
pub const WASM_MAX_PAGES: usize = 65_536;
// From emscripten resize_heap implementation
pub const WASM_MIN_PAGES: usize = 256;
/// Units of WebAssembly pages (as specified to be 65,536 bytes).
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]

View File

@ -7,6 +7,8 @@ use crate::{
};
use std::{ffi::c_void, mem, ptr};
use hashbrown::HashMap;
/// The context of the currently running WebAssembly instance.
///
/// This is implicitly passed to every webassembly function.
@ -22,32 +24,8 @@ use std::{ffi::c_void, mem, ptr};
#[derive(Debug)]
#[repr(C)]
pub struct Ctx {
/// A pointer to an array of locally-defined memories, indexed by `MemoryIndex`.
pub(crate) memories: *mut *mut LocalMemory,
/// A pointer to an array of locally-defined tables, indexed by `TableIndex`.
pub(crate) tables: *mut *mut LocalTable,
/// A pointer to an array of locally-defined globals, indexed by `GlobalIndex`.
pub(crate) globals: *mut *mut LocalGlobal,
/// A pointer to an array of imported memories, indexed by `MemoryIndex,
pub(crate) imported_memories: *mut *mut LocalMemory,
/// A pointer to an array of imported tables, indexed by `TableIndex`.
pub(crate) imported_tables: *mut *mut LocalTable,
/// A pointer to an array of imported globals, indexed by `GlobalIndex`.
pub(crate) imported_globals: *mut *mut LocalGlobal,
/// A pointer to an array of imported functions, indexed by `FuncIndex`.
pub(crate) imported_funcs: *mut ImportedFunc,
/// A pointer to an array of signature ids. Conceptually, this maps
/// from a static, module-local signature id to a runtime-global
/// signature id. This is used to allow call-indirect to other
/// modules safely.
pub(crate) dynamic_sigindices: *const SigId,
// `internal` must be the first field of `Ctx`.
pub(crate) internal: InternalCtx,
pub(crate) local_functions: *const *const Func,
@ -55,7 +33,7 @@ pub struct Ctx {
/// by the owning `Instance`.
local_backing: *mut LocalBacking,
import_backing: *mut ImportBacking,
module: *const ModuleInner,
pub module: *const ModuleInner,
//// This is intended to be user-supplied, per-instance
/// contextual data. There are currently some issue with it,
@ -70,7 +48,42 @@ pub struct Ctx {
/// If there's a function set in this field, it gets called
/// when the context is destructed, e.g. when an `Instance`
/// is dropped.
pub data_finalizer: Option<extern "C" fn(data: *mut c_void)>,
pub data_finalizer: Option<fn(data: *mut c_void)>,
}
/// The internal context of the currently running WebAssembly instance.
///
///
#[doc(hidden)]
#[derive(Debug)]
#[repr(C)]
pub struct InternalCtx {
/// A pointer to an array of locally-defined memories, indexed by `MemoryIndex`.
pub memories: *mut *mut LocalMemory,
/// A pointer to an array of locally-defined tables, indexed by `TableIndex`.
pub tables: *mut *mut LocalTable,
/// A pointer to an array of locally-defined globals, indexed by `GlobalIndex`.
pub globals: *mut *mut LocalGlobal,
/// A pointer to an array of imported memories, indexed by `MemoryIndex,
pub imported_memories: *mut *mut LocalMemory,
/// A pointer to an array of imported tables, indexed by `TableIndex`.
pub imported_tables: *mut *mut LocalTable,
/// A pointer to an array of imported globals, indexed by `GlobalIndex`.
pub imported_globals: *mut *mut LocalGlobal,
/// A pointer to an array of imported functions, indexed by `FuncIndex`.
pub imported_funcs: *mut ImportedFunc,
/// A pointer to an array of signature ids. Conceptually, this maps
/// from a static, module-local signature id to a runtime-global
/// signature id. This is used to allow call-indirect to other
/// modules safely.
pub dynamic_sigindices: *const SigId,
}
impl Ctx {
@ -81,16 +94,18 @@ impl Ctx {
module: &ModuleInner,
) -> Self {
Self {
memories: local_backing.vm_memories.as_mut_ptr(),
tables: local_backing.vm_tables.as_mut_ptr(),
globals: local_backing.vm_globals.as_mut_ptr(),
internal: InternalCtx {
memories: local_backing.vm_memories.as_mut_ptr(),
tables: local_backing.vm_tables.as_mut_ptr(),
globals: local_backing.vm_globals.as_mut_ptr(),
imported_memories: import_backing.vm_memories.as_mut_ptr(),
imported_tables: import_backing.vm_tables.as_mut_ptr(),
imported_globals: import_backing.vm_globals.as_mut_ptr(),
imported_funcs: import_backing.vm_functions.as_mut_ptr(),
imported_memories: import_backing.vm_memories.as_mut_ptr(),
imported_tables: import_backing.vm_tables.as_mut_ptr(),
imported_globals: import_backing.vm_globals.as_mut_ptr(),
imported_funcs: import_backing.vm_functions.as_mut_ptr(),
dynamic_sigindices: local_backing.dynamic_sigindices.as_ptr(),
dynamic_sigindices: local_backing.dynamic_sigindices.as_ptr(),
},
local_functions: local_backing.local_functions.as_ptr(),
local_backing,
@ -108,19 +123,21 @@ impl Ctx {
import_backing: &mut ImportBacking,
module: &ModuleInner,
data: *mut c_void,
data_finalizer: extern "C" fn(*mut c_void),
data_finalizer: fn(*mut c_void),
) -> Self {
Self {
memories: local_backing.vm_memories.as_mut_ptr(),
tables: local_backing.vm_tables.as_mut_ptr(),
globals: local_backing.vm_globals.as_mut_ptr(),
internal: InternalCtx {
memories: local_backing.vm_memories.as_mut_ptr(),
tables: local_backing.vm_tables.as_mut_ptr(),
globals: local_backing.vm_globals.as_mut_ptr(),
imported_memories: import_backing.vm_memories.as_mut_ptr(),
imported_tables: import_backing.vm_tables.as_mut_ptr(),
imported_globals: import_backing.vm_globals.as_mut_ptr(),
imported_funcs: import_backing.vm_functions.as_mut_ptr(),
imported_memories: import_backing.vm_memories.as_mut_ptr(),
imported_tables: import_backing.vm_tables.as_mut_ptr(),
imported_globals: import_backing.vm_globals.as_mut_ptr(),
imported_funcs: import_backing.vm_functions.as_mut_ptr(),
dynamic_sigindices: local_backing.dynamic_sigindices.as_ptr(),
dynamic_sigindices: local_backing.dynamic_sigindices.as_ptr(),
},
local_functions: local_backing.local_functions.as_ptr(),
local_backing,
@ -164,6 +181,11 @@ impl Ctx {
},
}
}
/// Gives access to the emscripten symbol map, used for debugging
pub unsafe fn borrow_symbol_map(&self) -> &Option<HashMap<u32, String>> {
&(*self.module).info.em_symbol_map
}
}
#[doc(hidden)]
@ -356,43 +378,45 @@ impl Anyfunc {
#[cfg(test)]
mod vm_offset_tests {
use super::{Anyfunc, Ctx, ImportedFunc, LocalGlobal, LocalMemory, LocalTable};
use super::{Anyfunc, Ctx, ImportedFunc, InternalCtx, LocalGlobal, LocalMemory, LocalTable};
#[test]
fn vmctx() {
assert_eq!(0usize, offset_of!(Ctx => internal).get_byte_offset(),);
assert_eq!(
Ctx::offset_memories() as usize,
offset_of!(Ctx => memories).get_byte_offset(),
offset_of!(InternalCtx => memories).get_byte_offset(),
);
assert_eq!(
Ctx::offset_tables() as usize,
offset_of!(Ctx => tables).get_byte_offset(),
offset_of!(InternalCtx => tables).get_byte_offset(),
);
assert_eq!(
Ctx::offset_globals() as usize,
offset_of!(Ctx => globals).get_byte_offset(),
offset_of!(InternalCtx => globals).get_byte_offset(),
);
assert_eq!(
Ctx::offset_imported_memories() as usize,
offset_of!(Ctx => imported_memories).get_byte_offset(),
offset_of!(InternalCtx => imported_memories).get_byte_offset(),
);
assert_eq!(
Ctx::offset_imported_tables() as usize,
offset_of!(Ctx => imported_tables).get_byte_offset(),
offset_of!(InternalCtx => imported_tables).get_byte_offset(),
);
assert_eq!(
Ctx::offset_imported_globals() as usize,
offset_of!(Ctx => imported_globals).get_byte_offset(),
offset_of!(InternalCtx => imported_globals).get_byte_offset(),
);
assert_eq!(
Ctx::offset_imported_funcs() as usize,
offset_of!(Ctx => imported_funcs).get_byte_offset(),
offset_of!(InternalCtx => imported_funcs).get_byte_offset(),
);
assert_eq!(
@ -480,7 +504,7 @@ mod vm_ctx_tests {
str: String,
}
extern "C" fn test_data_finalizer(data: *mut c_void) {
fn test_data_finalizer(data: *mut c_void) {
let test_data: &mut TestData = unsafe { &mut *(data as *mut TestData) };
assert_eq!(test_data.x, 10);
assert_eq!(test_data.y, true);
@ -543,52 +567,38 @@ mod vm_ctx_tests {
fn generate_module() -> ModuleInner {
use super::Func;
use crate::backend::{
sys::Memory, Backend, CacheGen, FuncResolver, ProtectedCaller, Token, UserTrapper,
};
use crate::cache::{Error as CacheError, WasmHash};
use crate::error::RuntimeResult;
use crate::types::{FuncIndex, LocalFuncIndex, Value};
use crate::backend::{sys::Memory, Backend, CacheGen, RunnableModule};
use crate::cache::Error as CacheError;
use crate::typed_func::Wasm;
use crate::types::{LocalFuncIndex, SigIndex};
use hashbrown::HashMap;
use std::any::Any;
use std::ptr::NonNull;
struct Placeholder;
impl FuncResolver for Placeholder {
fn get(
impl RunnableModule for Placeholder {
fn get_func(
&self,
_module: &ModuleInner,
_module: &ModuleInfo,
_local_func_index: LocalFuncIndex,
) -> Option<NonNull<Func>> {
None
}
}
impl ProtectedCaller for Placeholder {
fn call(
&self,
_module: &ModuleInner,
_func_index: FuncIndex,
_params: &[Value],
_import_backing: &ImportBacking,
_vmctx: *mut Ctx,
_: Token,
) -> RuntimeResult<Vec<Value>> {
Ok(vec![])
fn get_trampoline(&self, _module: &ModuleInfo, _sig_index: SigIndex) -> Option<Wasm> {
unimplemented!()
}
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
unsafe fn do_early_trap(&self, _: Box<dyn Any>) -> ! {
unimplemented!()
}
}
impl CacheGen for Placeholder {
fn generate_cache(
&self,
module: &ModuleInner,
) -> Result<(Box<ModuleInfo>, Box<[u8]>, Memory), CacheError> {
fn generate_cache(&self) -> Result<(Box<[u8]>, Memory), CacheError> {
unimplemented!()
}
}
ModuleInner {
func_resolver: Box::new(Placeholder),
protected_caller: Box::new(Placeholder),
runnable_module: Box::new(Placeholder),
cache_gen: Box::new(Placeholder),
info: ModuleInfo {
memories: Map::new(),
@ -614,6 +624,10 @@ mod vm_ctx_tests {
namespace_table: StringTable::new(),
name_table: StringTable::new(),
em_symbol_map: None,
custom_sections: HashMap::new(),
},
}
}

View File

@ -17,7 +17,7 @@ pub unsafe extern "C" fn local_static_memory_grow(
memory_index: LocalMemoryIndex,
delta: Pages,
) -> i32 {
let local_memory = *ctx.memories.add(memory_index.index());
let local_memory = *ctx.internal.memories.add(memory_index.index());
let memory = (*local_memory).memory as *mut StaticMemory;
match (*memory).grow(delta, &mut *local_memory) {
@ -30,7 +30,7 @@ pub unsafe extern "C" fn local_static_memory_size(
ctx: &vm::Ctx,
memory_index: LocalMemoryIndex,
) -> Pages {
let local_memory = *ctx.memories.add(memory_index.index());
let local_memory = *ctx.internal.memories.add(memory_index.index());
let memory = (*local_memory).memory as *mut StaticMemory;
(*memory).size()
@ -41,7 +41,7 @@ pub unsafe extern "C" fn local_dynamic_memory_grow(
memory_index: LocalMemoryIndex,
delta: Pages,
) -> i32 {
let local_memory = *ctx.memories.add(memory_index.index());
let local_memory = *ctx.internal.memories.add(memory_index.index());
let memory = (*local_memory).memory as *mut DynamicMemory;
match (*memory).grow(delta, &mut *local_memory) {
@ -54,7 +54,7 @@ pub unsafe extern "C" fn local_dynamic_memory_size(
ctx: &vm::Ctx,
memory_index: LocalMemoryIndex,
) -> Pages {
let local_memory = *ctx.memories.add(memory_index.index());
let local_memory = *ctx.internal.memories.add(memory_index.index());
let memory = (*local_memory).memory as *mut DynamicMemory;
(*memory).size()
@ -69,7 +69,10 @@ pub unsafe extern "C" fn imported_static_memory_grow(
import_memory_index: ImportedMemoryIndex,
delta: Pages,
) -> i32 {
let local_memory = *ctx.imported_memories.add(import_memory_index.index());
let local_memory = *ctx
.internal
.imported_memories
.add(import_memory_index.index());
let memory = (*local_memory).memory as *mut StaticMemory;
match (*memory).grow(delta, &mut *local_memory) {
@ -82,7 +85,10 @@ pub unsafe extern "C" fn imported_static_memory_size(
ctx: &vm::Ctx,
import_memory_index: ImportedMemoryIndex,
) -> Pages {
let local_memory = *ctx.imported_memories.add(import_memory_index.index());
let local_memory = *ctx
.internal
.imported_memories
.add(import_memory_index.index());
let memory = (*local_memory).memory as *mut StaticMemory;
(*memory).size()
@ -93,7 +99,7 @@ pub unsafe extern "C" fn imported_dynamic_memory_grow(
memory_index: ImportedMemoryIndex,
delta: Pages,
) -> i32 {
let local_memory = *ctx.imported_memories.add(memory_index.index());
let local_memory = *ctx.internal.imported_memories.add(memory_index.index());
let memory = (*local_memory).memory as *mut DynamicMemory;
match (*memory).grow(delta, &mut *local_memory) {
@ -106,7 +112,7 @@ pub unsafe extern "C" fn imported_dynamic_memory_size(
ctx: &vm::Ctx,
memory_index: ImportedMemoryIndex,
) -> Pages {
let local_memory = *ctx.imported_memories.add(memory_index.index());
let local_memory = *ctx.internal.imported_memories.add(memory_index.index());
let memory = (*local_memory).memory as *mut DynamicMemory;
(*memory).size()