From a5a23d8c4a6e33b85ff9f3adffeaf7ed44a52914 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 24 Dec 2018 21:40:26 -0800 Subject: [PATCH] Factor WasmNamespace out of lib/wast and into lib/execute as Namespace. This makes it usable for things other than just wast tests. --- lib/execute/src/lib.rs | 3 + lib/execute/src/namespace.rs | 94 +++++++++++++ lib/wast/src/lib.rs | 4 +- lib/wast/src/wast.rs | 253 +++++++++++++++++------------------ src/wasmtime.rs | 43 ++++-- 5 files changed, 253 insertions(+), 144 deletions(-) create mode 100644 lib/execute/src/namespace.rs diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index a2e7edc3d8..39b061c2da 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -25,6 +25,7 @@ #![cfg_attr(not(feature = "std"), feature(alloc))] extern crate cranelift_codegen; +#[macro_use] extern crate cranelift_entity; extern crate cranelift_frontend; extern crate cranelift_wasm; @@ -43,6 +44,7 @@ mod action; mod instance_plus; mod jit_code; mod link; +mod namespace; mod resolver; mod target_tunables; mod trampoline_park; @@ -51,6 +53,7 @@ pub use action::{ActionError, ActionOutcome, RuntimeValue}; pub use instance_plus::InstancePlus; pub use jit_code::JITCode; pub use link::link_module; +pub use namespace::{InstancePlusIndex, Namespace}; pub use resolver::{NullResolver, Resolver}; pub use target_tunables::target_tunables; diff --git a/lib/execute/src/namespace.rs b/lib/execute/src/namespace.rs new file mode 100644 index 0000000000..a30eda4c9e --- /dev/null +++ b/lib/execute/src/namespace.rs @@ -0,0 +1,94 @@ +//! The core WebAssembly spec does not specify how imports are to be resolved +//! to exports. This file provides one possible way to manage multiple instances +//! and resolve imports to exports among them. + +use action::{ActionError, ActionOutcome, RuntimeValue}; +use cranelift_codegen::isa; +use cranelift_entity::PrimaryMap; +use instance_plus::InstancePlus; +use jit_code::JITCode; +use resolver::Resolver; +use std::collections::HashMap; +use wasmtime_runtime::Export; + +/// An opaque reference to an `InstancePlus`. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct InstancePlusIndex(u32); +entity_impl!(InstancePlusIndex, "instance"); + +/// A namespace containing instances keyed by name. +/// +/// Note that `Namespace` implements the `Resolver` trait, so it can resolve +/// imports using defined exports. +pub struct Namespace { + /// Mapping from identifiers to indices in `self.instances`. + names: HashMap, + + /// The instances, available by index. + instances: PrimaryMap, +} + +impl Namespace { + /// Construct a new `Namespace`. + pub fn new() -> Self { + Self { + names: HashMap::new(), + instances: PrimaryMap::new(), + } + } + + /// Install a new `InstancePlus` in this `Namespace`, optionally with the + /// given name, and return its index. + pub fn instance( + &mut self, + instance_name: Option<&str>, + instance: InstancePlus, + ) -> InstancePlusIndex { + let index = self.instances.push(instance); + if let Some(instance_name) = instance_name { + self.names.insert(instance_name.into(), index); + } + index + } + + /// Get the instance index registered with the given `instance_name`. + pub fn get_instance_index(&mut self, instance_name: &str) -> Option<&mut InstancePlusIndex> { + self.names.get_mut(instance_name) + } + + /// Register an instance with a given name. + pub fn register(&mut self, name: String, index: InstancePlusIndex) { + self.names.insert(name, index); + } + + /// Invoke an exported function from an instance. + pub fn invoke( + &mut self, + jit_code: &mut JITCode, + isa: &isa::TargetIsa, + index: InstancePlusIndex, + field_name: &str, + args: &[RuntimeValue], + ) -> Result { + self.instances[index].invoke(jit_code, isa, &field_name, &args) + } + + /// Get the value of an exported global from an instance. + pub fn get( + &mut self, + index: InstancePlusIndex, + field_name: &str, + ) -> Result { + self.instances[index].get(&field_name) + } +} + +impl Resolver for Namespace { + fn resolve(&mut self, instance: &str, field: &str) -> Option { + if let Some(index) = self.names.get(instance) { + self.instances[*index].instance.lookup(field) + } else { + None + } + } +} diff --git a/lib/wast/src/lib.rs b/lib/wast/src/lib.rs index d986b30764..50f68500c9 100644 --- a/lib/wast/src/lib.rs +++ b/lib/wast/src/lib.rs @@ -23,9 +23,8 @@ )] extern crate cranelift_codegen; -extern crate cranelift_wasm; -#[macro_use] extern crate cranelift_entity; +extern crate cranelift_wasm; extern crate failure; #[macro_use] extern crate failure_derive; @@ -39,4 +38,5 @@ extern crate wasmtime_runtime; mod spectest; mod wast; +pub use spectest::instantiate_spectest; pub use wast::{WastContext, WastError}; diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs index b044a8fcb9..35ce76d505 100644 --- a/lib/wast/src/wast.rs +++ b/lib/wast/src/wast.rs @@ -1,14 +1,13 @@ use cranelift_codegen::isa; -use cranelift_entity::PrimaryMap; use spectest::instantiate_spectest; -use std::collections::HashMap; use std::io::Read; use std::path::Path; use std::{fmt, fs, io, str}; use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value}; use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig}; -use wasmtime_execute::{ActionError, ActionOutcome, InstancePlus, JITCode, Resolver, RuntimeValue}; -use wasmtime_runtime::Export; +use wasmtime_execute::{ + ActionError, ActionOutcome, InstancePlus, InstancePlusIndex, JITCode, Namespace, RuntimeValue, +}; /// Translate from a script::Value to a RuntimeValue. fn runtime_value(v: Value) -> RuntimeValue { @@ -20,17 +19,17 @@ fn runtime_value(v: Value) -> RuntimeValue { } } -/// Indicates an unknown module was specified. +/// Indicates an unknown instance was specified. #[derive(Fail, Debug)] -pub struct UnknownModule { - module: Option, +pub struct UnknownInstance { + instance: Option, } -impl fmt::Display for UnknownModule { +impl fmt::Display for UnknownInstance { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.module { - None => write!(f, "no default module present"), - Some(ref name) => write!(f, "no module {} present", name), + match self.instance { + None => write!(f, "no default instance present"), + Some(ref name) => write!(f, "no instance {} present", name), } } } @@ -40,8 +39,8 @@ impl fmt::Display for UnknownModule { pub enum WastError { /// An assert command was not satisfied. Assert(String), - /// An unknown module name was used. - Module(UnknownModule), + /// An unknown instance name was used. + Instance(UnknownInstance), /// An error occured while performing an action. Action(ActionError), /// An action trapped. @@ -56,7 +55,7 @@ impl fmt::Display for WastError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { WastError::Assert(ref message) => write!(f, "Assert command failed: {}", message), - WastError::Module(ref error) => error.fmt(f), + WastError::Instance(ref error) => error.fmt(f), WastError::Action(ref error) => error.fmt(f), WastError::Trap(ref message) => write!(f, "trap: {}", message), WastError::Type(ref message) => write!(f, "type error: {}", message), @@ -74,41 +73,12 @@ pub struct WastFileError { error: WastError, } -/// An opaque reference to an `InstancePlus`. -#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct InstancePlusIndex(u32); -entity_impl!(InstancePlusIndex, "instance"); - -struct WasmNamespace { - names: HashMap, - instances: PrimaryMap, -} - -impl WasmNamespace { - fn new() -> Self { - Self { - names: HashMap::new(), - instances: PrimaryMap::new(), - } - } -} - -impl Resolver for WasmNamespace { - fn resolve(&mut self, module: &str, field: &str) -> Option { - if let Some(index) = self.names.get(module) { - self.instances[*index].instance.lookup(field) - } else { - None - } - } -} - /// 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. current: Option, - namespace: WasmNamespace, + namespace: Namespace, jit_code: JITCode, } @@ -117,7 +87,7 @@ impl WastContext { pub fn new() -> Self { Self { current: None, - namespace: WasmNamespace::new(), + namespace: Namespace::new(), jit_code: JITCode::new(), } } @@ -153,17 +123,22 @@ impl WastContext { InstancePlus::new(&mut self.jit_code, isa, &data, &mut self.namespace) } - fn get_instance(&mut self, module: &Option) -> Result { - let index = *if let Some(name) = module { - self.namespace.names.get_mut(name).ok_or_else(|| { - WastError::Module(UnknownModule { - module: Some(name.to_owned()), + fn get_index( + &mut self, + instance_name: &Option, + ) -> Result { + let index = *if let Some(instance_name) = instance_name { + self.namespace + .get_instance_index(instance_name) + .ok_or_else(|| { + WastError::Instance(UnknownInstance { + instance: Some(instance_name.to_string()), + }) }) - }) } else { self.current .as_mut() - .ok_or_else(|| WastError::Module(UnknownModule { module: None })) + .ok_or_else(|| WastError::Instance(UnknownInstance { instance: None })) }?; Ok(index) @@ -172,58 +147,11 @@ impl WastContext { /// Register "spectest" which is used by the spec testsuite. pub fn register_spectest(&mut self) -> Result<(), ActionError> { let instance = instantiate_spectest()?; - let index = self.namespace.instances.push(instance); - self.register("spectest".to_owned(), index); + self.namespace.instance(Some("spectest"), instance); Ok(()) } - /// Define a module and register it. - pub fn module( - &mut self, - isa: &isa::TargetIsa, - name: Option, - module: ModuleBinary, - ) -> Result<(), ActionError> { - let instance = self.instantiate(isa, module)?; - let index = self.namespace.instances.push(instance); - if let Some(name) = name { - self.register(name, index); - } - self.current = Some(index); - Ok(()) - } - - /// Register a module to make it available for performing actions. - pub fn register(&mut self, name: String, index: InstancePlusIndex) { - self.namespace.names.insert(name, index); - } - - /// Invoke an exported function from a defined module. - pub fn invoke( - &mut self, - isa: &isa::TargetIsa, - module: Option, - field: &str, - args: &[Value], - ) -> Result { - let mut value_args = Vec::with_capacity(args.len()); - for arg in args { - value_args.push(runtime_value(*arg)); - } - let index = self.get_instance(&module)?; - self.namespace.instances[index] - .invoke(&mut self.jit_code, isa, &field, &value_args) - .map_err(WastError::Action) - } - - /// Get the value of an exported global from a defined module. - pub fn get(&mut self, module: Option, field: &str) -> Result { - let index = self.get_instance(&module)?; - self.namespace.instances[index] - .get(&field) - .map_err(WastError::Action) - } - + /// Perform the action portion of a command. fn perform_action( &mut self, isa: &isa::TargetIsa, @@ -231,16 +159,82 @@ impl WastContext { ) -> Result { match action { Action::Invoke { - module, + module: instance_name, field, args, - } => self.invoke(isa, module, &field, &args), - Action::Get { module, field } => { - let value = self.get(module, &field)?; - Ok(ActionOutcome::Returned { - values: vec![value], - }) - } + } => self.invoke(isa, instance_name, &field, &args), + Action::Get { + module: instance_name, + field, + } => self.get(instance_name, &field), + } + } + + /// Define a module and register it. + fn module( + &mut self, + isa: &isa::TargetIsa, + instance_name: Option, + module: ModuleBinary, + ) -> Result<(), ActionError> { + let instance = self.instantiate(isa, module)?; + let index = self + .namespace + .instance(instance_name.as_ref().map(String::as_str), instance); + 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); + Ok(()) + } + + /// Invoke an exported function from an instance. + fn invoke( + &mut self, + isa: &isa::TargetIsa, + instance_name: Option, + field: &str, + args: &[Value], + ) -> Result { + let value_args = args + .iter() + .map(|arg| runtime_value(*arg)) + .collect::>(); + let index = self.get_index(&instance_name)?; + self.namespace + .invoke(&mut self.jit_code, isa, index, &field, &value_args) + .map_err(WastError::Action) + } + + /// Get the value of an exported global from an instance. + fn get( + &mut self, + 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], + }) + } + + /// Perform the action of a `PerformAction`. + fn perform_action_command( + &mut self, + isa: &isa::TargetIsa, + action: Action, + ) -> Result<(), WastError> { + match self.perform_action(isa, action)? { + ActionOutcome::Returned { .. } => Ok(()), + ActionOutcome::Trapped { message } => Err(WastError::Trap(message)), } } @@ -255,8 +249,11 @@ impl WastContext { while let Some(Command { kind, line }) = parser.next().expect("parser") { match kind { - CommandKind::Module { module, name } => { - self.module(isa, name, module) + CommandKind::Module { + module: instance_name, + name, + } => { + self.module(isa, name, instance_name) .map_err(|error| WastFileError { filename: filename.to_string(), line, @@ -264,29 +261,21 @@ impl WastContext { })?; } CommandKind::Register { name, as_name } => { - let index = self.get_instance(&name).map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })?; - self.register(as_name, index); - } - CommandKind::PerformAction(action) => match self - .perform_action(isa, action) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { - ActionOutcome::Returned { .. } => {} - ActionOutcome::Trapped { message } => { - return Err(WastFileError { + self.register(name, as_name) + .map_err(|error| WastFileError { filename: filename.to_string(), line, - error: WastError::Trap(message), - }); - } - }, + error, + })?; + } + CommandKind::PerformAction(action) => { + self.perform_action_command(isa, action) + .map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })?; + } CommandKind::AssertReturn { action, expected } => { match self .perform_action(isa, action) diff --git a/src/wasmtime.rs b/src/wasmtime.rs index e4272ed575..d9ded12565 100644 --- a/src/wasmtime.rs +++ b/src/wasmtime.rs @@ -34,6 +34,7 @@ extern crate cranelift_codegen; extern crate cranelift_native; extern crate docopt; extern crate wasmtime_execute; +extern crate wasmtime_wast; #[macro_use] extern crate serde_derive; extern crate file_per_thread_logger; @@ -51,7 +52,8 @@ use std::io::prelude::*; use std::path::Path; use std::path::PathBuf; use std::process::exit; -use wasmtime_execute::{ActionOutcome, InstancePlus, JITCode, NullResolver}; +use wasmtime_execute::{ActionOutcome, InstancePlus, JITCode, Namespace}; +use wasmtime_wast::instantiate_spectest; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; @@ -122,9 +124,19 @@ fn main() { let isa = isa_builder.finish(settings::Flags::new(flag_builder)); + let mut namespace = Namespace::new(); + + // Make spectest available by default. + namespace.instance( + Some("spectest"), + instantiate_spectest().expect("instantiating spectest"), + ); + + let mut jit_code = JITCode::new(); + for filename in &args.arg_file { let path = Path::new(&filename); - match handle_module(&args, path, &*isa) { + match handle_module(&mut jit_code, &mut namespace, &args, path, &*isa) { Ok(()) => {} Err(message) => { let name = path.as_os_str().to_string_lossy(); @@ -135,21 +147,32 @@ fn main() { } } -fn handle_module(args: &Args, path: &Path, isa: &TargetIsa) -> Result<(), String> { +fn handle_module( + jit_code: &mut JITCode, + namespace: &mut Namespace, + args: &Args, + path: &Path, + isa: &TargetIsa, +) -> Result<(), String> { let mut data = read_to_end(path.to_path_buf()).map_err(|err| String::from(err.description()))?; - // if data is using wat-format, first convert data to wasm + + // If data is using wat-format, first convert data to wasm. if !data.starts_with(&[b'\0', b'a', b's', b'm']) { data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?; } - let mut resolver = NullResolver {}; - let mut jit_code = JITCode::new(); - let mut instance_plus = - InstancePlus::new(&mut jit_code, isa, &data, &mut resolver).map_err(|e| e.to_string())?; + // Create a new `InstancePlus` by compiling and instantiating a wasm module. + let instance_plus = + InstancePlus::new(jit_code, isa, &data, namespace).map_err(|e| e.to_string())?; + + // Register it in the namespace. + let index = namespace.instance(None, instance_plus); + + // If a function to invoke was given, invoke it. if let Some(ref f) = args.flag_invoke { - match instance_plus - .invoke(&mut jit_code, isa, &f, &[]) + match namespace + .invoke(jit_code, isa, index, &f, &[]) .map_err(|e| e.to_string())? { ActionOutcome::Returned { .. } => {}