Use wabt spec module (#48)

Use wabt-rs script parser to parse official testsuite.
This commit is contained in:
Sergey Pepyakin 2018-02-14 13:40:31 +03:00 committed by GitHub
parent 07fbe31ec2
commit c99ee1d986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 113 additions and 356 deletions

6
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "spec/wabt"] [submodule "spec/testsuite"]
path = spec/wabt path = spec/testsuite
url = https://github.com/WebAssembly/wabt.git url = https://github.com/webassembly/testsuite

View File

@ -7,19 +7,7 @@ readme = "README.md"
repository = "https://github.com/nikvolf/parity-wasm" repository = "https://github.com/nikvolf/parity-wasm"
homepage = "https://github.com/nikvolf/parity-wasm" homepage = "https://github.com/nikvolf/parity-wasm"
description = "parity-wasm testsuite" description = "parity-wasm testsuite"
build = "build.rs"
[build-dependencies]
cmake = "0.1"
[dependencies] [dependencies]
wasmi = { path = ".." } wasmi = { path = ".." }
serde_json = "1.0" wabt = "0.2.0"
serde_derive = "1.0"
serde = "1.0"
[dev-dependencies]
wasmi = { path = ".." }
serde_json = "1.0"
serde_derive = "1.0"
serde = "1.0"

View File

@ -1,8 +0,0 @@
extern crate cmake;
use cmake::Config;
fn main() {
let _dst = Config::new("wabt")
.define("BUILD_TESTS", "OFF")
.build();
}

View File

@ -1,9 +1,6 @@
#[cfg_attr(test, macro_use)] #[cfg(test)] extern crate serde_derive;
extern crate wasmi; extern crate wasmi;
extern crate serde; extern crate wabt;
extern crate serde_json;
mod run; mod run;
mod test;
mod fixtures; mod fixtures;

View File

