diff --git a/lib/jit/Cargo.toml b/lib/jit/Cargo.toml index 97b179bafc..27573e45ff 100644 --- a/lib/jit/Cargo.toml +++ b/lib/jit/Cargo.toml @@ -23,6 +23,7 @@ failure = { version = "0.1.3", default-features = false } failure_derive = { version = "0.1.3", default-features = false } target-lexicon = { version = "0.2.0", default-features = false } hashbrown = { version = "0.1.8", optional = true } +wasmparser = "0.23.0" [features] default = ["std"] diff --git a/lib/jit/src/context.rs b/lib/jit/src/context.rs new file mode 100644 index 0000000000..6429a7c5b2 --- /dev/null +++ b/lib/jit/src/context.rs @@ -0,0 +1,182 @@ +use crate::{ + instantiate, ActionError, ActionOutcome, Compiler, Instance, InstanceIndex, Namespace, + RuntimeValue, SetupError, +}; +use cranelift_codegen::isa::TargetIsa; +use std::borrow::ToOwned; +use std::boxed::Box; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use std::string::{String, ToString}; +use std::{fmt, str}; +use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig}; + +/// Indicates an unknown instance was specified. +#[derive(Fail, Debug)] +pub struct UnknownInstance { + instance_name: String, +} + +impl fmt::Display for UnknownInstance { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "no instance {} present", self.instance_name) + } +} + +/// Error message used by `WastContext`. +#[derive(Fail, Debug)] +pub enum ContextError { + /// An unknown instance name was used. + Instance(UnknownInstance), + /// An error occured while performing an action. + Action(ActionError), +} + +impl fmt::Display for ContextError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + ContextError::Instance(ref error) => error.fmt(f), + ContextError::Action(ref error) => error.fmt(f), + } + } +} + +/// A convenient context for compiling and executing WebAssembly instances. +pub struct Context { + namespace: Namespace, + compiler: Box, + global_exports: Rc>>>, +} + +impl Context { + /// Construct a new instance of `Context`. + pub fn new(compiler: Box) -> Self { + Self { + namespace: Namespace::new(), + compiler, + global_exports: Rc::new(RefCell::new(HashMap::new())), + } + } + + /// Construct a new instance of `Context` with the given target. + pub fn with_isa(isa: Box) -> Self { + Self::new(Box::new(Compiler::new(isa))) + } + + fn validate(&mut self, data: &[u8]) -> Result<(), String> { + let config = ValidatingParserConfig { + operator_config: OperatorValidatorConfig { + enable_threads: false, + enable_reference_types: false, + }, + mutable_global_imports: true, + }; + + // TODO: Fix Cranelift to be able to perform validation itself, rather + // than calling into wasmparser ourselves here. + if validate(data, Some(config)) { + Ok(()) + } else { + // TODO: Work with wasmparser to get better error messages. + Err("module did not validate".to_owned()) + } + } + + fn instantiate(&mut self, data: &[u8]) -> Result { + self.validate(&data).map_err(SetupError::Validate)?; + + instantiate( + &mut *self.compiler, + &data, + &mut self.namespace, + Rc::clone(&mut self.global_exports), + ) + } + + /// Return the instance index for the instance with the given name. + pub fn get_instance_index( + &mut self, + instance_name: &str, + ) -> Result { + self.namespace + .get_instance_index(instance_name) + .ok_or_else(|| UnknownInstance { + instance_name: instance_name.to_string(), + }) + } + + /// Instantiate a module instance and register the instance. + pub fn instantiate_module( + &mut self, + instance_name: Option, + data: &[u8], + ) -> Result { + let instance = self.instantiate(data).map_err(ActionError::Setup)?; + Ok(self.instance(instance_name, instance)) + } + + /// Install a new `Instance` in this `Namespace`, optionally with the + /// given name, and return its index. + pub fn instance(&mut self, instance_name: Option, instance: Instance) -> InstanceIndex { + self.namespace.instance(instance_name, instance) + } + + /// Register an additional name for an existing registered instance. + pub fn alias(&mut self, name: &str, as_name: String) -> Result<(), UnknownInstance> { + let index = self.get_instance_index(&name)?; + self.alias_for_indexed(index, as_name); + Ok(()) + } + + /// Register an additional name for an existing registered instance. + pub fn alias_for_indexed(&mut self, index: InstanceIndex, as_name: String) { + self.namespace.alias_for_indexed(index, as_name) + } + + /// Invoke an exported function from an instance. + pub fn invoke( + &mut self, + instance_name: &str, + field: &str, + args: &[RuntimeValue], + ) -> Result { + let index = self + .get_instance_index(&instance_name) + .map_err(ContextError::Instance)?; + self.invoke_indexed(index, field, args) + .map_err(ContextError::Action) + } + + /// Invoke an exported function from an instance. + pub fn invoke_indexed( + &mut self, + index: InstanceIndex, + field: &str, + args: &[RuntimeValue], + ) -> Result { + self.namespace + .invoke(&mut *self.compiler, index, field, &args) + } + + /// Get the value of an exported global variable from an instance. + pub fn get(&mut self, instance_name: &str, field: &str) -> Result { + let index = self + .get_instance_index(&instance_name) + .map_err(ContextError::Instance)?; + self.get_indexed(index, field).map_err(ContextError::Action) + } + + /// Get the value of an exported global variable from an instance. + pub fn get_indexed( + &mut self, + index: InstanceIndex, + field: &str, + ) -> Result { + self.namespace + .get(index, field) + .map(|value| ActionOutcome::Returned { + values: vec![value], + }) + } +} diff --git a/lib/jit/src/lib.rs b/lib/jit/src/lib.rs index 1176ee67ed..a6ff8447a5 100644 --- a/lib/jit/src/lib.rs +++ b/lib/jit/src/lib.rs @@ -44,6 +44,7 @@ extern crate failure_derive; mod action; mod code_memory; mod compiler; +mod context; mod instantiate; mod link; mod namespace; @@ -52,6 +53,7 @@ mod target_tunables; pub use crate::action::{ActionError, ActionOutcome, RuntimeValue}; pub use crate::compiler::Compiler; +pub use crate::context::{Context, ContextError, UnknownInstance}; pub use crate::instantiate::{instantiate, CompiledModule, SetupError}; pub use crate::link::link_module; pub use crate::namespace::{InstanceIndex, Namespace}; diff --git a/lib/jit/src/namespace.rs b/lib/jit/src/namespace.rs index 7794066eeb..e5c2eb24e8 100644 --- a/lib/jit/src/namespace.rs +++ b/lib/jit/src/namespace.rs @@ -39,22 +39,22 @@ impl Namespace { /// Install a new `Instance` in this `Namespace`, optionally with the /// given name, and return its index. - pub fn instance(&mut self, instance_name: Option<&str>, instance: Instance) -> InstanceIndex { + pub fn instance(&mut self, instance_name: Option, instance: Instance) -> InstanceIndex { let index = self.instances.push(instance); if let Some(instance_name) = instance_name { - self.names.insert(instance_name.into(), index); + self.names.insert(instance_name, index); } index } /// Get the instance index registered with the given `instance_name`. - pub fn get_instance_index(&mut self, instance_name: &str) -> Option<&mut InstanceIndex> { - self.names.get_mut(instance_name) + pub fn get_instance_index(&mut self, instance_name: &str) -> Option { + self.names.get_mut(instance_name).cloned() } - /// Register an instance with a given name. - pub fn register(&mut self, name: String, index: InstanceIndex) { - self.names.insert(name, index); + /// Register an additional name for an existing registered instance. + pub fn alias_for_indexed(&mut self, existing_index: InstanceIndex, new_name: String) { + self.names.insert(new_name, existing_index); } /// Invoke an exported function from an instance. diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs index 043f2b9523..d917143cc4 100644 --- a/lib/runtime/src/instance.rs +++ b/lib/runtime/src/instance.rs @@ -760,8 +760,6 @@ impl Instance { pub fn lookup(&mut self, field: &str) -> Option { 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 { return None; }; diff --git a/lib/wast/Cargo.toml b/lib/wast/Cargo.toml index 67ed2d386d..a5540f4e85 100644 --- a/lib/wast/Cargo.toml +++ b/lib/wast/Cargo.toml @@ -23,7 +23,6 @@ wabt = "0.7" target-lexicon = "0.2.0" failure = { version = "0.1.3", default-features = false } failure_derive = { version = "0.1.3", default-features = false } -wasmparser = "0.23.0" [badges] maintenance = { status = "experimental" } diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs index 1d52740b6c..187330f1c8 100644 --- a/lib/wast/src/wast.rs +++ b/lib/wast/src/wast.rs @@ -1,15 +1,11 @@ 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}; use wasmtime_jit::{ - instantiate, ActionError, ActionOutcome, Compiler, Instance, InstanceIndex, InstantiationError, - Namespace, RuntimeValue, SetupError, + ActionError, ActionOutcome, Compiler, Context, InstanceIndex, InstantiationError, RuntimeValue, + UnknownInstance, }; /// Translate from a `script::Value` to a `RuntimeValue`. @@ -22,21 +18,6 @@ fn runtime_value(v: Value) -> RuntimeValue { } } -/// Indicates an unknown instance was specified. -#[derive(Fail, Debug)] -pub struct UnknownInstance { - instance: Option, -} - -impl fmt::Display for UnknownInstance { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.instance { - None => write!(f, "no default instance present"), - Some(ref name) => write!(f, "no instance {} present", name), - } - } -} - /// Error message used by `WastContext`. #[derive(Fail, Debug)] pub enum WastError { @@ -44,6 +25,8 @@ pub enum WastError { Assert(String), /// An unknown instance name was used. Instance(UnknownInstance), + /// No default instance has been registered yet. + NoDefaultInstance, /// An error occured while performing an action. Action(ActionError), /// An action trapped. @@ -63,6 +46,7 @@ impl fmt::Display for WastError { match *self { WastError::Assert(ref message) => write!(f, "Assert command failed: {}", message), WastError::Instance(ref error) => error.fmt(f), + WastError::NoDefaultInstance => write!(f, "no default instance defined yet"), WastError::Action(ref error) => error.fmt(f), WastError::Trap(ref message) => write!(f, "trap: {}", message), WastError::Type(ref message) => write!(f, "type error: {}", message), @@ -85,10 +69,11 @@ pub struct WastFileError { /// The wast test script language allows modules to be defined and actions /// to be performed on them. pub struct WastContext { - /// A namespace of wasm modules, keyed by an optional name. + /// Wast files have a concept of a "current" module, which is the most + /// recently defined. current: Option, - namespace: Namespace, - compiler: Box, + + context: Context, } impl WastContext { @@ -96,56 +81,23 @@ impl WastContext { pub fn new(compiler: Box) -> Self { Self { current: None, - namespace: Namespace::new(), - compiler, + context: Context::new(compiler), } } - fn validate(&mut self, data: &[u8]) -> Result<(), String> { - let config = ValidatingParserConfig { - operator_config: OperatorValidatorConfig { - enable_threads: false, - enable_reference_types: false, - }, - mutable_global_imports: true, - }; - - // TODO: Fix Cranelift to be able to perform validation itself, rather - // than calling into wasmparser ourselves here. - if validate(data, Some(config)) { - Ok(()) - } else { - // TODO: Work with wasmparser to get better error messages. - Err("module did not validate".to_owned()) - } - } - - fn instantiate(&mut self, module: ModuleBinary) -> Result { - let data = module.into_vec(); - - self.validate(&data).map_err(SetupError::Validate)?; - - instantiate( - &mut *self.compiler, - &data, - &mut self.namespace, - Rc::new(RefCell::new(HashMap::new())), - ) - } - - fn get_index(&mut self, instance_name: &Option) -> Result { - let index = *if let Some(instance_name) = instance_name { - self.namespace + fn get_instance_index( + &mut self, + instance_name: Option<&str>, + ) -> Result { + let index = if let Some(instance_name) = instance_name { + self.context .get_instance_index(instance_name) - .ok_or_else(|| { - WastError::Instance(UnknownInstance { - instance: Some(instance_name.to_string()), - }) - }) + .map_err(WastError::Instance) } else { self.current .as_mut() - .ok_or_else(|| WastError::Instance(UnknownInstance { instance: None })) + .cloned() + .ok_or_else(|| WastError::NoDefaultInstance) }?; Ok(index) @@ -154,7 +106,7 @@ impl WastContext { /// Register "spectest" which is used by the spec testsuite. pub fn register_spectest(&mut self) -> Result<(), InstantiationError> { let instance = instantiate_spectest()?; - self.namespace.instance(Some("spectest"), instance); + self.context.instance(Some("spectest".to_owned()), instance); Ok(()) } @@ -179,18 +131,17 @@ impl WastContext { instance_name: Option, module: ModuleBinary, ) -> Result<(), ActionError> { - let instance = self.instantiate(module).map_err(ActionError::Setup)?; let index = self - .namespace - .instance(instance_name.as_ref().map(String::as_str), instance); + .context + .instantiate_module(instance_name, &module.into_vec())?; self.current = Some(index); Ok(()) } /// Register an instance to make it available for performing actions. fn register(&mut self, name: Option, as_name: String) -> Result<(), WastError> { - let index = self.get_index(&name)?; - self.namespace.register(as_name, index); + let index = self.get_instance_index(name.as_ref().map(|x| &**x))?; + self.context.alias_for_indexed(index, as_name); Ok(()) } @@ -205,9 +156,9 @@ impl WastContext { .iter() .map(|arg| runtime_value(*arg)) .collect::>(); - let index = self.get_index(&instance_name)?; - self.namespace - .invoke(&mut *self.compiler, index, field, &value_args) + let index = self.get_instance_index(instance_name.as_ref().map(|x| &**x))?; + self.context + .invoke_indexed(index, field, &value_args) .map_err(WastError::Action) } @@ -217,14 +168,10 @@ impl WastContext { instance_name: Option, field: &str, ) -> Result { - let index = self.get_index(&instance_name)?; - let value = self - .namespace - .get(index, field) - .map_err(WastError::Action)?; - Ok(ActionOutcome::Returned { - values: vec![value], - }) + let index = self.get_instance_index(instance_name.as_ref().map(|x| &**x))?; + self.context + .get_indexed(index, field) + .map_err(WastError::Action) } /// Perform the action of a `PerformAction`. diff --git a/src/wasmtime.rs b/src/wasmtime.rs index 7cf11bd8a1..c1ce9a2d6e 100644 --- a/src/wasmtime.rs +++ b/src/wasmtime.rs @@ -39,18 +39,14 @@ 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; use std::io::prelude::*; -use std::path::Path; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::exit; -use std::rc::Rc; use wabt; -use wasmtime_jit::{instantiate, ActionOutcome, Compiler, Namespace}; +use wasmtime_jit::{ActionOutcome, Context}; use wasmtime_wast::instantiate_spectest; static LOG_FILENAME_PREFIX: &str = "wasmtime.dbg."; @@ -121,26 +117,17 @@ fn main() { } let isa = isa_builder.finish(settings::Flags::new(flag_builder)); - let mut compiler = Compiler::new(isa); - - let mut namespace = Namespace::new(); - let global_exports = Rc::new(RefCell::new(HashMap::new())); + let mut context = Context::with_isa(isa); // Make spectest available by default. - namespace.instance( - Some("spectest"), + context.instance( + Some("spectest".to_owned()), instantiate_spectest().expect("instantiating spectest"), ); for filename in &args.arg_file { let path = Path::new(&filename); - match handle_module( - &mut compiler, - &mut namespace, - Rc::clone(&global_exports), - &args, - path, - ) { + match handle_module(&mut context, &args, path) { Ok(()) => {} Err(message) => { let name = path.as_os_str().to_string_lossy(); @@ -151,13 +138,7 @@ fn main() { } } -fn handle_module( - compiler: &mut Compiler, - namespace: &mut Namespace, - global_exports: Rc>>>, - args: &Args, - path: &Path, -) -> Result<(), String> { +fn handle_module(context: &mut Context, args: &Args, path: &Path) -> Result<(), String> { let mut data = read_to_end(path.to_path_buf()).map_err(|err| String::from(err.description()))?; @@ -167,16 +148,14 @@ fn handle_module( } // Create a new `Instance` by compiling and instantiating a wasm module. - 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); + let index = context + .instantiate_module(None, &data) + .map_err(|e| e.to_string())?; // If a function to invoke was given, invoke it. if let Some(ref f) = args.flag_invoke { - match namespace - .invoke(compiler, index, f, &[]) + match context + .invoke_indexed(index, f, &[]) .map_err(|e| e.to_string())? { ActionOutcome::Returned { .. } => {}