Factor WasmNamespace out of lib/wast and into lib/execute as Namespace.

This makes it usable for things other than just wast tests.
This commit is contained in:
Dan Gohman
2018-12-24 21:40:26 -08:00
parent 71c0142cd4
commit a5a23d8c4a
5 changed files with 253 additions and 144 deletions

View File

@@ -25,6 +25,7 @@
#![cfg_attr(not(feature = "std"), feature(alloc))] #![cfg_attr(not(feature = "std"), feature(alloc))]
extern crate cranelift_codegen; extern crate cranelift_codegen;
#[macro_use]
extern crate cranelift_entity; extern crate cranelift_entity;
extern crate cranelift_frontend; extern crate cranelift_frontend;
extern crate cranelift_wasm; extern crate cranelift_wasm;
@@ -43,6 +44,7 @@ mod action;
mod instance_plus; mod instance_plus;
mod jit_code; mod jit_code;
mod link; mod link;
mod namespace;
mod resolver; mod resolver;
mod target_tunables; mod target_tunables;
mod trampoline_park; mod trampoline_park;
@@ -51,6 +53,7 @@ pub use action::{ActionError, ActionOutcome, RuntimeValue};
pub use instance_plus::InstancePlus; pub use instance_plus::InstancePlus;
pub use jit_code::JITCode; pub use jit_code::JITCode;
pub use link::link_module; pub use link::link_module;
pub use namespace::{InstancePlusIndex, Namespace};
pub use resolver::{NullResolver, Resolver}; pub use resolver::{NullResolver, Resolver};
pub use target_tunables::target_tunables; pub use target_tunables::target_tunables;

View File

@@ -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<String, InstancePlusIndex>,
/// The instances, available by index.
instances: PrimaryMap<InstancePlusIndex, InstancePlus>,
}
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<ActionOutcome, ActionError> {
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<RuntimeValue, ActionError> {
self.instances[index].get(&field_name)
}
}
impl Resolver for Namespace {
fn resolve(&mut self, instance: &str, field: &str) -> Option<Export> {
if let Some(index) = self.names.get(instance) {
self.instances[*index].instance.lookup(field)
} else {
None
}
}
}

View File

@@ -23,9 +23,8 @@
)] )]
extern crate cranelift_codegen; extern crate cranelift_codegen;
extern crate cranelift_wasm;
#[macro_use]
extern crate cranelift_entity; extern crate cranelift_entity;
extern crate cranelift_wasm;
extern crate failure; extern crate failure;
#[macro_use] #[macro_use]
extern crate failure_derive; extern crate failure_derive;
@@ -39,4 +38,5 @@ extern crate wasmtime_runtime;
mod spectest; mod spectest;
mod wast; mod wast;
pub use spectest::instantiate_spectest;
pub use wast::{WastContext, WastError}; pub use wast::{WastContext, WastError};

View File

