From 9947bc52092957f2dc64a1fa975963047d254a31 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 18 Oct 2019 15:25:48 -0500 Subject: [PATCH] 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 --- Cargo.toml | 2 +- lightbeam/Cargo.toml | 2 +- lightbeam/src/lib.rs | 2 - lightbeam/src/tests.rs | 3 +- src/bin/wasmtime.rs | 20 +- tests/instantiate.rs | 18 +- wasmtime-api/Cargo.toml | 1 - wasmtime-wast/Cargo.toml | 3 +- wasmtime-wast/src/lib.rs | 4 +- wasmtime-wast/src/wast.rs | 637 ++++++++++++++------------------------ 10 files changed, 236 insertions(+), 456 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7677272118..94018ebdd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ failure = "0.1" target-lexicon = { version = "0.8.1", default-features = false } pretty_env_logger = "0.3.0" file-per-thread-logger = "0.1.1" -wabt = "0.9.2" +wat = "1.0.2" libc = "0.2.60" rayon = "1.1" wasm-webidl-bindings = "0.5" diff --git a/lightbeam/Cargo.toml b/lightbeam/Cargo.toml index 8a6d4572c7..625c36d912 100644 --- a/lightbeam/Cargo.toml +++ b/lightbeam/Cargo.toml @@ -26,7 +26,7 @@ typemap = "0.3" [dev-dependencies] lazy_static = "1.2" -wabt = "0.9.2" +wat = "1.0.2" quickcheck = "0.9.0" [badges] diff --git a/lightbeam/src/lib.rs b/lightbeam/src/lib.rs index 80bcd9ac03..2e8f00168e 100644 --- a/lightbeam/src/lib.rs +++ b/lightbeam/src/lib.rs @@ -20,8 +20,6 @@ extern crate lazy_static; #[cfg(test)] #[macro_use] extern crate quickcheck; -#[cfg(test)] -extern crate wabt; // Just so we can implement `Signature` for `cranelift_codegen::ir::Signature` extern crate cranelift_codegen; extern crate multi_mut; diff --git a/lightbeam/src/tests.rs b/lightbeam/src/tests.rs index 78c63c47ee..a832062ec2 100644 --- a/lightbeam/src/tests.rs +++ b/lightbeam/src/tests.rs @@ -1,8 +1,7 @@ use super::{module::ExecutionError, translate, ExecutableModule}; -use wabt; fn translate_wat(wat: &str) -> ExecutableModule { - let wasm = wabt::wat2wasm(wat).unwrap(); + let wasm = wat::parse_str(wat).unwrap(); let compiled = translate(&wasm).unwrap(); compiled } diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 22a141d9a3..f17a919da7 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -40,9 +40,8 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::fs::File; use std::path::Component; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::process::exit; -use wabt; use wasi_common::preopen_dir; use wasmtime::pick_compilation_strategy; use wasmtime_api::{Config, Engine, HostRef, Instance, Module, Store}; @@ -115,19 +114,6 @@ struct Args { flag_wasi_c: bool, } -fn read_wasm(path: PathBuf) -> Result, 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)> { let mut preopen_dirs = Vec::new(); @@ -353,8 +339,8 @@ fn instantiate_module( module_registry: &HashMap)>, path: &Path, ) -> Result<(HostRef, HostRef, Vec), Error> { - // Read the wasm module binary. - let data = read_wasm(path.to_path_buf())?; + // Read the wasm module binary either as `*.wat` or a raw binary + let data = wat::parse_file(path.to_path_buf())?; let module = HostRef::new(Module::new(store.clone(), &data)?); diff --git a/tests/instantiate.rs b/tests/instantiate.rs index 3da32b1d97..fc0d5237eb 100644 --- a/tests/instantiate.rs +++ b/tests/instantiate.rs @@ -5,32 +5,16 @@ use core::cell::RefCell; use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; use std::collections::HashMap; -use std::fs::File; -use std::io; -use std::io::Read; use std::path::PathBuf; -use wabt; use wasmtime_jit::{instantiate, CompilationStrategy, Compiler, NullResolver}; -#[cfg(test)] const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat"; -#[cfg(test)] -fn read_to_end(path: PathBuf) -> Result, io::Error> { - let mut buf: Vec = 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. #[test] fn test_environ_translate() { let path = PathBuf::from(PATH_MODULE_RS2WASM_ADD_FUNC); - let wat_data = read_to_end(path).unwrap(); - assert!(wat_data.len() > 0); - - let data = wabt::wat2wasm(wat_data).expect("expecting valid wat-file"); + let data = wat::parse_file(path).expect("expecting valid wat-file"); assert!(data.len() > 0); let mut flag_builder = settings::builder(); diff --git a/wasmtime-api/Cargo.toml b/wasmtime-api/Cargo.toml index fc20fe214a..aebd3c2f3c 100644 --- a/wasmtime-api/Cargo.toml +++ b/wasmtime-api/Cargo.toml @@ -39,7 +39,6 @@ wasi-common = { git = "https://github.com/CraneStation/wasi-common", rev = "c3bf docopt = "1.0.1" serde = { "version" = "1.0.94", features = ["derive"] } pretty_env_logger = "0.3.0" -wabt = "0.9.2" wasmtime-wast = { path="../wasmtime-wast" } wasmtime-wasi = { path="../wasmtime-wasi" } rayon = "1.1" diff --git a/wasmtime-wast/Cargo.toml b/wasmtime-wast/Cargo.toml index d8b5e022c3..2556282000 100644 --- a/wasmtime-wast/Cargo.toml +++ b/wasmtime-wast/Cargo.toml @@ -17,10 +17,9 @@ cranelift-wasm = { version = "0.46.1", features = ["enable-serde"] } wasmtime-jit = { path = "../wasmtime-jit" } wasmtime-runtime = { path = "../wasmtime-runtime" } wasmtime-environ = { path = "../wasmtime-environ" } -wabt = "0.9.2" +wast = "2.0.0" target-lexicon = "0.8.1" failure = { version = "0.1.3", default-features = false } -failure_derive = { version = "0.1.3", default-features = false } [badges] maintenance = { status = "experimental" } diff --git a/wasmtime-wast/src/lib.rs b/wasmtime-wast/src/lib.rs index f4e05ec02a..b8831c02c7 100644 --- a/wasmtime-wast/src/lib.rs +++ b/wasmtime-wast/src/lib.rs @@ -23,14 +23,12 @@ )] extern crate alloc; -#[macro_use] -extern crate failure_derive; mod spectest; mod wast; pub use crate::spectest::instantiate_spectest; -pub use crate::wast::{WastContext, WastError}; +pub use crate::wast::WastContext; /// Version number of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/wasmtime-wast/src/wast.rs b/wasmtime-wast/src/wast.rs index 8616bfca38..96113c17ef 100644 --- a/wasmtime-wast/src/wast.rs +++ b/wasmtime-wast/src/wast.rs @@ -1,73 +1,28 @@ use crate::spectest::instantiate_spectest; -use std::io::Read; +use failure::{bail, Error, ResultExt}; use std::path::Path; -use std::{fmt, fs, io, str}; -use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value}; -use wabt::Features as WabtFeatures; +use std::str; use wasmtime_jit::{ ActionError, ActionOutcome, Compiler, Context, Features, InstanceHandle, InstantiationError, - RuntimeValue, UnknownInstance, + RuntimeValue, SetupError, }; /// Translate from a `script::Value` to a `RuntimeValue`. -fn runtime_value(v: Value) -> RuntimeValue { - match v { - Value::I32(x) => RuntimeValue::I32(x), - Value::I64(x) => RuntimeValue::I64(x), - Value::F32(x) => RuntimeValue::F32(x.to_bits()), - Value::F64(x) => RuntimeValue::F64(x.to_bits()), - Value::V128(x) => RuntimeValue::V128(x.to_le_bytes()), +fn runtime_value(v: &wast::Expression<'_>) -> RuntimeValue { + use wast::Instruction::*; + + if v.instrs.len() != 1 { + panic!("too many instructions in {:?}", v); } -} - -/// Error message used by `WastContext`. -#[derive(Fail, Debug)] -pub enum WastError { - /// An assert command was not satisfied. - 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), - } + match &v.instrs[0] { + I32Const(x) => RuntimeValue::I32(*x), + I64Const(x) => RuntimeValue::I64(*x), + F32Const(x) => RuntimeValue::F32(x.bits), + F64Const(x) => RuntimeValue::F64(x.bits), + other => panic!("couldn't convert {:?} to a runtime value", other), } } -/// 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 /// to be performed on them. pub struct WastContext { @@ -95,429 +50,321 @@ impl WastContext { } } - fn get_instance( - &mut self, - instance_name: Option<&str>, - ) -> Result<&mut InstanceHandle, WastError> { + fn get_instance(&mut self, instance_name: Option<&str>) -> Result<&mut InstanceHandle, Error> { let instance = if let Some(instance_name) = instance_name { self.context .get_instance(instance_name) - .map_err(WastError::Instance) + .context("failed to fetch instance")? } else { self.current .as_mut() - .ok_or_else(|| WastError::NoDefaultInstance) - }?; + .ok_or_else(|| failure::format_err!("no current instance"))? + }; Ok(instance) } /// 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()?; self.context.name_instance("spectest".to_owned(), instance); Ok(()) } /// Perform the action portion of a command. - fn perform_action(&mut self, action: Action) -> Result { - match action { - Action::Invoke { - module: instance_name, - field, - args, - } => self.invoke(instance_name, &field, &args), - Action::Get { - module: instance_name, - field, - } => self.get(instance_name, &field), + fn perform_execute(&mut self, exec: wast::WastExecute<'_>) -> Result { + match exec { + wast::WastExecute::Invoke(invoke) => self.perform_invoke(invoke), + wast::WastExecute::Module(mut module) => { + let binary = module.encode()?; + let result = self.context.instantiate_module(None, &binary); + match result { + Ok(_) => Ok(ActionOutcome::Returned { values: Vec::new() }), + Err(ActionError::Setup(SetupError::Instantiate( + InstantiationError::StartTrap(message), + ))) => 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 { + self.invoke(exec.module.map(|i| i.name()), exec.name, &exec.args) + } + /// Define a module and register it. - fn module( - &mut self, - instance_name: Option, - module: ModuleBinary, - ) -> Result<(), ActionError> { + fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<(), Error> { let index = self .context - .instantiate_module(instance_name, &module.into_vec())?; + .instantiate_module(instance_name.map(|s| s.to_string()), module)?; 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 instance = self.get_instance(name.as_ref().map(|x| &**x))?.clone(); - self.context.name_instance(as_name, instance); + fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<(), Error> { + let instance = self.get_instance(name)?.clone(); + self.context.name_instance(as_name.to_string(), instance); Ok(()) } /// Invoke an exported function from an instance. fn invoke( &mut self, - instance_name: Option, + instance_name: Option<&str>, field: &str, - args: &[Value], - ) -> Result { - let value_args = args - .iter() - .map(|arg| runtime_value(*arg)) - .collect::>(); - let mut instance = self - .get_instance(instance_name.as_ref().map(|x| &**x))? - .clone(); - self.context + args: &[wast::Expression], + ) -> Result { + let value_args = args.iter().map(runtime_value).collect::>(); + let mut instance = self.get_instance(instance_name)?.clone(); + let result = self + .context .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. - fn get( - &mut self, - instance_name: Option, - field: &str, - ) -> Result { + fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result { let instance = self .get_instance(instance_name.as_ref().map(|x| &**x))? .clone(); - self.context + let result = self + .context .get(&instance, field) - .map_err(WastError::Action) - } - - /// 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)), - } + .with_context(|_| format!("failed to get field `{}`", field))?; + Ok(result) } /// Run a wast script from a byte buffer. - pub fn run_buffer(&mut self, filename: &str, wast: &[u8]) -> Result<(), WastFileError> { - let features: WabtFeatures = convert_features(self.context.features()); + pub fn run_buffer(&mut self, filename: &str, wast: &[u8]) -> Result<(), Error> { + use wast::WastDirective::*; - // Work around https://github.com/pepyakin/wabt-rs/issues/59 - let test_filename = Path::new(filename) - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_owned(); + let wast = str::from_utf8(wast)?; - let mut parser = ScriptParser::from_source_and_name_with_features( - str::from_utf8(wast) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line: 0, - error: WastError::Utf8(error), - })? - .as_bytes(), - &test_filename, - features, - ) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line: 0, - error: WastError::Syntax(error), - })?; + let adjust_wast = |mut err: wast::Error| { + err.set_path(filename.as_ref()); + err.set_text(wast); + err + }; + let context = |sp: wast::Span| { + let (line, col) = sp.linecol_in(wast); + format!("for directive on {}:{}:{}", filename, line, col) + }; - while let Some(Command { kind, line }) = parser.next().expect("parser") { - match kind { - CommandKind::Module { - module: instance_name, - name, - } => { - self.module(name, instance_name) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error: WastError::Action(error), - })?; + let buf = wast::parser::ParseBuffer::new(wast).map_err(adjust_wast)?; + let wast = wast::parser::parse::(&buf).map_err(adjust_wast)?; + + for directive in wast.directives { + match directive { + Module(mut module) => { + let binary = module.encode().map_err(adjust_wast)?; + self.module(module.name.map(|s| s.name()), &binary) + .with_context(|_| context(module.span))?; } - CommandKind::Register { name, as_name } => { - self.register(name, as_name) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })?; + Register { span, name, module } => { + self.register(module.map(|s| s.name()), name) + .with_context(|_| context(span))?; } - CommandKind::PerformAction(action) => { - self.perform_action_command(action) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })?; + Invoke(i) => { + let span = i.span; + self.perform_invoke(i).with_context(|_| context(span))?; } - CommandKind::AssertReturn { action, expected } => { - match self.perform_action(action).map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { - ActionOutcome::Returned { values } => { - for (v, e) in values - .iter() - .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 - )), - }); - } + AssertReturn { + span, + exec, + results, + } => match self.perform_execute(exec).with_context(|_| context(span))? { + ActionOutcome::Returned { values } => { + for (v, e) in values.iter().zip(results.iter().map(runtime_value)) { + if *v == e { + continue; } - } - ActionOutcome::Trapped { message } => { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Assert(format!("unexpected trap: {}", message)), - }); + bail!("{}\nexpected {}, got {}", context(span), e, v) } } - } - CommandKind::AssertTrap { action, 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 - )), - }); - } - } + ActionOutcome::Trapped { message } => { + bail!("{}\nunexpected trap: {}", context(span), message) } - } - CommandKind::AssertExhaustion { action, 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 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 - )), - }); - } - } + }, + AssertTrap { + span, + exec, + message, + } => match self.perform_execute(exec).with_context(|_| context(span))? { + ActionOutcome::Returned { values } => { + bail!("{}\nexpected trap, got {:?}", context(span), values) } - } - CommandKind::AssertReturnCanonicalNan { action } => { - match self.perform_action(action).map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { + ActionOutcome::Trapped { + message: trap_message, + } => { + if trap_message.contains(message) { + continue; + } + 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 } => { for v in values.iter() { match v { RuntimeValue::I32(_) | RuntimeValue::I64(_) => { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Type(format!( - "unexpected integer type in NaN test" - )), - }); + bail!("{}\nunexpected integer in NaN test", context(span)) + } + RuntimeValue::V128(_) => { + bail!("{}\nunexpected vector in NaN test", context(span)) } RuntimeValue::F32(x) => { if (x & 0x7fffffff) != 0x7fc00000 { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Assert(format!( - "expected canonical NaN" - )), - }); + bail!("{}\nexpected canonical NaN", context(span)) } } RuntimeValue::F64(x) => { if (x & 0x7fffffffffffffff) != 0x7ff8000000000000 { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Assert(format!( - "expected canonical NaN" - )), - }); + bail!("{}\nexpected canonical NaN", context(span)) } } - RuntimeValue::V128(_) => { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Type(format!( - "unexpected vector type in NaN test" - )), - }); - } }; } } ActionOutcome::Trapped { message } => { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Assert(format!("unexpected trap: {}", message)), - }); + bail!("{}\nunexpected trap: {}", context(span), message) } } } - CommandKind::AssertReturnArithmeticNan { action } => { - match self.perform_action(action).map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { + AssertReturnArithmeticNan { span, invoke } => { + match self + .perform_invoke(invoke) + .with_context(|_| context(span))? + { ActionOutcome::Returned { values } => { for v in values.iter() { match v { RuntimeValue::I32(_) | RuntimeValue::I64(_) => { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Type(format!( - "unexpected integer type in NaN test", - )), - }); + bail!("{}\nunexpected integer in NaN test", context(span)) + } + RuntimeValue::V128(_) => { + bail!("{}\nunexpected vector in NaN test", context(span)) } RuntimeValue::F32(x) => { if (x & 0x00400000) != 0x00400000 { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Assert(format!( - "expected arithmetic NaN" - )), - }); + bail!("{}\nexpected arithmetic NaN", context(span)) } } RuntimeValue::F64(x) => { if (x & 0x0008000000000000) != 0x0008000000000000 { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Assert(format!( - "expected arithmetic NaN" - )), - }); + bail!("{}\nexpected arithmetic NaN", context(span)) } } - RuntimeValue::V128(_) => { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Type(format!( - "unexpected vector type in NaN test", - )), - }); - } }; } } ActionOutcome::Trapped { message } => { - return Err(WastFileError { - filename: filename.to_string(), - line, - error: WastError::Assert(format!("unexpected trap: {}", message)), - }); + bail!("{}\nunexpected trap: {}", context(span), message) } } } - CommandKind::AssertInvalid { module, message } => { - self.module(None, module).expect_err(&format!( - "{}:{}: invalid module was successfully instantiated", - filename, line - )); + AssertInvalid { + span, + mut module, + 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!( - "{}:{}: TODO: Check the assert_invalid message: {}", - filename, line, message + "{}\nTODO: Check the assert_invalid message: {}", + context(span), + message ); + drop(err); } - CommandKind::AssertMalformed { module, message } => { - self.module(None, module).expect_err(&format!( - "{}:{}: malformed module was successfully instantiated", - filename, line - )); + AssertMalformed { + span, + module, + 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!( - "{}:{}: TODO: Check the assert_malformed message: {}", - filename, line, message + "{}\nTODO: Check the assert_malformed message: {}", + context(span), + message ); + drop(err); } - CommandKind::AssertUninstantiable { module, message } => { - let _err = self.module(None, module).expect_err(&format!( - "{}:{}: uninstantiable module was successfully instantiated", - filename, line - )); + AssertUnlinkable { + span, + mut module, + 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!( - "{}:{}: TODO: Check the assert_uninstantiable message: {}", - filename, line, 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 + "{}\nTODO: Check the assert_unlinkable message: {}", + context(span), + message ); + drop(err); } + AssertReturnFunc { .. } => panic!("need to implement assert_return_func"), } } @@ -525,39 +372,9 @@ impl WastContext { } /// Run a wast script from a file. - pub fn run_file(&mut self, path: &Path) -> Result<(), WastFileError> { - let filename = path.display().to_string(); - let buffer = read_to_end(path).map_err(|e| WastFileError { - filename, - line: 0, - error: WastError::IO(e), - })?; - self.run_buffer(&path.display().to_string(), &buffer) + pub fn run_file(&mut self, path: &Path) -> Result<(), Error> { + let bytes = + std::fs::read(path).with_context(|_| format!("failed to read `{}`", path.display()))?; + self.run_buffer(path.to_str().unwrap(), &bytes) } } - -fn read_to_end(path: &Path) -> Result, io::Error> { - let mut buf: Vec = 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 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 -}