Switch from wabt crate to wast (#434)
* Switch lightbeam from `wabt` to `wast` Switch from a C++-based `*.wat` parser to a Rust-based parser * Remove unneeded `wabt` dev-dependency from wasmtime-api * Rewrite `wasmtime-wast` crate with `wast-parser` This commit moves the `wasmtime-wast` crate off the `wabt` crate on to the `wast-parser` crate which is a Rust implementation of a `*.wast` and `*.wat` parser. The intention here is to continue to reduce the amount of C++ required to build wasmtime! * Use new `wat` and `wast` crate names
This commit is contained in:
committed by
Dan Gohman
parent
ebef2c6b57
commit
9947bc5209
@@ -33,7 +33,7 @@ failure = "0.1"
|
|||||||
target-lexicon = { version = "0.8.1", default-features = false }
|
target-lexicon = { version = "0.8.1", default-features = false }
|
||||||
pretty_env_logger = "0.3.0"
|
pretty_env_logger = "0.3.0"
|
||||||
file-per-thread-logger = "0.1.1"
|
file-per-thread-logger = "0.1.1"
|
||||||
wabt = "0.9.2"
|
wat = "1.0.2"
|
||||||
libc = "0.2.60"
|
libc = "0.2.60"
|
||||||
rayon = "1.1"
|
rayon = "1.1"
|
||||||
wasm-webidl-bindings = "0.5"
|
wasm-webidl-bindings = "0.5"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ typemap = "0.3"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.2"
|
lazy_static = "1.2"
|
||||||
wabt = "0.9.2"
|
wat = "1.0.2"
|
||||||
quickcheck = "0.9.0"
|
quickcheck = "0.9.0"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ extern crate lazy_static;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate quickcheck;
|
extern crate quickcheck;
|
||||||
#[cfg(test)]
|
|
||||||
extern crate wabt;
|
|
||||||
// Just so we can implement `Signature` for `cranelift_codegen::ir::Signature`
|
// Just so we can implement `Signature` for `cranelift_codegen::ir::Signature`
|
||||||
extern crate cranelift_codegen;
|
extern crate cranelift_codegen;
|
||||||
extern crate multi_mut;
|
extern crate multi_mut;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use super::{module::ExecutionError, translate, ExecutableModule};
|
use super::{module::ExecutionError, translate, ExecutableModule};
|
||||||
use wabt;
|
|
||||||
|
|
||||||
fn translate_wat(wat: &str) -> ExecutableModule {
|
fn translate_wat(wat: &str) -> ExecutableModule {
|
||||||
let wasm = wabt::wat2wasm(wat).unwrap();
|
let wasm = wat::parse_str(wat).unwrap();
|
||||||
let compiled = translate(&wasm).unwrap();
|
let compiled = translate(&wasm).unwrap();
|
||||||
compiled
|
compiled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,8 @@ use std::collections::HashMap;
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Component;
|
use std::path::Component;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use wabt;
|
|
||||||
use wasi_common::preopen_dir;
|
use wasi_common::preopen_dir;
|
||||||
use wasmtime::pick_compilation_strategy;
|
use wasmtime::pick_compilation_strategy;
|
||||||
use wasmtime_api::{Config, Engine, HostRef, Instance, Module, Store};
|
use wasmtime_api::{Config, Engine, HostRef, Instance, Module, Store};
|
||||||
@@ -115,19 +114,6 @@ struct Args {
|
|||||||
flag_wasi_c: bool,
|
flag_wasi_c: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_wasm(path: PathBuf) -> Result<Vec<u8>, Error> {
|
|
||||||
let data = std::fs::read(&path)
|
|
||||||
.with_context(|_| format!("failed to read file: {}", path.display()))?;
|
|
||||||
|
|
||||||
// If data is a wasm binary, use that. If it's using wat format, convert it
|
|
||||||
// to a wasm binary with wat2wasm.
|
|
||||||
Ok(if data.starts_with(&[b'\0', b'a', b's', b'm']) {
|
|
||||||
data
|
|
||||||
} else {
|
|
||||||
wabt::wat2wasm(data)?
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_preopen_dirs(flag_dir: &[String], flag_mapdir: &[String]) -> Vec<(String, File)> {
|
fn compute_preopen_dirs(flag_dir: &[String], flag_mapdir: &[String]) -> Vec<(String, File)> {
|
||||||
let mut preopen_dirs = Vec::new();
|
let mut preopen_dirs = Vec::new();
|
||||||
|
|
||||||
@@ -353,8 +339,8 @@ fn instantiate_module(
|
|||||||
module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>,
|
module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> Result<(HostRef<Instance>, HostRef<Module>, Vec<u8>), Error> {
|
) -> Result<(HostRef<Instance>, HostRef<Module>, Vec<u8>), Error> {
|
||||||
// Read the wasm module binary.
|
// Read the wasm module binary either as `*.wat` or a raw binary
|
||||||
let data = read_wasm(path.to_path_buf())?;
|
let data = wat::parse_file(path.to_path_buf())?;
|
||||||
|
|
||||||
let module = HostRef::new(Module::new(store.clone(), &data)?);
|
let module = HostRef::new(Module::new(store.clone(), &data)?);
|
||||||
|
|
||||||
|
|||||||
@@ -5,32 +5,16 @@ use core::cell::RefCell;
|
|||||||
use cranelift_codegen::settings;
|
use cranelift_codegen::settings;
|
||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use wabt;
|
|
||||||
use wasmtime_jit::{instantiate, CompilationStrategy, Compiler, NullResolver};
|
use wasmtime_jit::{instantiate, CompilationStrategy, Compiler, NullResolver};
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat";
|
const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat";
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn read_to_end(path: PathBuf) -> Result<Vec<u8>, io::Error> {
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
|
||||||
let mut file = File::open(path)?;
|
|
||||||
file.read_to_end(&mut buf)?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple test reading a wasm-file and translating to binary representation.
|
/// Simple test reading a wasm-file and translating to binary representation.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_environ_translate() {
|
fn test_environ_translate() {
|
||||||
let path = PathBuf::from(PATH_MODULE_RS2WASM_ADD_FUNC);
|
let path = PathBuf::from(PATH_MODULE_RS2WASM_ADD_FUNC);
|
||||||
let wat_data = read_to_end(path).unwrap();
|
let data = wat::parse_file(path).expect("expecting valid wat-file");
|
||||||
assert!(wat_data.len() > 0);
|
|
||||||
|
|
||||||
let data = wabt::wat2wasm(wat_data).expect("expecting valid wat-file");
|
|
||||||
assert!(data.len() > 0);
|
assert!(data.len() > 0);
|
||||||
|
|
||||||
let mut flag_builder = settings::builder();
|
let mut flag_builder = settings::builder();
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ wasi-common = { git = "https://github.com/CraneStation/wasi-common", rev = "c3bf
|
|||||||
docopt = "1.0.1"
|
docopt = "1.0.1"
|
||||||
serde = { "version" = "1.0.94", features = ["derive"] }
|
serde = { "version" = "1.0.94", features = ["derive"] }
|
||||||
pretty_env_logger = "0.3.0"
|
pretty_env_logger = "0.3.0"
|
||||||
wabt = "0.9.2"
|
|
||||||
wasmtime-wast = { path="../wasmtime-wast" }
|
wasmtime-wast = { path="../wasmtime-wast" }
|
||||||
wasmtime-wasi = { path="../wasmtime-wasi" }
|
wasmtime-wasi = { path="../wasmtime-wasi" }
|
||||||
rayon = "1.1"
|
rayon = "1.1"
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ cranelift-wasm = { version = "0.46.1", features = ["enable-serde"] }
|
|||||||
wasmtime-jit = { path = "../wasmtime-jit" }
|
wasmtime-jit = { path = "../wasmtime-jit" }
|
||||||
wasmtime-runtime = { path = "../wasmtime-runtime" }
|
wasmtime-runtime = { path = "../wasmtime-runtime" }
|
||||||
wasmtime-environ = { path = "../wasmtime-environ" }
|
wasmtime-environ = { path = "../wasmtime-environ" }
|
||||||
wabt = "0.9.2"
|
wast = "2.0.0"
|
||||||
target-lexicon = "0.8.1"
|
target-lexicon = "0.8.1"
|
||||||
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 }
|
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
maintenance = { status = "experimental" }
|
maintenance = { status = "experimental" }
|
||||||
|
|||||||
@@ -23,14 +23,12 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
#[macro_use]
|
|
||||||
extern crate failure_derive;
|
|
||||||
|
|
||||||
mod spectest;
|
mod spectest;
|
||||||
mod wast;
|
mod wast;
|
||||||
|
|
||||||
pub use crate::spectest::instantiate_spectest;
|
pub use crate::spectest::instantiate_spectest;
|
||||||
pub use crate::wast::{WastContext, WastError};
|
pub use crate::wast::WastContext;
|
||||||
|
|
||||||
/// Version number of this crate.
|
/// Version number of this crate.
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|||||||
@@ -1,73 +1,28 @@
|
|||||||
use crate::spectest::instantiate_spectest;
|
use crate::spectest::instantiate_spectest;
|
||||||
use std::io::Read;
|
use failure::{bail, Error, ResultExt};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fmt, fs, io, str};
|
use std::str;
|
||||||
use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value};
|
|
||||||
use wabt::Features as WabtFeatures;
|
|
||||||
use wasmtime_jit::{
|
use wasmtime_jit::{
|
||||||
ActionError, ActionOutcome, Compiler, Context, Features, InstanceHandle, InstantiationError,
|
ActionError, ActionOutcome, Compiler, Context, Features, InstanceHandle, InstantiationError,
|
||||||
RuntimeValue, UnknownInstance,
|
RuntimeValue, SetupError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 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: &wast::Expression<'_>) -> RuntimeValue {
|
||||||
match v {
|
use wast::Instruction::*;
|
||||||
Value::I32(x) => RuntimeValue::I32(x),
|
|
||||||
Value::I64(x) => RuntimeValue::I64(x),
|
if v.instrs.len() != 1 {
|
||||||
Value::F32(x) => RuntimeValue::F32(x.to_bits()),
|
panic!("too many instructions in {:?}", v);
|
||||||
Value::F64(x) => RuntimeValue::F64(x.to_bits()),
|
|
||||||
Value::V128(x) => RuntimeValue::V128(x.to_le_bytes()),
|
|
||||||
}
|
}
|
||||||
}
|
match &v.instrs[0] {
|
||||||
|
I32Const(x) => RuntimeValue::I32(*x),
|
||||||
/// Error message used by `WastContext`.
|
I64Const(x) => RuntimeValue::I64(*x),
|
||||||
#[derive(Fail, Debug)]
|
F32Const(x) => RuntimeValue::F32(x.bits),
|
||||||
pub enum WastError {
|
F64Const(x) => RuntimeValue::F64(x.bits),
|
||||||
/// An assert command was not satisfied.
|
other => panic!("couldn't convert {:?} to a runtime value", other),
|
||||||
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.
|
|
||||||
Trap(String),
|
|
||||||
/// There was a type error in inputs or outputs of an action.
|
|
||||||
Type(String),
|
|
||||||
/// The was a syntax error while parsing the wast script.
|
|
||||||
Syntax(wabt::script::Error),
|
|
||||||
/// The was a character encoding error while parsing the wast script.
|
|
||||||
Utf8(str::Utf8Error),
|
|
||||||
/// The was an I/O error while reading the wast file.
|
|
||||||
IO(io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
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::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),
|
|
||||||
WastError::Syntax(ref message) => write!(f, "syntax error: {}", message),
|
|
||||||
WastError::Utf8(ref message) => write!(f, "UTF-8 decoding error: {}", message),
|
|
||||||
WastError::IO(ref error) => write!(f, "I/O error: {}", error),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error message with a source file and line number.
|
|
||||||
#[derive(Fail, Debug)]
|
|
||||||
#[fail(display = "{}:{}: {}", filename, line, error)]
|
|
||||||
pub struct WastFileError {
|
|
||||||
filename: String,
|
|
||||||
line: u64,
|
|
||||||
error: WastError,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {
|
||||||
@@ -95,429 +50,321 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_instance(
|
fn get_instance(&mut self, instance_name: Option<&str>) -> Result<&mut InstanceHandle, Error> {
|
||||||
&mut self,
|
|
||||||
instance_name: Option<&str>,
|
|
||||||
) -> Result<&mut InstanceHandle, WastError> {
|
|
||||||
let instance = if let Some(instance_name) = instance_name {
|
let instance = if let Some(instance_name) = instance_name {
|
||||||
self.context
|
self.context
|
||||||
.get_instance(instance_name)
|
.get_instance(instance_name)
|
||||||
.map_err(WastError::Instance)
|
.context("failed to fetch instance")?
|
||||||
} else {
|
} else {
|
||||||
self.current
|
self.current
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| WastError::NoDefaultInstance)
|
.ok_or_else(|| failure::format_err!("no current instance"))?
|
||||||
}?;
|
};
|
||||||
|
|
||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<(), Error> {
|
||||||
let instance = instantiate_spectest()?;
|
let instance = instantiate_spectest()?;
|
||||||
self.context.name_instance("spectest".to_owned(), instance);
|
self.context.name_instance("spectest".to_owned(), instance);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the action portion of a command.
|
/// Perform the action portion of a command.
|
||||||
fn perform_action(&mut self, action: Action) -> Result<ActionOutcome, WastError> {
|
fn perform_execute(&mut self, exec: wast::WastExecute<'_>) -> Result<ActionOutcome, Error> {
|
||||||
match action {
|
match exec {
|
||||||
Action::Invoke {
|
wast::WastExecute::Invoke(invoke) => self.perform_invoke(invoke),
|
||||||
module: instance_name,
|
wast::WastExecute::Module(mut module) => {
|
||||||
field,
|
let binary = module.encode()?;
|
||||||
args,
|
let result = self.context.instantiate_module(None, &binary);
|
||||||
} => self.invoke(instance_name, &field, &args),
|
match result {
|
||||||
Action::Get {
|
Ok(_) => Ok(ActionOutcome::Returned { values: Vec::new() }),
|
||||||
module: instance_name,
|
Err(ActionError::Setup(SetupError::Instantiate(
|
||||||
field,
|
InstantiationError::StartTrap(message),
|
||||||
} => self.get(instance_name, &field),
|
))) => Ok(ActionOutcome::Trapped { message }),
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wast::WastExecute::Get { module, global } => self.get(module.map(|s| s.name()), global),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn perform_invoke(&mut self, exec: wast::WastInvoke<'_>) -> Result<ActionOutcome, Error> {
|
||||||
|
self.invoke(exec.module.map(|i| i.name()), exec.name, &exec.args)
|
||||||
|
}
|
||||||
|
|
||||||
/// Define a module and register it.
|
/// Define a module and register it.
|
||||||
fn module(
|
fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
instance_name: Option<String>,
|
|
||||||
module: ModuleBinary,
|
|
||||||
) -> Result<(), ActionError> {
|
|
||||||
let index = self
|
let index = self
|
||||||
.context
|
.context
|
||||||
.instantiate_module(instance_name, &module.into_vec())?;
|
.instantiate_module(instance_name.map(|s| s.to_string()), module)?;
|
||||||
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<&str>, as_name: &str) -> Result<(), Error> {
|
||||||
let instance = self.get_instance(name.as_ref().map(|x| &**x))?.clone();
|
let instance = self.get_instance(name)?.clone();
|
||||||
self.context.name_instance(as_name, instance);
|
self.context.name_instance(as_name.to_string(), instance);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoke an exported function from an instance.
|
/// Invoke an exported function from an instance.
|
||||||
fn invoke(
|
fn invoke(
|
||||||
&mut self,
|
&mut self,
|
||||||
instance_name: Option<String>,
|
instance_name: Option<&str>,
|
||||||
field: &str,
|
field: &str,
|
||||||
args: &[Value],
|
args: &[wast::Expression],
|
||||||
) -> Result<ActionOutcome, WastError> {
|
) -> Result<ActionOutcome, Error> {
|
||||||
let value_args = args
|
let value_args = args.iter().map(runtime_value).collect::<Vec<_>>();
|
||||||
.iter()
|
let mut instance = self.get_instance(instance_name)?.clone();
|
||||||
.map(|arg| runtime_value(*arg))
|
let result = self
|
||||||
.collect::<Vec<_>>();
|
.context
|
||||||
let mut instance = self
|
|
||||||
.get_instance(instance_name.as_ref().map(|x| &**x))?
|
|
||||||
.clone();
|
|
||||||
self.context
|
|
||||||
.invoke(&mut instance, field, &value_args)
|
.invoke(&mut instance, field, &value_args)
|
||||||
.map_err(WastError::Action)
|
.with_context(|_| format!("failed to invoke `{}`", field))?;
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of an exported global from an instance.
|
/// Get the value of an exported global from an instance.
|
||||||
fn get(
|
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<ActionOutcome, Error> {
|
||||||
&mut self,
|
|
||||||
instance_name: Option<String>,
|
|
||||||
field: &str,
|
|
||||||
) -> Result<ActionOutcome, WastError> {
|
|
||||||
let instance = self
|
let instance = self
|
||||||
.get_instance(instance_name.as_ref().map(|x| &**x))?
|
.get_instance(instance_name.as_ref().map(|x| &**x))?
|
||||||
.clone();
|
.clone();
|
||||||
self.context
|
let result = self
|
||||||
|
.context
|
||||||
.get(&instance, field)
|
.get(&instance, field)
|
||||||
.map_err(WastError::Action)
|
.with_context(|_| format!("failed to get field `{}`", field))?;
|
||||||
}
|
Ok(result)
|
||||||
|
|
||||||
/// Perform the action of a `PerformAction`.
|
|
||||||
fn perform_action_command(&mut self, action: Action) -> Result<(), WastError> {
|
|
||||||
match self.perform_action(action)? {
|
|
||||||
ActionOutcome::Returned { .. } => Ok(()),
|
|
||||||
ActionOutcome::Trapped { message } => Err(WastError::Trap(message)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a wast script from a byte buffer.
|
/// Run a wast script from a byte buffer.
|
||||||
pub fn run_buffer(&mut self, filename: &str, wast: &[u8]) -> Result<(), WastFileError> {
|
pub fn run_buffer(&mut self, filename: &str, wast: &[u8]) -> Result<(), Error> {
|
||||||
let features: WabtFeatures = convert_features(self.context.features());
|
use wast::WastDirective::*;
|
||||||
|
|
||||||
// Work around https://github.com/pepyakin/wabt-rs/issues/59
|
let wast = str::from_utf8(wast)?;
|
||||||
let test_filename = Path::new(filename)
|
|
||||||
.file_name()
|
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
let mut parser = ScriptParser::from_source_and_name_with_features(
|
let adjust_wast = |mut err: wast::Error| {
|
||||||
str::from_utf8(wast)
|
err.set_path(filename.as_ref());
|
||||||
.map_err(|error| WastFileError {
|
err.set_text(wast);
|
||||||
filename: filename.to_string(),
|
err
|
||||||
line: 0,
|
};
|
||||||
error: WastError::Utf8(error),
|
let context = |sp: wast::Span| {
|
||||||
})?
|
let (line, col) = sp.linecol_in(wast);
|
||||||
.as_bytes(),
|
format!("for directive on {}:{}:{}", filename, line, col)
|
||||||
&test_filename,
|
};
|
||||||
features,
|
|
||||||
)
|
|
||||||
.map_err(|error| WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line: 0,
|
|
||||||
error: WastError::Syntax(error),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
while let Some(Command { kind, line }) = parser.next().expect("parser") {
|
let buf = wast::parser::ParseBuffer::new(wast).map_err(adjust_wast)?;
|
||||||
match kind {
|
let wast = wast::parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
|
||||||
CommandKind::Module {
|
|
||||||
module: instance_name,
|
for directive in wast.directives {
|
||||||
name,
|
match directive {
|
||||||
} => {
|
Module(mut module) => {
|
||||||
self.module(name, instance_name)
|
let binary = module.encode().map_err(adjust_wast)?;
|
||||||
.map_err(|error| WastFileError {
|
self.module(module.name.map(|s| s.name()), &binary)
|
||||||
filename: filename.to_string(),
|
.with_context(|_| context(module.span))?;
|
||||||
line,
|
|
||||||
error: WastError::Action(error),
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
CommandKind::Register { name, as_name } => {
|
Register { span, name, module } => {
|
||||||
self.register(name, as_name)
|
self.register(module.map(|s| s.name()), name)
|
||||||
.map_err(|error| WastFileError {
|
.with_context(|_| context(span))?;
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error,
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
CommandKind::PerformAction(action) => {
|
Invoke(i) => {
|
||||||
self.perform_action_command(action)
|
let span = i.span;
|
||||||
.map_err(|error| WastFileError {
|
self.perform_invoke(i).with_context(|_| context(span))?;
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error,
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
CommandKind::AssertReturn { action, expected } => {
|
AssertReturn {
|
||||||
match self.perform_action(action).map_err(|error| WastFileError {
|
span,
|
||||||
filename: filename.to_string(),
|
exec,
|
||||||
line,
|
results,
|
||||||
error,
|
} => match self.perform_execute(exec).with_context(|_| context(span))? {
|
||||||
})? {
|
ActionOutcome::Returned { values } => {
|
||||||
ActionOutcome::Returned { values } => {
|
for (v, e) in values.iter().zip(results.iter().map(runtime_value)) {
|
||||||
for (v, e) in values
|
if *v == e {
|
||||||
.iter()
|
continue;
|
||||||
.cloned()
|
|
||||||
.zip(expected.iter().cloned().map(runtime_value))
|
|
||||||
{
|
|
||||||
if v != e {
|
|
||||||
return Err(WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected {}, got {}",
|
|
||||||
e, v
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
bail!("{}\nexpected {}, got {}", context(span), e, v)
|
||||||
ActionOutcome::Trapped { message } => {
|
|
||||||
return Err(WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!("unexpected trap: {}", message)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
ActionOutcome::Trapped { message } => {
|
||||||
CommandKind::AssertTrap { action, message } => {
|
bail!("{}\nunexpected trap: {}", context(span), message)
|
||||||
match self.perform_action(action).map_err(|error| WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error,
|
|
||||||
})? {
|
|
||||||
ActionOutcome::Returned { values } => {
|
|
||||||
return Err(WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected trap, but invoke returned with {:?}",
|
|
||||||
values
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ActionOutcome::Trapped {
|
|
||||||
message: trap_message,
|
|
||||||
} => {
|
|
||||||
if !trap_message.contains(&message) {
|
|
||||||
#[cfg(feature = "lightbeam")]
|
|
||||||
println!(
|
|
||||||
"{}:{}: TODO: Check the assert_trap message: {}",
|
|
||||||
filename, line, message
|
|
||||||
);
|
|
||||||
#[cfg(not(feature = "lightbeam"))]
|
|
||||||
return Err(WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected {}, got {}",
|
|
||||||
message, trap_message
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
CommandKind::AssertExhaustion { action, message } => {
|
AssertTrap {
|
||||||
match self.perform_action(action).map_err(|error| WastFileError {
|
span,
|
||||||
filename: filename.to_string(),
|
exec,
|
||||||
line,
|
message,
|
||||||
error,
|
} => match self.perform_execute(exec).with_context(|_| context(span))? {
|
||||||
})? {
|
ActionOutcome::Returned { values } => {
|
||||||
ActionOutcome::Returned { values } => {
|
bail!("{}\nexpected trap, got {:?}", context(span), values)
|
||||||
return Err(WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected callstack exhaustion, but invoke returned with {:?}",
|
|
||||||
values
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ActionOutcome::Trapped {
|
|
||||||
message: trap_message,
|
|
||||||
} => {
|
|
||||||
if !trap_message.contains(&message) {
|
|
||||||
return Err(WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected exhaustion with {}, got {}",
|
|
||||||
message, trap_message
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
ActionOutcome::Trapped {
|
||||||
CommandKind::AssertReturnCanonicalNan { action } => {
|
message: trap_message,
|
||||||
match self.perform_action(action).map_err(|error| WastFileError {
|
} => {
|
||||||
filename: filename.to_string(),
|
if trap_message.contains(message) {
|
||||||
line,
|
continue;
|
||||||
error,
|
}
|
||||||
})? {
|
if cfg!(feature = "lightbeam") {
|
||||||
|
println!(
|
||||||
|
"{}\nTODO: Check the assert_trap message: {}",
|
||||||
|
context(span),
|
||||||
|
message
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bail!(
|
||||||
|
"{}\nexpected {}, got {}",
|
||||||
|
context(span),
|
||||||
|
message,
|
||||||
|
trap_message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AssertExhaustion {
|
||||||
|
span,
|
||||||
|
call,
|
||||||
|
message,
|
||||||
|
} => match self.perform_invoke(call).with_context(|_| context(span))? {
|
||||||
|
ActionOutcome::Returned { values } => {
|
||||||
|
bail!("{}\nexpected trap, got {:?}", context(span), values)
|
||||||
|
}
|
||||||
|
ActionOutcome::Trapped {
|
||||||
|
message: trap_message,
|
||||||
|
} => {
|
||||||
|
if trap_message.contains(message) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bail!(
|
||||||
|
"{}\nexpected exhaustion with {}, got {}",
|
||||||
|
context(span),
|
||||||
|
message,
|
||||||
|
trap_message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AssertReturnCanonicalNan { span, invoke } => {
|
||||||
|
match self
|
||||||
|
.perform_invoke(invoke)
|
||||||
|
.with_context(|_| context(span))?
|
||||||
|
{
|
||||||
ActionOutcome::Returned { values } => {
|
ActionOutcome::Returned { values } => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
match v {
|
||||||
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
||||||
return Err(WastFileError {
|
bail!("{}\nunexpected integer in NaN test", context(span))
|
||||||
filename: filename.to_string(),
|
}
|
||||||
line,
|
RuntimeValue::V128(_) => {
|
||||||
error: WastError::Type(format!(
|
bail!("{}\nunexpected vector in NaN test", context(span))
|
||||||
"unexpected integer type in NaN test"
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
RuntimeValue::F32(x) => {
|
RuntimeValue::F32(x) => {
|
||||||
if (x & 0x7fffffff) != 0x7fc00000 {
|
if (x & 0x7fffffff) != 0x7fc00000 {
|
||||||
return Err(WastFileError {
|
bail!("{}\nexpected canonical NaN", context(span))
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected canonical NaN"
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeValue::F64(x) => {
|
RuntimeValue::F64(x) => {
|
||||||
if (x & 0x7fffffffffffffff) != 0x7ff8000000000000 {
|
if (x & 0x7fffffffffffffff) != 0x7ff8000000000000 {
|
||||||
return Err(WastFileError {
|
bail!("{}\nexpected canonical NaN", context(span))
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected canonical NaN"
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeValue::V128(_) => {
|
|
||||||
return Err(WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Type(format!(
|
|
||||||
"unexpected vector type in NaN test"
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActionOutcome::Trapped { message } => {
|
ActionOutcome::Trapped { message } => {
|
||||||
return Err(WastFileError {
|
bail!("{}\nunexpected trap: {}", context(span), message)
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!("unexpected trap: {}", message)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandKind::AssertReturnArithmeticNan { action } => {
|
AssertReturnArithmeticNan { span, invoke } => {
|
||||||
match self.perform_action(action).map_err(|error| WastFileError {
|
match self
|
||||||
filename: filename.to_string(),
|
.perform_invoke(invoke)
|
||||||
line,
|
.with_context(|_| context(span))?
|
||||||
error,
|
{
|
||||||
})? {
|
|
||||||
ActionOutcome::Returned { values } => {
|
ActionOutcome::Returned { values } => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
match v {
|
||||||
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
||||||
return Err(WastFileError {
|
bail!("{}\nunexpected integer in NaN test", context(span))
|
||||||
filename: filename.to_string(),
|
}
|
||||||
line,
|
RuntimeValue::V128(_) => {
|
||||||
error: WastError::Type(format!(
|
bail!("{}\nunexpected vector in NaN test", context(span))
|
||||||
"unexpected integer type in NaN test",
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
RuntimeValue::F32(x) => {
|
RuntimeValue::F32(x) => {
|
||||||
if (x & 0x00400000) != 0x00400000 {
|
if (x & 0x00400000) != 0x00400000 {
|
||||||
return Err(WastFileError {
|
bail!("{}\nexpected arithmetic NaN", context(span))
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected arithmetic NaN"
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeValue::F64(x) => {
|
RuntimeValue::F64(x) => {
|
||||||
if (x & 0x0008000000000000) != 0x0008000000000000 {
|
if (x & 0x0008000000000000) != 0x0008000000000000 {
|
||||||
return Err(WastFileError {
|
bail!("{}\nexpected arithmetic NaN", context(span))
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!(
|
|
||||||
"expected arithmetic NaN"
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeValue::V128(_) => {
|
|
||||||
return Err(WastFileError {
|
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Type(format!(
|
|
||||||
"unexpected vector type in NaN test",
|
|
||||||
)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActionOutcome::Trapped { message } => {
|
ActionOutcome::Trapped { message } => {
|
||||||
return Err(WastFileError {
|
bail!("{}\nunexpected trap: {}", context(span), message)
|
||||||
filename: filename.to_string(),
|
|
||||||
line,
|
|
||||||
error: WastError::Assert(format!("unexpected trap: {}", message)),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CommandKind::AssertInvalid { module, message } => {
|
AssertInvalid {
|
||||||
self.module(None, module).expect_err(&format!(
|
span,
|
||||||
"{}:{}: invalid module was successfully instantiated",
|
mut module,
|
||||||
filename, line
|
message,
|
||||||
));
|
} => {
|
||||||
|
let bytes = module.encode().map_err(adjust_wast)?;
|
||||||
|
let err = match self.module(None, &bytes) {
|
||||||
|
Ok(()) => bail!("{}\nexpected module to fail to build", context(span)),
|
||||||
|
Err(e) => e,
|
||||||
|
};
|
||||||
println!(
|
println!(
|
||||||
"{}:{}: TODO: Check the assert_invalid message: {}",
|
"{}\nTODO: Check the assert_invalid message: {}",
|
||||||
filename, line, message
|
context(span),
|
||||||
|
message
|
||||||
);
|
);
|
||||||
|
drop(err);
|
||||||
}
|
}
|
||||||
CommandKind::AssertMalformed { module, message } => {
|
AssertMalformed {
|
||||||
self.module(None, module).expect_err(&format!(
|
span,
|
||||||
"{}:{}: malformed module was successfully instantiated",
|
module,
|
||||||
filename, line
|
message,
|
||||||
));
|
} => {
|
||||||
|
let mut module = match module {
|
||||||
|
wast::QuoteModule::Module(m) => m,
|
||||||
|
// this is a `*.wat` parser test which we're not
|
||||||
|
// interested in
|
||||||
|
wast::QuoteModule::Quote(_) => return Ok(()),
|
||||||
|
};
|
||||||
|
let bytes = module.encode().map_err(adjust_wast)?;
|
||||||
|
let err = match self.module(None, &bytes) {
|
||||||
|
Ok(()) => {
|
||||||
|
bail!("{}\nexpected module to fail to instantiate", context(span))
|
||||||
|
}
|
||||||
|
Err(e) => e,
|
||||||
|
};
|
||||||
println!(
|
println!(
|
||||||
"{}:{}: TODO: Check the assert_malformed message: {}",
|
"{}\nTODO: Check the assert_malformed message: {}",
|
||||||
filename, line, message
|
context(span),
|
||||||
|
message
|
||||||
);
|
);
|
||||||
|
drop(err);
|
||||||
}
|
}
|
||||||
CommandKind::AssertUninstantiable { module, message } => {
|
AssertUnlinkable {
|
||||||
let _err = self.module(None, module).expect_err(&format!(
|
span,
|
||||||
"{}:{}: uninstantiable module was successfully instantiated",
|
mut module,
|
||||||
filename, line
|
message,
|
||||||
));
|
} => {
|
||||||
|
let bytes = module.encode().map_err(adjust_wast)?;
|
||||||
|
let err = match self.module(None, &bytes) {
|
||||||
|
Ok(()) => bail!("{}\nexpected module to fail to link", context(span)),
|
||||||
|
Err(e) => e,
|
||||||
|
};
|
||||||
println!(
|
println!(
|
||||||
"{}:{}: TODO: Check the assert_uninstantiable message: {}",
|
"{}\nTODO: Check the assert_unlinkable message: {}",
|
||||||
filename, line, message
|
context(span),
|
||||||
);
|
message
|
||||||
}
|
|
||||||
CommandKind::AssertUnlinkable { module, message } => {
|
|
||||||
let _err = self.module(None, module).expect_err(&format!(
|
|
||||||
"{}:{}: unlinkable module was successfully linked",
|
|
||||||
filename, line
|
|
||||||
));
|
|
||||||
println!(
|
|
||||||
"{}:{}: TODO: Check the assert_unlinkable message: {}",
|
|
||||||
filename, line, message
|
|
||||||
);
|
);
|
||||||
|
drop(err);
|
||||||
}
|
}
|
||||||
|
AssertReturnFunc { .. } => panic!("need to implement assert_return_func"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,39 +372,9 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Run a wast script from a file.
|
/// Run a wast script from a file.
|
||||||
pub fn run_file(&mut self, path: &Path) -> Result<(), WastFileError> {
|
pub fn run_file(&mut self, path: &Path) -> Result<(), Error> {
|
||||||
let filename = path.display().to_string();
|
let bytes =
|
||||||
let buffer = read_to_end(path).map_err(|e| WastFileError {
|
std::fs::read(path).with_context(|_| format!("failed to read `{}`", path.display()))?;
|
||||||
filename,
|
self.run_buffer(path.to_str().unwrap(), &bytes)
|
||||||
line: 0,
|
|
||||||
error: WastError::IO(e),
|
|
||||||
})?;
|
|
||||||
self.run_buffer(&path.display().to_string(), &buffer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_to_end(path: &Path) -> Result<Vec<u8>, io::Error> {
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
|
||||||
let mut file = fs::File::open(path)?;
|
|
||||||
file.read_to_end(&mut buf)?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper to convert wasmtime features to WABT features; would be nicer as Into<WabtFeatures> but
|
|
||||||
/// wasmtime-jit does not have a wabt dependency
|
|
||||||
fn convert_features(features: &Features) -> WabtFeatures {
|
|
||||||
let mut wabt_features = WabtFeatures::new();
|
|
||||||
if features.simd {
|
|
||||||
wabt_features.enable_simd()
|
|
||||||
}
|
|
||||||
if features.multi_value {
|
|
||||||
wabt_features.enable_multi_value()
|
|
||||||
}
|
|
||||||
if features.bulk_memory {
|
|
||||||
wabt_features.enable_bulk_memory()
|
|
||||||
}
|
|
||||||
if features.threads {
|
|
||||||
wabt_features.enable_threads()
|
|
||||||
}
|
|
||||||
wabt_features
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user