@ -1,27 +1,30 @@
#![cfg(test)] #![cfg(test)]
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::fs::File;
use std::collections::HashMap; use std::collections::HashMap;
use serde_json;
use super::test;
use wasmi::{ use wasmi::{
Error as InterpreterError, Externals, FuncRef, Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor,
GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor,
MemoryInstance, MemoryRef, ModuleImportResolver, ModuleInstance, MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef,
ModuleRef, RuntimeValue, TableInstance, TableRef, ValueType, RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap,
Module, Signature, MemoryDescriptor, Trap, ValueType};
TableDescriptor, GlobalDescriptor, FuncInstance, RuntimeArgs,
};
use wasmi::memory_units::Pages; use wasmi::memory_units::Pages;
use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value};
fn spec_to_runtime_value(value: Value) -> RuntimeValue {
match value {
Value::I32(v) => RuntimeValue::I32(v),
Value::I64(v) => RuntimeValue::I64(v),
Value::F32(v) => RuntimeValue::F32(v),
Value::F64(v) => RuntimeValue::F64(v),
}
}
#[derive(Debug)] #[derive(Debug)]
enum Error { enum Error {
Load(String), Load(String),
Start(Trap), Start(Trap),
Script(script::Error),
Interpreter(InterpreterError), Interpreter(InterpreterError),
} }
@ -31,6 +34,12 @@ impl From<InterpreterError> for Error {
} }
} }
impl From<script::Error> for Error {
fn from(e: script::Error) -> Error {
Error::Script(e)
}
}
struct SpecModule { struct SpecModule {
table: TableRef, table: TableRef,
memory: MemoryRef, memory: MemoryRef,
@ -88,9 +97,10 @@ impl ModuleImportResolver for SpecModule {
return Ok(func); return Ok(func);
} }
Err(InterpreterError::Instantiation( Err(InterpreterError::Instantiation(format!(
format!("Unknown host func import {}", field_name), "Unknown host func import {}",
)) field_name
)))
} }
fn resolve_global( fn resolve_global(
@ -107,9 +117,10 @@ impl ModuleImportResolver for SpecModule {
}; };
} }
Err(InterpreterError::Instantiation( Err(InterpreterError::Instantiation(format!(
format!("Unknown host global import {}", field_name), "Unknown host global import {}",
)) field_name
)))
} }
fn resolve_memory( fn resolve_memory(
@ -121,9 +132,10 @@ impl ModuleImportResolver for SpecModule {
return Ok(self.memory.clone()); return Ok(self.memory.clone());
} }
Err(InterpreterError::Instantiation( Err(InterpreterError::Instantiation(format!(
format!("Unknown host memory import {}", field_name), "Unknown host memory import {}",
)) field_name
)))
} }
fn resolve_table( fn resolve_table(
@ -135,9 +147,10 @@ impl ModuleImportResolver for SpecModule {
return Ok(self.table.clone()); return Ok(self.table.clone());
} }
Err(InterpreterError::Instantiation( Err(InterpreterError::Instantiation(format!(
format!("Unknown host table import {}", field_name), "Unknown host table import {}",
)) field_name
)))
} }
} }
@ -176,9 +189,9 @@ impl SpecDriver {
fn module_or_last(&self, name: Option<&str>) -> Result<ModuleRef, InterpreterError> { fn module_or_last(&self, name: Option<&str>) -> Result<ModuleRef, InterpreterError> {
match name { match name {
Some(name) => self.module(name), Some(name) => self.module(name),
None => self.last_module.clone().ok_or_else(|| { None => self.last_module
InterpreterError::Instantiation("No modules registered".into()) .clone()
}), .ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())),
} }
} }
} }
@ -241,23 +254,12 @@ impl ImportResolver for SpecDriver {
} }
} }
fn try_load_module(base_dir: &Path, module_path: &str) -> Result<Module, Error> { fn try_load_module(wasm: &[u8]) -> Result<Module, Error> {
use std::io::prelude::*; Module::from_buffer(wasm).map_err(|e| Error::Load(e.to_string()))
let mut wasm_path = PathBuf::from(base_dir.clone());
wasm_path.push(module_path);
let mut file = File::open(wasm_path).unwrap();
let mut buf = Vec::new();
file.read_to_end(&mut buf).unwrap();
Module::from_buffer(buf).map_err(|e| Error::Load(e.to_string()))
} }
fn try_load( fn try_load(wasm: &[u8], spec_driver: &mut SpecDriver) -> Result<(), Error> {
base_dir: &Path, let module = try_load_module(wasm)?;
module_path: &str,
spec_driver: &mut SpecDriver,
) -> Result<(), Error> {
let module = try_load_module(base_dir, module_path)?;
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?; let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?;
instance instance
.run_start(spec_driver.spec_module()) .run_start(spec_driver.spec_module())
@ -265,14 +267,8 @@ fn try_load(
Ok(()) Ok(())
} }
fn load_module( fn load_module(wasm: &[u8], name: &Option<String>, spec_driver: &mut SpecDriver) -> ModuleRef {
base_dir: &Path, let module = try_load_module(wasm).expect(&format!("Wasm failed to load"));
path: &str,
name: &Option<String>,
spec_driver: &mut SpecDriver,
) -> ModuleRef {
let module =
try_load_module(base_dir, path).expect(&format!("Wasm file {} failed to load", path));
let instance = ModuleInstance::new(&module, spec_driver) let instance = ModuleInstance::new(&module, spec_driver)
.expect("Instantiation failed") .expect("Instantiation failed")
.run_start(spec_driver.spec_module()) .run_start(spec_driver.spec_module())
@ -284,66 +280,42 @@ fn load_module(
instance instance
} }
fn runtime_value(test_val: &test::RuntimeValue) -> RuntimeValue {
match test_val.value_type.as_ref() {
"i32" => {
let unsigned: u32 = test_val.value.parse().expect("Literal parse error");
RuntimeValue::I32(unsigned as i32)
}
"i64" => {
let unsigned: u64 = test_val.value.parse().expect("Literal parse error");
RuntimeValue::I64(unsigned as i64)
}
"f32" => {
let unsigned: u32 = test_val.value.parse().expect("Literal parse error");
RuntimeValue::decode_f32(unsigned)
}
"f64" => {
let unsigned: u64 = test_val.value.parse().expect("Literal parse error");
RuntimeValue::decode_f64(unsigned)
}
_ => panic!("Unknwon runtime value type"),
}
}
fn runtime_values(test_vals: &[test::RuntimeValue]) -> Vec<RuntimeValue> {
test_vals
.iter()
.map(runtime_value)
.collect::<Vec<RuntimeValue>>()
}
fn run_action( fn run_action(
program: &mut SpecDriver, program: &mut SpecDriver,
action: &test::Action, action: &Action,
) -> Result<Option<RuntimeValue>, InterpreterError> { ) -> Result<Option<RuntimeValue>, InterpreterError> {
match *action { match *action {
test::Action::Invoke { Action::Invoke {
ref module, ref module,
ref field, ref field,
ref args, ref args,
} => { } => {
let module = program.module_or_last(module.as_ref().map(|x| x.as_ref())).expect(&format!( let module = program
"Expected program to have loaded module {:?}", .module_or_last(module.as_ref().map(|x| x.as_ref()))
module .expect(&format!(
)); "Expected program to have loaded module {:?}",
module
));
module.invoke_export( module.invoke_export(
&jstring_to_rstring(field), field,
&runtime_values(args), &args.iter()
.cloned()
.map(spec_to_runtime_value)
.collect::<Vec<_>>(),
program.spec_module(), program.spec_module(),
) )
} }
test::Action::Get { Action::Get {
ref module, ref module,
ref field, ref field,
.. ..
} => { } => {
let module = program.module_or_last(module.as_ref().map(|x| x.as_ref())).expect(&format!( let module = program
"Expected program to have loaded module {:?}", .module_or_last(module.as_ref().map(|x| x.as_ref()))
module .expect(&format!(
)); "Expected program to have loaded module {:?}",
let field = jstring_to_rstring(&field); module
));
let global = module let global = module
.export_by_name(&field) .export_by_name(&field)
.ok_or_else(|| { .ok_or_else(|| {
@ -359,92 +331,29 @@ fn run_action(
} }
} }
pub struct FixtureParams {
failing: bool,
json: String,
}
pub fn run_wast2wasm(name: &str) -> FixtureParams {
let outdir = env::var("OUT_DIR").unwrap();
let mut wast2wasm_path = PathBuf::from(outdir.clone());
wast2wasm_path.push("bin");
wast2wasm_path.push("wast2json");
let mut json_spec_path = PathBuf::from(outdir.clone());
json_spec_path.push(&format!("{}.json", name));
let wast2wasm_output = Command::new(wast2wasm_path)
.arg("-o")
.arg(&json_spec_path)
.arg(&format!("./wabt/third_party/testsuite/{}.wast", name))
.output()
.expect("Failed to execute process");
FixtureParams {
json: json_spec_path.to_str().unwrap().to_owned(),
failing: {
if !wast2wasm_output.status.success() {
println!("wast2json error code: {}", wast2wasm_output.status);
println!(
"wast2json stdout: {}",
String::from_utf8_lossy(&wast2wasm_output.stdout)
);
println!(
"wast2json stderr: {}",
String::from_utf8_lossy(&wast2wasm_output.stderr)
);
true
} else {
false
}
},
}
}
pub fn spec(name: &str) { pub fn spec(name: &str) {
let tmpdir = env::var("OUT_DIR").unwrap(); println!("running test: {}", name);
try_spec(name).expect("Failed to run spec");
let fixture = run_wast2wasm(name); }
let wast2wasm_fail_expected = name.ends_with(".fail");
if wast2wasm_fail_expected {
if !fixture.failing {
panic!("wast2json expected to fail, but terminated normally");
}
// Failing fixture, bail out.
return;
}
if fixture.failing {
panic!("wast2json terminated abnormally, expected to success");
}
let mut f =
File::open(&fixture.json).expect(&format!("Failed to load json file {}", &fixture.json));
let spec: test::Spec =
serde_json::from_reader(&mut f).expect("Failed to deserialize JSON file");
fn try_spec(name: &str) -> Result<(), Error> {
let mut spec_driver = SpecDriver::new(); let mut spec_driver = SpecDriver::new();
for command in &spec.commands { let spec_script_path = format!("testsuite/{}.wast", name);
println!("command {:?}", command); let mut parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script");
match command { while let Some(Command { kind, line }) = parser.next()? {
&test::Command::Module { match kind {
ref name, CommandKind::Module { name, module, .. } => {
ref filename, load_module(&module.into_vec()?, &name, &mut spec_driver);
..
} => {
load_module(tmpdir.as_ref(), &filename, &name, &mut spec_driver);
} }
&test::Command::AssertReturn { CommandKind::AssertReturn { action, expected } => {
line, let result = run_action(&mut spec_driver, &action);
ref action,
ref expected,
} => {
let result = run_action(&mut spec_driver, action);
match result { match result {
Ok(result) => { Ok(result) => {
let spec_expected = runtime_values(expected); let spec_expected = expected
.iter()
.cloned()
.map(spec_to_runtime_value)
.collect::<Vec<_>>();
let actual_result = result.into_iter().collect::<Vec<RuntimeValue>>(); let actual_result = result.into_iter().collect::<Vec<RuntimeValue>>();
for (actual_result, spec_expected) in for (actual_result, spec_expected) in
actual_result.iter().zip(spec_expected.iter()) actual_result.iter().zip(spec_expected.iter())
@ -470,9 +379,9 @@ pub fn spec(name: &str) {
} }
} }
} }
&test::Command::AssertReturnCanonicalNan { line, ref action } | CommandKind::AssertReturnCanonicalNan { action }
&test::Command::AssertReturnArithmeticNan { line, ref action } => { | CommandKind::AssertReturnArithmeticNan { action } => {
let result = run_action(&mut spec_driver, action); let result = run_action(&mut spec_driver, &action);
match result { match result {
Ok(result) => { Ok(result) => {
for actual_result in result.into_iter().collect::<Vec<RuntimeValue>>() { for actual_result in result.into_iter().collect::<Vec<RuntimeValue>>() {
@ -495,19 +404,15 @@ pub fn spec(name: &str) {
} }
} }
} }
&test::Command::AssertExhaustion { CommandKind::AssertExhaustion { action, .. } => {
line, ref action, .. let result = run_action(&mut spec_driver, &action);
} => {
let result = run_action(&mut spec_driver, action);
match result { match result {
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result), Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e), Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e),
} }
} }
&test::Command::AssertTrap { CommandKind::AssertTrap { action, .. } => {
line, ref action, .. let result = run_action(&mut spec_driver, &action);
} => {
let result = run_action(&mut spec_driver, action);
match result { match result {
Ok(result) => { Ok(result) => {
panic!( panic!(
@ -520,55 +425,34 @@ pub fn spec(name: &str) {
} }
} }
} }
&test::Command::AssertInvalid { CommandKind::AssertInvalid { module, .. }
line, ref filename, .. | CommandKind::AssertMalformed { module, .. }
} | | CommandKind::AssertUnlinkable { module, .. } => {
&test::Command::AssertMalformed { let module_load = try_load(&module.into_vec()?, &mut spec_driver);
line, ref filename, ..
} |
&test::Command::AssertUnlinkable {
line, ref filename, ..
} => {
let module_load = try_load(tmpdir.as_ref(), filename, &mut spec_driver);
match module_load { match module_load {
Ok(_) => panic!("Expected invalid module definition, got some module!"), Ok(_) => panic!("Expected invalid module definition, got some module!"),
Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e), Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e),
} }
} }
&test::Command::AssertUninstantiable { CommandKind::AssertUninstantiable { module, .. } => {
line, ref filename, .. match try_load(&module.into_vec()?, &mut spec_driver) {
} => match try_load(tmpdir.as_ref(), &filename, &mut spec_driver) { Ok(_) => panic!("Expected error running start function at line {}", line),
Ok(_) => panic!("Expected error running start function at line {}", line), Err(e) => println!("assert_uninstantiable - success ({:?})", e),
Err(e) => println!("assert_uninstantiable - success ({:?})", e), }
}, }
&test::Command::Register { CommandKind::Register { name, as_name, .. } => {
line,
ref name,
ref as_name,
..
} => {
let module = match spec_driver.module_or_last(name.as_ref().map(|x| x.as_ref())) { let module = match spec_driver.module_or_last(name.as_ref().map(|x| x.as_ref())) {
Ok(module) => module, Ok(module) => module,
Err(e) => panic!("No such module, at line {} - ({:?})", e, line), Err(e) => panic!("No such module, at line {} - ({:?})", e, line),
}; };
spec_driver.add_module(Some(as_name.clone()), module); spec_driver.add_module(Some(as_name.clone()), module);
} }
&test::Command::Action { line, ref action } => { CommandKind::PerformAction(action) => match run_action(&mut spec_driver, &action) {
match run_action(&mut spec_driver, action) { Ok(_) => {}
Ok(_) => {} Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e),
Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e), },
}
}
} }
} }
}
// Convert json string to correct rust UTF8 string. Ok(())
// The reason is that, for example, rust character "\u{FEEF}" (3-byte UTF8 BOM) is represented as "\u00ef\u00bb\u00bf" in spec json.
// It is incorrect. Correct BOM representation in json is "\uFEFF" => we need to do a double utf8-parse here.
// This conversion is incorrect in general case (casting char to u8)!!!
fn jstring_to_rstring(jstring: &str) -> String {
let jstring_chars: Vec<u8> = jstring.chars().map(|c| c as u8).collect();
let rstring = String::from_utf8(jstring_chars).unwrap();
rstring
} }

View File

@ -1,104 +0,0 @@
#![cfg(test)]
#[derive(Deserialize, Debug)]
pub struct RuntimeValue {
#[serde(rename = "type")]
pub value_type: String,
pub value: String,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
pub enum Action {
#[serde(rename = "invoke")]
Invoke {
module: Option<String>,
field: String,
args: Vec<RuntimeValue>,
},
#[serde(rename = "get")]
Get {
module: Option<String>,
field: String,
}
}
#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
pub enum Command {
#[serde(rename = "module")]
Module {
line: u64,
name: Option<String>,
filename: String
},
#[serde(rename = "assert_return")]
AssertReturn {
line: u64,
action: Action,
expected: Vec<RuntimeValue>,
},
#[serde(rename = "assert_return_canonical_nan")]
AssertReturnCanonicalNan {
line: u64,
action: Action,
},
#[serde(rename = "assert_return_arithmetic_nan")]
AssertReturnArithmeticNan {
line: u64,
action: Action,
},
#[serde(rename = "assert_trap")]
AssertTrap {
line: u64,
action: Action,
text: String,
},
#[serde(rename = "assert_invalid")]
AssertInvalid {
line: u64,
filename: String,
text: String,
},
#[serde(rename = "assert_malformed")]
AssertMalformed {
line: u64,
filename: String,
text: String,
},
#[serde(rename = "assert_uninstantiable")]
AssertUninstantiable {
line: u64,
filename: String,
text: String,
},
#[serde(rename = "assert_exhaustion")]
AssertExhaustion {
line: u64,
action: Action,
},
#[serde(rename = "assert_unlinkable")]
AssertUnlinkable {
line: u64,
filename: String,
text: String,
},
#[serde(rename = "register")]
Register {
line: u64,
name: Option<String>,
#[serde(rename = "as")]
as_name: String,
},
#[serde(rename = "action")]
Action {
line: u64,
action: Action,
},
}
#[derive(Deserialize, Debug)]
pub struct Spec {
pub source_filename: String,
pub commands: Vec<Command>,
}

1
spec/testsuite Submodule

@ -0,0 +1 @@
Subproject commit b38bf51ea9c956951b9466aa8243d9727d009bb2

@ -1 +0,0 @@
Subproject commit a74072b2163ca20645e4a313636507cb3984f5fb