@@ -1,14 +1,13 @@
use cranelift_codegen::isa; use cranelift_codegen::isa;
use cranelift_entity::PrimaryMap;
use spectest::instantiate_spectest; use spectest::instantiate_spectest;
use std::collections::HashMap;
use std::io::Read; use std::io::Read;
use std::path::Path; use std::path::Path;
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 wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig};
use wasmtime_execute::{ActionError, ActionOutcome, InstancePlus, JITCode, Resolver, RuntimeValue}; use wasmtime_execute::{
use wasmtime_runtime::Export; ActionError, ActionOutcome, InstancePlus, InstancePlusIndex, JITCode, Namespace, RuntimeValue,
};
/// Translate from a script::Value to a RuntimeValue. /// Translate from a script::Value to a RuntimeValue.
fn runtime_value(v: Value) -> 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)] #[derive(Fail, Debug)]
pub struct UnknownModule { pub struct UnknownInstance {
module: Option<String>, instance: Option<String>,
} }
impl fmt::Display for UnknownModule { impl fmt::Display for UnknownInstance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.module { match self.instance {
None => write!(f, "no default module present"), None => write!(f, "no default instance present"),
Some(ref name) => write!(f, "no module {} present", name), Some(ref name) => write!(f, "no instance {} present", name),
} }
} }
} }
@@ -40,8 +39,8 @@ impl fmt::Display for UnknownModule {
pub enum WastError { pub enum WastError {
/// An assert command was not satisfied. /// An assert command was not satisfied.
Assert(String), Assert(String),
/// An unknown module name was used. /// An unknown instance name was used.
Module(UnknownModule), Instance(UnknownInstance),
/// An error occured while performing an action. /// An error occured while performing an action.
Action(ActionError), Action(ActionError),
/// An action trapped. /// An action trapped.
@@ -56,7 +55,7 @@ impl fmt::Display for WastError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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::Module(ref error) => error.fmt(f), WastError::Instance(ref error) => error.fmt(f),
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),
@@ -74,41 +73,12 @@ pub struct WastFileError {
error: WastError, 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<String, InstancePlusIndex>,
instances: PrimaryMap<InstancePlusIndex, InstancePlus>,
}
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<Export> {
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 /// 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. /// A namespace of wasm modules, keyed by an optional name.
current: Option<InstancePlusIndex>, current: Option<InstancePlusIndex>,
namespace: WasmNamespace, namespace: Namespace,
jit_code: JITCode, jit_code: JITCode,
} }
@@ -117,7 +87,7 @@ impl WastContext {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
current: None, current: None,
namespace: WasmNamespace::new(), namespace: Namespace::new(),
jit_code: JITCode::new(), jit_code: JITCode::new(),
} }
} }
@@ -153,17 +123,22 @@ impl WastContext {
InstancePlus::new(&mut self.jit_code, isa, &data, &mut self.namespace) InstancePlus::new(&mut self.jit_code, isa, &data, &mut self.namespace)
} }
fn get_instance(&mut self, module: &Option<String>) -> Result<InstancePlusIndex, WastError> { fn get_index(
let index = *if let Some(name) = module { &mut self,
self.namespace.names.get_mut(name).ok_or_else(|| { instance_name: &Option<String>,
WastError::Module(UnknownModule { ) -> Result<InstancePlusIndex, WastError> {
module: Some(name.to_owned()), 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 { } else {
self.current self.current
.as_mut() .as_mut()
.ok_or_else(|| WastError::Module(UnknownModule { module: None })) .ok_or_else(|| WastError::Instance(UnknownInstance { instance: None }))
}?; }?;
Ok(index) Ok(index)
@@ -172,58 +147,11 @@ 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<(), ActionError> { pub fn register_spectest(&mut self) -> Result<(), ActionError> {
let instance = instantiate_spectest()?; let instance = instantiate_spectest()?;
let index = self.namespace.instances.push(instance); self.namespace.instance(Some("spectest"), instance);
self.register("spectest".to_owned(), index);
Ok(()) Ok(())
} }
/// Define a module and register it. /// Perform the action portion of a command.
pub fn module(
&mut self,
isa: &isa::TargetIsa,
name: Option<String>,
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<String>,
field: &str,
args: &[Value],
) -> Result<ActionOutcome, WastError> {
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<String>, field: &str) -> Result<RuntimeValue, WastError> {
let index = self.get_instance(&module)?;
self.namespace.instances[index]
.get(&field)
.map_err(WastError::Action)
}
fn perform_action( fn perform_action(
&mut self, &mut self,
isa: &isa::TargetIsa, isa: &isa::TargetIsa,
@@ -231,16 +159,82 @@ impl WastContext {
) -> Result<ActionOutcome, WastError> { ) -> Result<ActionOutcome, WastError> {
match action { match action {
Action::Invoke { Action::Invoke {
module, module: instance_name,
field, field,
args, args,
} => self.invoke(isa, module, &field, &args), } => self.invoke(isa, instance_name, &field, &args),
Action::Get { module, field } => { Action::Get {
let value = self.get(module, &field)?; module: instance_name,
Ok(ActionOutcome::Returned { field,
values: vec![value], } => self.get(instance_name, &field),
}) }
} }
/// Define a module and register it.
fn module(
&mut self,
isa: &isa::TargetIsa,
instance_name: Option<String>,
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<String>, 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<String>,
field: &str,
args: &[Value],
) -> Result<ActionOutcome, WastError> {
let value_args = args
.iter()
.map(|arg| runtime_value(*arg))
.collect::<Vec<_>>();
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<String>,
field: &str,
) -> Result<ActionOutcome, WastError> {
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") { while let Some(Command { kind, line }) = parser.next().expect("parser") {
match kind { match kind {
CommandKind::Module { module, name } => { CommandKind::Module {
self.module(isa, name, module) module: instance_name,
name,
} => {
self.module(isa, name, instance_name)
.map_err(|error| WastFileError { .map_err(|error| WastFileError {
filename: filename.to_string(), filename: filename.to_string(),
line, line,
@@ -264,29 +261,21 @@ impl WastContext {
})?; })?;
} }
CommandKind::Register { name, as_name } => { CommandKind::Register { name, as_name } => {
let index = self.get_instance(&name).map_err(|error| WastFileError { self.register(name, as_name)
filename: filename.to_string(), .map_err(|error| WastFileError {
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 {
filename: filename.to_string(), filename: filename.to_string(),
line, 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 } => { CommandKind::AssertReturn { action, expected } => {
match self match self
.perform_action(isa, action) .perform_action(isa, action)

View File

@@ -34,6 +34,7 @@ extern crate cranelift_codegen;
extern crate cranelift_native; extern crate cranelift_native;
extern crate docopt; extern crate docopt;
extern crate wasmtime_execute; extern crate wasmtime_execute;
extern crate wasmtime_wast;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate file_per_thread_logger; extern crate file_per_thread_logger;
@@ -51,7 +52,8 @@ use std::io::prelude::*;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::exit; 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."; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg.";
@@ -122,9 +124,19 @@ fn main() {
let isa = isa_builder.finish(settings::Flags::new(flag_builder)); 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 { for filename in &args.arg_file {
let path = Path::new(&filename); let path = Path::new(&filename);
match handle_module(&args, path, &*isa) { match handle_module(&mut jit_code, &mut namespace, &args, path, &*isa) {
Ok(()) => {} Ok(()) => {}
Err(message) => { Err(message) => {
let name = path.as_os_str().to_string_lossy(); 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 = 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()))?;
// 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']) { if !data.starts_with(&[b'\0', b'a', b's', b'm']) {
data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?; 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 { if let Some(ref f) = args.flag_invoke {
match instance_plus match namespace
.invoke(&mut jit_code, isa, &f, &[]) .invoke(jit_code, isa, index, &f, &[])
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
{ {
ActionOutcome::Returned { .. } => {} ActionOutcome::Returned { .. } => {}