Create a Context class to group together various state objects.

This commit is contained in:
Dan Gohman
2019-01-31 14:43:32 -08:00
parent 4675948c2a
commit 8b140cc748
8 changed files with 235 additions and 127 deletions

View File

@@ -23,6 +23,7 @@ failure = { version = "0.1.3", default-features = false }
failure_derive = { 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 } target-lexicon = { version = "0.2.0", default-features = false }
hashbrown = { version = "0.1.8", optional = true } hashbrown = { version = "0.1.8", optional = true }
wasmparser = "0.23.0"
[features] [features]
default = ["std"] default = ["std"]

182
lib/jit/src/context.rs Normal file
View File

@@ -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<Compiler>,
global_exports: Rc<RefCell<HashMap<String, Option<wasmtime_runtime::Export>>>>,
}
impl Context {
/// Construct a new instance of `Context`.
pub fn new(compiler: Box<Compiler>) -> 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<TargetIsa>) -> 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<Instance, SetupError> {
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<InstanceIndex, UnknownInstance> {
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<String>,
data: &[u8],
) -> Result<InstanceIndex, ActionError> {
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<String>, 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<ActionOutcome, ContextError> {
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<ActionOutcome, ActionError> {
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<ActionOutcome, ContextError> {
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<ActionOutcome, ActionError> {
self.namespace
.get(index, field)
.map(|value| ActionOutcome::Returned {
values: vec![value],
})
}
}

View File

@@ -44,6 +44,7 @@ extern crate failure_derive;
mod action; mod action;
mod code_memory; mod code_memory;
mod compiler; mod compiler;
mod context;
mod instantiate; mod instantiate;
mod link; mod link;
mod namespace; mod namespace;
@@ -52,6 +53,7 @@ mod target_tunables;
pub use crate::action::{ActionError, ActionOutcome, RuntimeValue}; pub use crate::action::{ActionError, ActionOutcome, RuntimeValue};
pub use crate::compiler::Compiler; pub use crate::compiler::Compiler;
pub use crate::context::{Context, ContextError, UnknownInstance};
pub use crate::instantiate::{instantiate, CompiledModule, SetupError}; pub use crate::instantiate::{instantiate, CompiledModule, SetupError};
pub use crate::link::link_module; pub use crate::link::link_module;
pub use crate::namespace::{InstanceIndex, Namespace}; pub use crate::namespace::{InstanceIndex, Namespace};

View File

@@ -39,22 +39,22 @@ impl Namespace {
/// Install a new `Instance` in this `Namespace`, optionally with the /// Install a new `Instance` in this `Namespace`, optionally with the
/// given name, and return its index. /// 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<String>, instance: Instance) -> InstanceIndex {
let index = self.instances.push(instance); let index = self.instances.push(instance);
if let Some(instance_name) = instance_name { if let Some(instance_name) = instance_name {
self.names.insert(instance_name.into(), index); self.names.insert(instance_name, index);
} }
index index
} }
/// Get the instance index registered with the given `instance_name`. /// Get the instance index registered with the given `instance_name`.
pub fn get_instance_index(&mut self, instance_name: &str) -> Option<&mut InstanceIndex> { pub fn get_instance_index(&mut self, instance_name: &str) -> Option<InstanceIndex> {
self.names.get_mut(instance_name) self.names.get_mut(instance_name).cloned()
} }
/// Register an instance with a given name. /// Register an additional name for an existing registered instance.
pub fn register(&mut self, name: String, index: InstanceIndex) { pub fn alias_for_indexed(&mut self, existing_index: InstanceIndex, new_name: String) {
self.names.insert(name, index); self.names.insert(new_name, existing_index);
} }
/// Invoke an exported function from an instance. /// Invoke an exported function from an instance.

View File

@@ -760,8 +760,6 @@ impl Instance {
pub fn lookup(&mut self, field: &str) -> Option<Export> { pub fn lookup(&mut self, field: &str) -> Option<Export> {
let export = if let Some(export) = self.module.exports.get(field) { let export = if let Some(export) = self.module.exports.get(field) {
export.clone() export.clone()
} else if let Some(export) = self.mmap_field.contents().lookup_global_export(field) {
return Some(export.clone());
} else { } else {
return None; return None;
}; };

View File

@@ -23,7 +23,6 @@ wabt = "0.7"
target-lexicon = "0.2.0" target-lexicon = "0.2.0"
failure = { version = "0.1.3", default-features = false } failure = { version = "0.1.3", default-features = false }
failure_derive = { version = "0.1.3", default-features = false } failure_derive = { version = "0.1.3", default-features = false }
wasmparser = "0.23.0"
[badges] [badges]
maintenance = { status = "experimental" } maintenance = { status = "experimental" }

View File

@@ -1,15 +1,11 @@
use crate::spectest::instantiate_spectest; use crate::spectest::instantiate_spectest;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::Read; use std::io::Read;
use std::path::Path; use std::path::Path;
use std::rc::Rc;
use std::{fmt, fs, io, str}; use std::{fmt, fs, io, str};
use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value}; use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value};
use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig};
use wasmtime_jit::{ use wasmtime_jit::{
instantiate, ActionError, ActionOutcome, Compiler, Instance, InstanceIndex, InstantiationError, ActionError, ActionOutcome, Compiler, Context, InstanceIndex, InstantiationError, RuntimeValue,
Namespace, RuntimeValue, SetupError, UnknownInstance,
}; };
/// Translate from a `script::Value` to a `RuntimeValue`. /// 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<String>,
}
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`. /// Error message used by `WastContext`.
#[derive(Fail, Debug)] #[derive(Fail, Debug)]
pub enum WastError { pub enum WastError {
@@ -44,6 +25,8 @@ pub enum WastError {
Assert(String), Assert(String),
/// An unknown instance name was used. /// An unknown instance name was used.
Instance(UnknownInstance), Instance(UnknownInstance),
/// No default instance has been registered yet.
NoDefaultInstance,
/// An error occured while performing an action. /// An error occured while performing an action.
Action(ActionError), Action(ActionError),
/// An action trapped. /// An action trapped.
@@ -63,6 +46,7 @@ impl fmt::Display for WastError {
match *self { match *self {
WastError::Assert(ref message) => write!(f, "Assert command failed: {}", message), WastError::Assert(ref message) => write!(f, "Assert command failed: {}", message),
WastError::Instance(ref error) => error.fmt(f), WastError::Instance(ref error) => error.fmt(f),
WastError::NoDefaultInstance => write!(f, "no default instance defined yet"),
WastError::Action(ref error) => error.fmt(f), WastError::Action(ref error) => error.fmt(f),
WastError::Trap(ref message) => write!(f, "trap: {}", message), WastError::Trap(ref message) => write!(f, "trap: {}", message),
WastError::Type(ref message) => write!(f, "type error: {}", 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 /// The wast test script language allows modules to be defined and actions
/// to be performed on them. /// to be performed on them.
pub struct WastContext { 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<InstanceIndex>, current: Option<InstanceIndex>,
namespace: Namespace,
compiler: Box<Compiler>, context: Context,
} }
impl WastContext { impl WastContext {
@@ -96,56 +81,23 @@ impl WastContext {
pub fn new(compiler: Box<Compiler>) -> Self { pub fn new(compiler: Box<Compiler>) -> Self {
Self { Self {
current: None, current: None,
namespace: Namespace::new(), context: Context::new(compiler),
compiler,
} }
} }
fn validate(&mut self, data: &[u8]) -> Result<(), String> { fn get_instance_index(
let config = ValidatingParserConfig { &mut self,
operator_config: OperatorValidatorConfig { instance_name: Option<&str>,
enable_threads: false, ) -> Result<InstanceIndex, WastError> {
enable_reference_types: false, let index = if let Some(instance_name) = instance_name {
}, self.context
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<Instance, SetupError> {
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<String>) -> Result<InstanceIndex, WastError> {
let index = *if let Some(instance_name) = instance_name {
self.namespace
.get_instance_index(instance_name) .get_instance_index(instance_name)
.ok_or_else(|| { .map_err(WastError::Instance)
WastError::Instance(UnknownInstance {
instance: Some(instance_name.to_string()),
})
})
} else { } else {
self.current self.current
.as_mut() .as_mut()
.ok_or_else(|| WastError::Instance(UnknownInstance { instance: None })) .cloned()
.ok_or_else(|| WastError::NoDefaultInstance)
}?; }?;
Ok(index) Ok(index)
@@ -154,7 +106,7 @@ impl WastContext {
/// Register "spectest" which is used by the spec testsuite. /// Register "spectest" which is used by the spec testsuite.
pub fn register_spectest(&mut self) -> Result<(), InstantiationError> { pub fn register_spectest(&mut self) -> Result<(), InstantiationError> {
let instance = instantiate_spectest()?; let instance = instantiate_spectest()?;
self.namespace.instance(Some("spectest"), instance); self.context.instance(Some("spectest".to_owned()), instance);
Ok(()) Ok(())
} }
@@ -179,18 +131,17 @@ impl WastContext {
instance_name: Option<String>, instance_name: Option<String>,
module: ModuleBinary, module: ModuleBinary,
) -> Result<(), ActionError> { ) -> Result<(), ActionError> {
let instance = self.instantiate(module).map_err(ActionError::Setup)?;
let index = self let index = self
.namespace .context
.instance(instance_name.as_ref().map(String::as_str), instance); .instantiate_module(instance_name, &module.into_vec())?;
self.current = Some(index); self.current = Some(index);
Ok(()) Ok(())
} }
/// Register an instance to make it available for performing actions. /// Register an instance to make it available for performing actions.
fn register(&mut self, name: Option<String>, as_name: String) -> Result<(), WastError> { fn register(&mut self, name: Option<String>, as_name: String) -> Result<(), WastError> {
let index = self.get_index(&name)?; let index = self.get_instance_index(name.as_ref().map(|x| &**x))?;
self.namespace.register(as_name, index); self.context.alias_for_indexed(index, as_name);
Ok(()) Ok(())
} }
@@ -205,9 +156,9 @@ impl WastContext {
.iter() .iter()
.map(|arg| runtime_value(*arg)) .map(|arg| runtime_value(*arg))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let index = self.get_index(&instance_name)?; let index = self.get_instance_index(instance_name.as_ref().map(|x| &**x))?;
self.namespace self.context
.invoke(&mut *self.compiler, index, field, &value_args) .invoke_indexed(index, field, &value_args)
.map_err(WastError::Action) .map_err(WastError::Action)
} }
@@ -217,14 +168,10 @@ impl WastContext {
instance_name: Option<String>, instance_name: Option<String>,
field: &str, field: &str,
) -> Result<ActionOutcome, WastError> { ) -> Result<ActionOutcome, WastError> {
let index = self.get_index(&instance_name)?; let index = self.get_instance_index(instance_name.as_ref().map(|x| &**x))?;
let value = self self.context
.namespace .get_indexed(index, field)
.get(index, field) .map_err(WastError::Action)
.map_err(WastError::Action)?;
Ok(ActionOutcome::Returned {
values: vec![value],
})
} }
/// Perform the action of a `PerformAction`. /// Perform the action of a `PerformAction`.

View File

@@ -39,18 +39,14 @@ use cranelift_native;
use docopt::Docopt; use docopt::Docopt;
use file_per_thread_logger; use file_per_thread_logger;
use pretty_env_logger; use pretty_env_logger;
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::Path; use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::process::exit; use std::process::exit;
use std::rc::Rc;
use wabt; use wabt;
use wasmtime_jit::{instantiate, ActionOutcome, Compiler, Namespace}; use wasmtime_jit::{ActionOutcome, Context};
use wasmtime_wast::instantiate_spectest; use wasmtime_wast::instantiate_spectest;
static LOG_FILENAME_PREFIX: &str = "wasmtime.dbg."; static LOG_FILENAME_PREFIX: &str = "wasmtime.dbg.";
@@ -121,26 +117,17 @@ fn main() {
} }
let isa = isa_builder.finish(settings::Flags::new(flag_builder)); let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut compiler = Compiler::new(isa); let mut context = Context::with_isa(isa);
let mut namespace = Namespace::new();
let global_exports = Rc::new(RefCell::new(HashMap::new()));
// Make spectest available by default. // Make spectest available by default.
namespace.instance( context.instance(
Some("spectest"), Some("spectest".to_owned()),
instantiate_spectest().expect("instantiating spectest"), instantiate_spectest().expect("instantiating spectest"),
); );
for filename in &args.arg_file { for filename in &args.arg_file {
let path = Path::new(&filename); let path = Path::new(&filename);
match handle_module( match handle_module(&mut context, &args, path) {
&mut compiler,
&mut namespace,
Rc::clone(&global_exports),
&args,
path,
) {
Ok(()) => {} Ok(()) => {}
Err(message) => { Err(message) => {
let name = path.as_os_str().to_string_lossy(); let name = path.as_os_str().to_string_lossy();
@@ -151,13 +138,7 @@ fn main() {
} }
} }
fn handle_module( fn handle_module(context: &mut Context, args: &Args, path: &Path) -> Result<(), String> {
compiler: &mut Compiler,
namespace: &mut Namespace,
global_exports: Rc<RefCell<HashMap<String, Option<wasmtime_runtime::Export>>>>,
args: &Args,
path: &Path,
) -> Result<(), String> {
let mut data = let mut data =
read_to_end(path.to_path_buf()).map_err(|err| String::from(err.description()))?; 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. // Create a new `Instance` by compiling and instantiating a wasm module.
let instance = let index = context
instantiate(compiler, &data, namespace, global_exports).map_err(|e| e.to_string())?; .instantiate_module(None, &data)
.map_err(|e| e.to_string())?;
// Register it in the namespace.
let index = namespace.instance(None, instance);
// If a function to invoke was given, invoke it. // If a function to invoke was given, invoke it.
if let Some(ref f) = args.flag_invoke { if let Some(ref f) = args.flag_invoke {
match namespace match context
.invoke(compiler, index, f, &[]) .invoke_indexed(index, f, &[])
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
{ {
ActionOutcome::Returned { .. } => {} ActionOutcome::Returned { .. } => {}