diff --git a/fuzz/fuzz_targets/compile.rs b/fuzz/fuzz_targets/compile.rs index e94e659838..59d4ca7ef5 100644 --- a/fuzz/fuzz_targets/compile.rs +++ b/fuzz/fuzz_targets/compile.rs @@ -9,6 +9,9 @@ extern crate wasmtime_environ; extern crate wasmtime_jit; use cranelift_codegen::settings; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; use wasmparser::validate; use wasmtime_jit::{CompiledModule, Compiler, NullResolver}; @@ -23,7 +26,8 @@ fuzz_target!(|data: &[u8]| { let isa = isa_builder.finish(settings::Flags::new(flag_builder)); let mut compiler = Compiler::new(isa); let mut resolver = NullResolver {}; - let _compiled = match CompiledModule::new(&mut compiler, data, &mut resolver) { + let mut global_exports = Rc::new(RefCell::new(HashMap::new())); + let _compiled = match CompiledModule::new(&mut compiler, data, &mut resolver, global_exports) { Ok(x) => x, Err(_) => return, }; diff --git a/lib/environ/Cargo.toml b/lib/environ/Cargo.toml index 3961c5d9a7..8cf7451b32 100644 --- a/lib/environ/Cargo.toml +++ b/lib/environ/Cargo.toml @@ -18,12 +18,12 @@ cranelift-wasm = "0.26.0" cast = { version = "0.2.2", default-features = false } failure = { version = "0.1.3", default-features = false } failure_derive = { version = "0.1.3", default-features = false } -hashbrown = { version = "0.1.8", optional = true } +indexmap = "1.0.2" [features] default = ["std"] std = ["cranelift-codegen/std", "cranelift-wasm/std"] -core = ["hashbrown/nightly", "cranelift-codegen/core", "cranelift-wasm/core"] +core = ["cranelift-codegen/core", "cranelift-wasm/core"] [badges] maintenance = { status = "experimental" } diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index 3df9331acc..04111ce7d0 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -34,11 +34,6 @@ extern crate alloc as std; #[macro_use] extern crate std; -#[cfg(not(feature = "std"))] -use hashbrown::HashMap; -#[cfg(feature = "std")] -use std::collections::HashMap; - #[macro_use] extern crate failure_derive; diff --git a/lib/environ/src/module.rs b/lib/environ/src/module.rs index 7d89118019..11e68ef3da 100644 --- a/lib/environ/src/module.rs +++ b/lib/environ/src/module.rs @@ -1,6 +1,5 @@ //! Data structures for representing decoded wasm modules. -use super::HashMap; use crate::tunables::Tunables; use cranelift_codegen::ir; use cranelift_entity::{EntityRef, PrimaryMap}; @@ -8,6 +7,7 @@ use cranelift_wasm::{ DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex, }; +use indexmap::IndexMap; use std::string::String; use std::vec::Vec; @@ -162,7 +162,7 @@ pub struct Module { pub globals: PrimaryMap, /// Exported entities. - pub exports: HashMap, + pub exports: IndexMap, /// The module "start" function, if present. pub start_func: Option, @@ -184,7 +184,7 @@ impl Module { table_plans: PrimaryMap::new(), memory_plans: PrimaryMap::new(), globals: PrimaryMap::new(), - exports: HashMap::new(), + exports: IndexMap::new(), start_func: None, table_elements: Vec::new(), } diff --git a/lib/jit/Cargo.toml b/lib/jit/Cargo.toml index 84dcf169a9..9c49a902a5 100644 --- a/lib/jit/Cargo.toml +++ b/lib/jit/Cargo.toml @@ -16,8 +16,8 @@ cranelift-codegen = "0.26.0" cranelift-entity = "0.26.0" cranelift-wasm = "0.26.0" cranelift-frontend = "0.26.0" -wasmtime-environ = { path = "../environ" } -wasmtime-runtime = { path = "../runtime" } +wasmtime-environ = { path = "../environ", default-features = false } +wasmtime-runtime = { path = "../runtime", default-features = false } region = "1.0.0" failure = { version = "0.1.3", default-features = false } failure_derive = { version = "0.1.3", default-features = false } diff --git a/lib/jit/src/instantiate.rs b/lib/jit/src/instantiate.rs index 43ffdf9e19..02e5da777a 100644 --- a/lib/jit/src/instantiate.rs +++ b/lib/jit/src/instantiate.rs @@ -3,9 +3,11 @@ //! `CompiledModule` to allow compiling and instantiating to be done as separate //! steps. +use super::HashMap; use crate::compiler::Compiler; use crate::link::link_module; use crate::resolver::Resolver; +use core::cell::RefCell; use cranelift_entity::{BoxedSlice, PrimaryMap}; use cranelift_wasm::{DefinedFuncIndex, SignatureIndex}; use std::boxed::Box; @@ -16,7 +18,7 @@ use wasmtime_environ::{ CompileError, DataInitializer, DataInitializerLocation, Module, ModuleEnvironment, }; use wasmtime_runtime::{ - Imports, Instance, InstantiationError, VMFunctionBody, VMSharedSignatureIndex, + Export, Imports, Instance, InstantiationError, VMFunctionBody, VMSharedSignatureIndex, }; /// An error condition while setting up a wasm instance, be it validation, @@ -112,6 +114,7 @@ pub struct CompiledModule { imports: Imports, data_initializers: Box<[OwnedDataInitializer]>, signatures: BoxedSlice, + global_exports: Rc>>>, } impl CompiledModule { @@ -120,26 +123,28 @@ impl CompiledModule { compiler: &mut Compiler, data: &'data [u8], resolver: &mut dyn Resolver, + global_exports: Rc>>>, ) -> Result { let raw = RawCompiledModule::<'data>::new(compiler, data, resolver)?; - Ok(Self { - module: Rc::new(raw.module), - finished_functions: raw.finished_functions, - imports: raw.imports, - data_initializers: raw - .data_initializers + Ok(Self::from_parts( + raw.module, + global_exports, + raw.finished_functions, + raw.imports, + raw.data_initializers .iter() .map(OwnedDataInitializer::new) .collect::>() .into_boxed_slice(), - signatures: raw.signatures.clone(), - }) + raw.signatures.clone(), + )) } /// Construct a `CompiledModule` from component parts. pub fn from_parts( module: Module, + global_exports: Rc>>>, finished_functions: BoxedSlice, imports: Imports, data_initializers: Box<[OwnedDataInitializer]>, @@ -147,6 +152,7 @@ impl CompiledModule { ) -> Self { Self { module: Rc::new(module), + global_exports: Rc::clone(&global_exports), finished_functions, imports, data_initializers, @@ -170,6 +176,7 @@ impl CompiledModule { .collect::>(); Instance::new( Rc::clone(&self.module), + Rc::clone(&self.global_exports), self.finished_functions.clone(), self.imports.clone(), &data_initializers, @@ -206,11 +213,13 @@ pub fn instantiate( compiler: &mut Compiler, data: &[u8], resolver: &mut dyn Resolver, + global_exports: Rc>>>, ) -> Result { let raw = RawCompiledModule::new(compiler, data, resolver)?; Instance::new( Rc::new(raw.module), + global_exports, raw.finished_functions, raw.imports, &*raw.data_initializers, diff --git a/lib/jit/src/lib.rs b/lib/jit/src/lib.rs index bb484b6803..1176ee67ed 100644 --- a/lib/jit/src/lib.rs +++ b/lib/jit/src/lib.rs @@ -32,7 +32,7 @@ extern crate alloc as std; extern crate std; #[cfg(not(feature = "std"))] -use hashbrown::{map as hash_map, HashMap}; +use hashbrown::{hash_map, HashMap}; #[cfg(feature = "std")] use std::collections::{hash_map, HashMap}; diff --git a/lib/runtime/Cargo.toml b/lib/runtime/Cargo.toml index 50113d242c..2284c58e90 100644 --- a/lib/runtime/Cargo.toml +++ b/lib/runtime/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" cranelift-codegen = "0.26.0" cranelift-entity = "0.26.0" cranelift-wasm = "0.26.0" -wasmtime-environ = { path = "../environ" } +wasmtime-environ = { path = "../environ", default-features = false } region = "1.0.0" lazy_static = "1.2.0" libc = { version = "0.2.44", default-features = false } @@ -24,6 +24,7 @@ memoffset = "0.2.1" cast = { version = "0.2.2", default-features = false } failure = { version = "0.1.3", default-features = false } failure_derive = { version = "0.1.3", default-features = false } +indexmap = "1.0.2" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.6", features = ["winbase", "memoryapi"] } @@ -35,8 +36,7 @@ regex = "1.0.6" [features] default = ["std"] -std = ["cranelift-codegen/std", "cranelift-wasm/std"] -core = ["cranelift-codegen/core", "cranelift-wasm/core", "wasmtime-environ/core"] +std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std"] [badges] maintenance = { status = "experimental" } diff --git a/lib/runtime/src/export.rs b/lib/runtime/src/export.rs index e7789ef725..9a34fc7de2 100644 --- a/lib/runtime/src/export.rs +++ b/lib/runtime/src/export.rs @@ -6,7 +6,7 @@ use cranelift_wasm::Global; use wasmtime_environ::{MemoryPlan, TablePlan}; /// The value of an export passed from one instance to another. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Export { /// A function export value. Function { diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs index 0094db97c7..bf2e77d717 100644 --- a/lib/runtime/src/instance.rs +++ b/lib/runtime/src/instance.rs @@ -14,6 +14,8 @@ use crate::vmcontext::{ VMTableImport, }; use core::any::Any; +use core::borrow::Borrow; +use core::cell::RefCell; use core::slice; use core::{mem, ptr}; use cranelift_entity::EntityRef; @@ -22,10 +24,12 @@ use cranelift_wasm::{ DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableIndex, }; +use indexmap; use std::borrow::ToOwned; use std::boxed::Box; +use std::collections::{hash_map, HashMap}; use std::rc::Rc; -use std::string::String; +use std::string::{String, ToString}; use wasmtime_environ::{DataInitializer, Module, TableElements, VMOffsets}; fn signature_id( @@ -67,17 +71,127 @@ fn imported_table<'vmctx>( } } +fn imported_memory<'vmctx>( + vmctx: &'vmctx VMContext, + offsets: &VMOffsets, + index: MemoryIndex, +) -> &'vmctx VMMemoryImport { + #[allow(clippy::cast_ptr_alignment)] + unsafe { + let ptr = (vmctx as *const VMContext as *const u8) + .add(cast::usize(offsets.vmctx_vmmemory_import(index))); + &*(ptr as *const VMMemoryImport) + } +} + +fn imported_global<'vmctx>( + vmctx: &'vmctx VMContext, + offsets: &VMOffsets, + index: GlobalIndex, +) -> &'vmctx VMGlobalImport { + unsafe { + let ptr = (vmctx as *const VMContext as *const u8) + .add(cast::usize(offsets.vmctx_vmglobal_import(index))); + #[allow(clippy::cast_ptr_alignment)] + &*(ptr as *const VMGlobalImport) + } +} + +fn table<'vmctx>( + vmctx: &'vmctx VMContext, + offsets: &VMOffsets, + index: DefinedTableIndex, +) -> &'vmctx VMTableDefinition { + unsafe { + let ptr = (vmctx as *const VMContext as *const u8) + .add(cast::usize(offsets.vmctx_vmtable_definition(index))); + #[allow(clippy::cast_ptr_alignment)] + &*(ptr as *const VMTableDefinition) + } +} + +fn table_mut<'vmctx>( + vmctx: &'vmctx mut VMContext, + offsets: &VMOffsets, + index: DefinedTableIndex, +) -> &'vmctx mut VMTableDefinition { + unsafe { + let ptr = (vmctx as *mut VMContext as *mut u8) + .add(cast::usize(offsets.vmctx_vmtable_definition(index))); + #[allow(clippy::cast_ptr_alignment)] + &mut *(ptr as *mut VMTableDefinition) + } +} + +fn memory<'vmctx>( + vmctx: &'vmctx VMContext, + offsets: &VMOffsets, + index: DefinedMemoryIndex, +) -> &'vmctx VMMemoryDefinition { + unsafe { + let ptr = (vmctx as *const VMContext as *const u8) + .add(cast::usize(offsets.vmctx_vmmemory_definition(index))); + #[allow(clippy::cast_ptr_alignment)] + &*(ptr as *const VMMemoryDefinition) + } +} + +fn memory_mut<'vmctx>( + vmctx: &'vmctx mut VMContext, + offsets: &VMOffsets, + index: DefinedMemoryIndex, +) -> &'vmctx mut VMMemoryDefinition { + unsafe { + let ptr = (vmctx as *mut VMContext as *mut u8) + .add(cast::usize(offsets.vmctx_vmmemory_definition(index))); + #[allow(clippy::cast_ptr_alignment)] + &mut *(ptr as *mut VMMemoryDefinition) + } +} + +fn global<'vmctx>( + vmctx: &'vmctx VMContext, + offsets: &VMOffsets, + index: DefinedGlobalIndex, +) -> &'vmctx VMGlobalDefinition { + unsafe { + let ptr = (vmctx as *const VMContext as *const u8) + .add(cast::usize(offsets.vmctx_vmglobal_definition(index))); + #[allow(clippy::cast_ptr_alignment)] + &*(ptr as *const VMGlobalDefinition) + } +} + +fn global_mut<'vmctx>( + vmctx: &'vmctx mut VMContext, + offsets: &VMOffsets, + index: DefinedGlobalIndex, +) -> &'vmctx mut VMGlobalDefinition { + unsafe { + let ptr = (vmctx as *mut VMContext as *mut u8) + .add(cast::usize(offsets.vmctx_vmglobal_definition(index))); + #[allow(clippy::cast_ptr_alignment)] + &mut *(ptr as *mut VMGlobalDefinition) + } +} + /// The actual contents of an instance. /// /// `Instance` is just a handle containing a pointer to an `InstanceContents`, /// which is specially allocated. /// /// This is repr(C) to ensure that the vmctx field is last. +/// FIXME: Should this be pub(crate)? #[repr(C)] -pub(crate) struct InstanceContents { +pub struct InstanceContents { /// Offsets in the `vmctx` region. offsets: VMOffsets, + /// A global namespace of exports. This is a temporary mechanism to avoid + /// cyclic dependencies when one module wants to import from another and + /// make its memory available too, that will be obviated by host-bindings. + global_exports: Rc>>>, + /// WebAssembly linear memory data. memories: BoxedSlice, @@ -128,6 +242,7 @@ impl InstanceContents { } /// Return the index `VMTableImport`. + #[allow(dead_code)] fn imported_table(&self, index: TableIndex) -> &VMTableImport { imported_table(&self.vmctx, &self.offsets, index) } @@ -143,11 +258,7 @@ impl InstanceContents { /// Return the indexed `VMMemoryImport`. fn imported_memory(&self, index: MemoryIndex) -> &VMMemoryImport { - unsafe { - let ptr = (&self.vmctx as *const VMContext as *const u8) - .add(cast::usize(self.offsets.vmctx_vmmemory_import(index))); - &*(ptr as *const VMMemoryImport) - } + imported_memory(&self.vmctx, &self.offsets, index) } /// Return a pointer to the `VMMemoryImport`s. @@ -161,11 +272,7 @@ impl InstanceContents { /// Return the indexed `VMGlobalImport`. fn imported_global(&self, index: GlobalIndex) -> &VMGlobalImport { - unsafe { - let ptr = (&self.vmctx as *const VMContext as *const u8) - .add(cast::usize(self.offsets.vmctx_vmglobal_import(index))); - &*(ptr as *const VMGlobalImport) - } + imported_global(&self.vmctx, &self.offsets, index) } /// Return a pointer to the `VMGlobalImport`s. @@ -180,20 +287,13 @@ impl InstanceContents { /// Return the indexed `VMTableDefinition`. #[allow(dead_code)] fn table(&self, index: DefinedTableIndex) -> &VMTableDefinition { - unsafe { - let ptr = (&self.vmctx as *const VMContext as *const u8) - .add(cast::usize(self.offsets.vmctx_vmtable_definition(index))); - &*(ptr as *const VMTableDefinition) - } + table(&self.vmctx, &self.offsets, index) } /// Return the indexed `VMTableDefinition`. + #[allow(dead_code)] fn table_mut(&mut self, index: DefinedTableIndex) -> &mut VMTableDefinition { - unsafe { - let ptr = (&mut self.vmctx as *mut VMContext as *mut u8) - .add(cast::usize(self.offsets.vmctx_vmtable_definition(index))); - &mut *(ptr as *mut VMTableDefinition) - } + table_mut(&mut self.vmctx, &self.offsets, index) } /// Return a pointer to the `VMTableDefinition`s. @@ -207,20 +307,12 @@ impl InstanceContents { /// Return the indexed `VMMemoryDefinition`. fn memory(&self, index: DefinedMemoryIndex) -> &VMMemoryDefinition { - unsafe { - let ptr = (&self.vmctx as *const VMContext as *const u8) - .add(cast::usize(self.offsets.vmctx_vmmemory_definition(index))); - &*(ptr as *const VMMemoryDefinition) - } + memory(&self.vmctx, &self.offsets, index) } /// Return the indexed `VMMemoryDefinition`. fn memory_mut(&mut self, index: DefinedMemoryIndex) -> &mut VMMemoryDefinition { - unsafe { - let ptr = (&mut self.vmctx as *mut VMContext as *mut u8) - .add(cast::usize(self.offsets.vmctx_vmmemory_definition(index))); - &mut *(ptr as *mut VMMemoryDefinition) - } + memory_mut(&mut self.vmctx, &self.offsets, index) } /// Return a pointer to the `VMMemoryDefinition`s. @@ -235,20 +327,12 @@ impl InstanceContents { /// Return the indexed `VMGlobalDefinition`. #[allow(dead_code)] fn global(&self, index: DefinedGlobalIndex) -> &VMGlobalDefinition { - unsafe { - let ptr = (&self.vmctx as *const VMContext as *const u8) - .add(cast::usize(self.offsets.vmctx_vmglobal_definition(index))); - &*(ptr as *const VMGlobalDefinition) - } + global(&self.vmctx, &self.offsets, index) } /// Return the indexed `VMGlobalDefinition`. fn global_mut(&mut self, index: DefinedGlobalIndex) -> &mut VMGlobalDefinition { - unsafe { - let ptr = (&mut self.vmctx as *mut VMContext as *mut u8) - .add(cast::usize(self.offsets.vmctx_vmglobal_definition(index))); - &mut *(ptr as *mut VMGlobalDefinition) - } + global_mut(&mut self.vmctx, &self.offsets, index) } /// Return a pointer to the `VMGlobalDefinition`s. @@ -280,30 +364,71 @@ impl InstanceContents { self.vmctx_mut() } + fn invoke_function( + &mut self, + module: &Module, + index: FuncIndex, + ) -> Result<(), InstantiationError> { + // TODO: Check that the callee's calling convention matches what we expect. + + let (callee_address, callee_vmctx) = match module.defined_func_index(index) { + Some(defined_index) => { + let body = *self + .finished_functions + .get(defined_index) + .expect("function index is out of bounds"); + (body, self.vmctx_mut() as *mut VMContext) + } + None => { + assert!(index.index() < module.imported_funcs.len()); + let import = self.imported_function(index); + (import.body, import.vmctx) + } + }; + + // Make the call. + unsafe { wasmtime_call(callee_address, callee_vmctx) } + .map_err(InstantiationError::StartTrap) + } + /// Invoke the WebAssembly start function of the instance, if one is present. fn invoke_start_function(&mut self, module: &Module) -> Result<(), InstantiationError> { if let Some(start_index) = module.start_func { - let (callee_address, callee_vmctx) = match module.defined_func_index(start_index) { - Some(defined_start_index) => { - let body = *self - .finished_functions - .get(defined_start_index) - .expect("start function index is out of bounds"); - (body, self.vmctx_mut() as *mut VMContext) + self.invoke_function(module, start_index) + } else if let Some(start_export) = module.exports.get("_start") { + // As a compatibility measure, if the module doesn't have a start + // function but does have a _start function exported, call that. + match start_export { + wasmtime_environ::Export::Function(func_index) => { + let sig = &module.signatures[module.functions[*func_index]]; + // No wasm params or returns; just the vmctx param. + if sig.params.len() == 1 && sig.returns.is_empty() { + self.invoke_function(module, *func_index) + } else { + Ok(()) + } } - None => { - assert!(start_index.index() < module.imported_funcs.len()); - let import = self.imported_function(start_index); - (import.body, import.vmctx) + _ => Ok(()), + } + } else if let Some(main_export) = module.exports.get("main") { + // As a further compatibility measure, if the module doesn't have a + // start function or a _start function exported, but does have a main + // function exported, call that. + match main_export { + wasmtime_environ::Export::Function(func_index) => { + let sig = &module.signatures[module.functions[*func_index]]; + // No wasm params or returns; just the vmctx param. + if sig.params.len() == 1 && sig.returns.is_empty() { + self.invoke_function(module, *func_index) + } else { + Ok(()) + } } - }; - - // Make the call. - unsafe { wasmtime_call(callee_address, callee_vmctx) } - .map_err(InstantiationError::StartTrap)?; + _ => Ok(()), + } + } else { + Ok(()) } - - Ok(()) } /// Return the offset from the vmctx pointer to its containing Instance. @@ -328,7 +453,8 @@ impl InstanceContents { } /// Return the memory index for the given `VMMemoryDefinition`. - pub(crate) fn memory_index(&self, memory: &VMMemoryDefinition) -> DefinedMemoryIndex { + /// FIXME: Should this be pub(crate)? + pub fn memory_index(&self, memory: &VMMemoryDefinition) -> DefinedMemoryIndex { let offsets = &self.offsets; let begin = unsafe { (&self.vmctx as *const VMContext as *const u8) @@ -355,11 +481,8 @@ impl InstanceContents { /// /// Returns `None` if memory can't be grown by the specified amount /// of pages. - pub(crate) fn memory_grow( - &mut self, - memory_index: DefinedMemoryIndex, - delta: u32, - ) -> Option { + /// FIXME: Should this be pub(crate)? + pub fn memory_grow(&mut self, memory_index: DefinedMemoryIndex, delta: u32) -> Option { let result = self .memories .get_mut(memory_index) @@ -414,6 +537,17 @@ impl InstanceContents { pub fn host_state(&mut self) -> &mut Any { &mut *self.host_state } + + pub(crate) fn lookup_global_export(&self, field: &str) -> Option { + let cell: &RefCell>> = + self.global_exports.borrow(); + let map: &mut HashMap> = + &mut cell.borrow_mut(); + if let Some(Some(export)) = map.get(field) { + return Some(export.clone()); + } + None + } } /// A wrapper around an `Mmap` holding an `InstanceContents`. @@ -461,6 +595,7 @@ impl Instance { /// Create a new `Instance`. pub fn new( module: Rc, + global_exports: Rc>>>, finished_functions: BoxedSlice, imports: Imports, data_initializers: &[DataInitializer<'_>], @@ -507,6 +642,7 @@ impl Instance { #[allow(clippy::cast_ptr_alignment)] let contents_ptr = contents_mmap.as_mut_ptr() as *mut InstanceContents; let contents = InstanceContents { + global_exports, offsets, memories, tables, @@ -572,6 +708,27 @@ impl Instance { initialize_memories(&*module, contents, data_initializers)?; initialize_globals(&*module, contents); + // Collect the exports for the global export map. + for (field, decl) in &module.exports { + use hash_map::Entry::*; + let cell: &RefCell>> = + contents.global_exports.borrow(); + let map: &mut HashMap> = + &mut cell.borrow_mut(); + match map.entry(field.to_string()) { + Vacant(entry) => { + entry.insert(Some(lookup_by_declaration( + &module, + &mut contents.vmctx, + &contents.offsets, + &contents.finished_functions, + &decl, + ))); + } + Occupied(ref mut entry) => *entry.get_mut() = None, + } + } + // Ensure that our signal handlers are ready for action. // TODO: Move these calls out of `Instance`. wasmtime_init_eager(); @@ -581,7 +738,7 @@ impl Instance { // invoked automatically at instantiation time. contents.invoke_start_function(&*module)?; - Ok(Instance { + Ok(Self { module, mmap_field: MmapField { mmap: contents_mmap, @@ -611,80 +768,21 @@ impl Instance { /// Lookup an export with the given name. pub fn lookup(&mut self, field: &str) -> Option { - let contents = self.mmap_field.contents_mut(); - if let Some(export) = self.module.exports.get(field) { - Some(match export { - wasmtime_environ::Export::Function(index) => { - let signature = self.module.signatures[self.module.functions[*index]].clone(); - let (address, vmctx) = - if let Some(def_index) = self.module.defined_func_index(*index) { - ( - contents.finished_functions[def_index], - &mut contents.vmctx as *mut VMContext, - ) - } else { - let import = contents.imported_function(*index); - (import.body, import.vmctx) - }; - Export::Function { - address, - signature, - vmctx, - } - } - wasmtime_environ::Export::Table(index) => { - let (definition, vmctx) = - if let Some(def_index) = self.module.defined_table_index(*index) { - ( - contents.table_mut(def_index) as *mut VMTableDefinition, - &mut contents.vmctx as *mut VMContext, - ) - } else { - let import = contents.imported_table(*index); - (import.from, import.vmctx) - }; - Export::Table { - definition, - vmctx, - table: self.module.table_plans[*index].clone(), - } - } - wasmtime_environ::Export::Memory(index) => { - let (definition, vmctx) = - if let Some(def_index) = self.module.defined_memory_index(*index) { - ( - contents.memory_mut(def_index) as *mut VMMemoryDefinition, - &mut contents.vmctx as *mut VMContext, - ) - } else { - let import = contents.imported_memory(*index); - (import.from, import.vmctx) - }; - Export::Memory { - definition, - vmctx, - memory: self.module.memory_plans[*index].clone(), - } - } - wasmtime_environ::Export::Global(index) => Export::Global { - definition: if let Some(def_index) = self.module.defined_global_index(*index) { - contents.global_mut(def_index) - } else { - contents.imported_global(*index).from - }, - global: self.module.globals[*index], - }, - }) + let export = if let Some(export) = self.module.exports.get(field) { + export.clone() + } else if let Some(export) = self.mmap_field.contents().lookup_global_export(field) { + return Some(export.clone()); } else { - None - } + return None; + }; + Some(self.lookup_by_declaration(&export)) } /// Lookup an export with the given name. This takes an immutable reference, - /// and the result is an `Export` that can only be used to read, not write. - /// This requirement is not enforced in the type system, so this function is - /// unsafe. + /// and the result is an `Export` that the type system doesn't prevent from + /// being used to mutate the instance, so this function is unsafe. pub unsafe fn lookup_immutable(&self, field: &str) -> Option { + #[allow(clippy::cast_ref_to_mut)] let temporary_mut = &mut *(self as *const Self as *mut Self); temporary_mut.lookup(field) } @@ -693,6 +791,104 @@ impl Instance { pub fn host_state(&mut self) -> &mut Any { self.mmap_field.contents_mut().host_state() } + + /// Lookup an export with the given export declaration. + pub fn lookup_by_declaration(&mut self, export: &wasmtime_environ::Export) -> Export { + let contents = self.mmap_field.contents_mut(); + lookup_by_declaration( + &self.module, + &mut contents.vmctx, + &contents.offsets, + &contents.finished_functions, + export, + ) + } + + /// Lookup an export with the given export declaration. This takes an immutable + /// reference, and the result is an `Export` that the type system doesn't prevent + /// from being used to mutate the instance, so this function is unsafe. + pub unsafe fn lookup_immutable_by_declaration( + &self, + export: &wasmtime_environ::Export, + ) -> Export { + #[allow(clippy::cast_ref_to_mut)] + let temporary_mut = &mut *(self as *const Self as *mut Self); + temporary_mut.lookup_by_declaration(export) + } + + /// Return an iterator over the exports of this instance. + /// + /// Specifically, it provides access to the key-value pairs, where they keys + /// are export names, and the values are export declarations which can be + /// resolved `lookup_by_declaration`. + pub fn exports(&self) -> indexmap::map::Iter { + self.module.exports.iter() + } +} + +fn lookup_by_declaration( + module: &Module, + vmctx: &mut VMContext, + offsets: &VMOffsets, + finished_functions: &BoxedSlice, + export: &wasmtime_environ::Export, +) -> Export { + match export { + wasmtime_environ::Export::Function(index) => { + let signature = module.signatures[module.functions[*index]].clone(); + let (address, vmctx) = if let Some(def_index) = module.defined_func_index(*index) { + (finished_functions[def_index], vmctx as *mut VMContext) + } else { + let import = imported_function(vmctx, offsets, *index); + (import.body, import.vmctx) + }; + Export::Function { + address, + signature, + vmctx, + } + } + wasmtime_environ::Export::Table(index) => { + let (definition, vmctx) = if let Some(def_index) = module.defined_table_index(*index) { + ( + table_mut(vmctx, offsets, def_index) as *mut VMTableDefinition, + vmctx as *mut VMContext, + ) + } else { + let import = imported_table(vmctx, offsets, *index); + (import.from, import.vmctx) + }; + Export::Table { + definition, + vmctx, + table: module.table_plans[*index].clone(), + } + } + wasmtime_environ::Export::Memory(index) => { + let (definition, vmctx) = if let Some(def_index) = module.defined_memory_index(*index) { + ( + memory_mut(vmctx, offsets, def_index) as *mut VMMemoryDefinition, + vmctx as *mut VMContext, + ) + } else { + let import = imported_memory(vmctx, offsets, *index); + (import.from, import.vmctx) + }; + Export::Memory { + definition, + vmctx, + memory: module.memory_plans[*index].clone(), + } + } + wasmtime_environ::Export::Global(index) => Export::Global { + definition: if let Some(def_index) = module.defined_global_index(*index) { + global_mut(vmctx, offsets, def_index) + } else { + imported_global(vmctx, offsets, *index).from + }, + global: module.globals[*index], + }, + } } fn check_table_init_bounds( diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index b7cb319170..95787051ef 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -21,20 +21,6 @@ clippy::use_self ) )] -#![no_std] -#![cfg_attr(not(feature = "std"), feature(alloc))] - -#[cfg(not(feature = "std"))] -#[macro_use] -extern crate alloc as std; -#[cfg(feature = "std")] -#[macro_use] -extern crate std; - -#[cfg(not(feature = "std"))] -use hashbrown::{map as hash_map, HashMap}; -#[cfg(feature = "std")] -use std::collections::{hash_map, HashMap}; #[macro_use] extern crate lazy_static; diff --git a/lib/runtime/src/sig_registry.rs b/lib/runtime/src/sig_registry.rs index 79a175a5e8..73d8cf1439 100644 --- a/lib/runtime/src/sig_registry.rs +++ b/lib/runtime/src/sig_registry.rs @@ -1,10 +1,10 @@ //! Implement a registry of function signatures, for fast indirect call //! signature checking. -use super::{hash_map, HashMap}; use crate::vmcontext::VMSharedSignatureIndex; use cast; use cranelift_codegen::ir; +use std::collections::{hash_map, HashMap}; /// WebAssembly requires that the caller and callee signatures in an indirect /// call must match. To implement this efficiently, keep a registry of all diff --git a/lib/runtime/src/vmcontext.rs b/lib/runtime/src/vmcontext.rs index 10e6e95471..f0fd91195c 100644 --- a/lib/runtime/src/vmcontext.rs +++ b/lib/runtime/src/vmcontext.rs @@ -409,7 +409,7 @@ mod test_vmshared_signature_index { impl VMSharedSignatureIndex { /// Create a new `VMSharedSignatureIndex`. pub fn new(value: u32) -> Self { - VMSharedSignatureIndex(value) + Self(value) } } @@ -482,8 +482,9 @@ impl VMContext { /// /// This is unsafe because it doesn't work on just any `VMContext`, it must /// be a `VMContext` allocated as part of an `Instance`. + /// FIXME: make this pub(crate)? #[allow(clippy::cast_ptr_alignment)] - pub(crate) unsafe fn instance_contents(&mut self) -> &mut InstanceContents { + pub unsafe fn instance_contents(&mut self) -> &mut InstanceContents { &mut *((self as *mut Self as *mut u8).offset(-InstanceContents::vmctx_offset()) as *mut InstanceContents) } @@ -495,4 +496,9 @@ impl VMContext { pub unsafe fn host_state(&mut self) -> &mut Any { self.instance_contents().host_state() } + + /// Lookup an export in the global exports namespace. + pub unsafe fn lookup_global_export(&mut self, field: &str) -> Option { + self.instance_contents().lookup_global_export(field) + } } diff --git a/lib/wast/src/spectest.rs b/lib/wast/src/spectest.rs index 6f74877cda..b407b060a7 100644 --- a/lib/wast/src/spectest.rs +++ b/lib/wast/src/spectest.rs @@ -2,6 +2,8 @@ use cranelift_codegen::ir::types; use cranelift_codegen::{ir, isa}; use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType}; +use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use target_lexicon::HOST; use wasmtime_environ::{translate_signature, Export, MemoryPlan, Module, TablePlan}; @@ -216,6 +218,7 @@ pub fn instantiate_spectest() -> Result { Instance::new( Rc::new(module), + Rc::new(RefCell::new(HashMap::new())), finished_functions.into_boxed_slice(), imports, &data_initializers, diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs index b189977373..1d52740b6c 100644 --- a/lib/wast/src/wast.rs +++ b/lib/wast/src/wast.rs @@ -1,6 +1,9 @@ use crate::spectest::instantiate_spectest; +use std::cell::RefCell; +use std::collections::HashMap; use std::io::Read; use std::path::Path; +use std::rc::Rc; use std::{fmt, fs, io, str}; use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value}; use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig}; @@ -122,7 +125,12 @@ impl WastContext { self.validate(&data).map_err(SetupError::Validate)?; - instantiate(&mut *self.compiler, &data, &mut self.namespace) + instantiate( + &mut *self.compiler, + &data, + &mut self.namespace, + Rc::new(RefCell::new(HashMap::new())), + ) } fn get_index(&mut self, instance_name: &Option) -> Result { diff --git a/src/wasmtime.rs b/src/wasmtime.rs index d823d58e11..7cf11bd8a1 100644 --- a/src/wasmtime.rs +++ b/src/wasmtime.rs @@ -39,6 +39,8 @@ use cranelift_native; use docopt::Docopt; use file_per_thread_logger; use pretty_env_logger; +use std::cell::RefCell; +use std::collections::HashMap; use std::error::Error; use std::fs::File; use std::io; @@ -46,6 +48,7 @@ use std::io::prelude::*; use std::path::Path; use std::path::PathBuf; use std::process::exit; +use std::rc::Rc; use wabt; use wasmtime_jit::{instantiate, ActionOutcome, Compiler, Namespace}; use wasmtime_wast::instantiate_spectest; @@ -121,6 +124,7 @@ fn main() { let mut compiler = Compiler::new(isa); let mut namespace = Namespace::new(); + let global_exports = Rc::new(RefCell::new(HashMap::new())); // Make spectest available by default. namespace.instance( @@ -130,7 +134,13 @@ fn main() { for filename in &args.arg_file { let path = Path::new(&filename); - match handle_module(&mut compiler, &mut namespace, &args, path) { + match handle_module( + &mut compiler, + &mut namespace, + Rc::clone(&global_exports), + &args, + path, + ) { Ok(()) => {} Err(message) => { let name = path.as_os_str().to_string_lossy(); @@ -144,6 +154,7 @@ fn main() { fn handle_module( compiler: &mut Compiler, namespace: &mut Namespace, + global_exports: Rc>>>, args: &Args, path: &Path, ) -> Result<(), String> { @@ -156,7 +167,8 @@ fn handle_module( } // Create a new `Instance` by compiling and instantiating a wasm module. - let instance = instantiate(compiler, &data, namespace).map_err(|e| e.to_string())?; + let instance = + instantiate(compiler, &data, namespace, global_exports).map_err(|e| e.to_string())?; // Register it in the namespace. let index = namespace.instance(None, instance); @@ -181,7 +193,10 @@ fn handle_module( mod tests { use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; + use std::cell::RefCell; + use std::collections::HashMap; use std::path::PathBuf; + use std::rc::Rc; use wabt; use wasmtime_jit::{instantiate, Compiler, NullResolver}; @@ -207,7 +222,8 @@ mod tests { let mut resolver = NullResolver {}; let mut compiler = Compiler::new(isa); - let instance = instantiate(&mut compiler, &data, &mut resolver); + let global_exports = Rc::new(RefCell::new(HashMap::new())); + let instance = instantiate(&mut compiler, &data, &mut resolver, global_exports); assert!(instance.is_ok()); } }