mirror of
https://github.com/fluencelabs/wasmer
synced 2024-12-13 22:25:40 +00:00
1387 lines
60 KiB
Rust
1387 lines
60 KiB
Rust
#![deny(
|
|
bad_style,
|
|
dead_code,
|
|
unused_imports,
|
|
unused_variables,
|
|
unused_unsafe,
|
|
unreachable_patterns
|
|
)]
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
// TODO fix spec failures
|
|
// TODO fix panics and remove panic handlers
|
|
// TODO do something with messages _message, message: _, msg: _
|
|
// TODO consider git submodule for spectests? & separate dir for simd/extra tests
|
|
// TODO cleanup refactor
|
|
// TODO Files could be run with multiple threads
|
|
// TODO Allow running WAST &str directly (E.g. for use outside of spectests)
|
|
|
|
use std::collections::HashSet;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
struct SpecFailure {
|
|
file: String,
|
|
line: u64,
|
|
kind: String,
|
|
message: String,
|
|
}
|
|
|
|
struct TestReport {
|
|
failures: Vec<SpecFailure>,
|
|
passed: u32,
|
|
failed: u32,
|
|
allowed_failure: u32,
|
|
}
|
|
|
|
impl TestReport {
|
|
pub fn count_passed(&mut self) {
|
|
self.passed += 1;
|
|
}
|
|
|
|
pub fn has_failures(&self) -> bool {
|
|
self.failed > 0
|
|
}
|
|
|
|
pub fn add_failure(
|
|
&mut self,
|
|
failure: SpecFailure,
|
|
_testkey: &str,
|
|
excludes: &Vec<Exclude>,
|
|
line: u64,
|
|
) {
|
|
if excludes
|
|
.iter()
|
|
.any(|e| e.line_matches(line) && e.exclude_kind == ExcludeKind::Fail)
|
|
{
|
|
self.allowed_failure += 1;
|
|
return;
|
|
}
|
|
self.failed += 1;
|
|
self.failures.push(failure);
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "clif")]
|
|
fn get_compiler_name() -> &'static str {
|
|
"clif"
|
|
}
|
|
|
|
#[cfg(feature = "llvm")]
|
|
fn get_compiler_name() -> &'static str {
|
|
"llvm"
|
|
}
|
|
|
|
#[cfg(feature = "singlepass")]
|
|
fn get_compiler_name() -> &'static str {
|
|
"singlepass"
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn get_target_family() -> &'static str {
|
|
"unix"
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn get_target_family() -> &'static str {
|
|
"windows"
|
|
}
|
|
|
|
fn get_target_arch() -> &'static str {
|
|
if cfg!(target_arch = "x86_64") {
|
|
"x86_64"
|
|
} else if cfg!(target_arch = "aarch64") {
|
|
"aarch64"
|
|
} else if cfg!(target_arch = "x86") {
|
|
"x86"
|
|
} else if cfg!(target_arch = "mips") {
|
|
"mips"
|
|
} else if cfg!(target_arch = "powerpc") {
|
|
"powerpc"
|
|
} else if cfg!(target_arch = "powerpc64") {
|
|
"powerpc64"
|
|
} else if cfg!(target_arch = "arm") {
|
|
"arm"
|
|
} else {
|
|
panic!("unknown target arch")
|
|
}
|
|
}
|
|
|
|
// clif:skip:data.wast:172:unix:x86
|
|
#[allow(dead_code)]
|
|
struct Exclude {
|
|
backend: Option<String>,
|
|
exclude_kind: ExcludeKind,
|
|
file: String,
|
|
line: Option<u64>,
|
|
target_family: Option<String>,
|
|
target_arch: Option<String>,
|
|
}
|
|
|
|
impl Exclude {
|
|
fn line_matches(&self, value: u64) -> bool {
|
|
self.line.is_none() || self.line.unwrap() == value
|
|
}
|
|
|
|
fn line_exact_match(&self, value: u64) -> bool {
|
|
self.line.is_some() && self.line.unwrap() == value
|
|
}
|
|
|
|
fn matches_backend(&self, value: &str) -> bool {
|
|
self.backend.is_none() || self.backend.as_ref().unwrap() == value
|
|
}
|
|
|
|
fn matches_target_family(&self, value: &str) -> bool {
|
|
self.target_family.is_none() || self.target_family.as_ref().unwrap() == value
|
|
}
|
|
|
|
fn matches_target_arch(&self, value: &str) -> bool {
|
|
self.target_arch.is_none() || self.target_arch.as_ref().unwrap() == value
|
|
}
|
|
|
|
fn from(
|
|
backend: &str,
|
|
exclude_kind: &str,
|
|
file: &str,
|
|
line: &str,
|
|
target_family: &str,
|
|
target_arch: &str,
|
|
) -> Exclude {
|
|
let backend: Option<String> = match backend {
|
|
"*" => None,
|
|
"clif" => Some("clif".to_string()),
|
|
"singlepass" => Some("singlepass".to_string()),
|
|
"llvm" => Some("llvm".to_string()),
|
|
_ => panic!("backend {:?} not recognized", backend),
|
|
};
|
|
let exclude_kind = match exclude_kind {
|
|
"skip" => ExcludeKind::Skip,
|
|
"fail" => ExcludeKind::Fail,
|
|
_ => panic!("exclude kind {:?} not recognized", exclude_kind),
|
|
};
|
|
let line = match line {
|
|
"*" => None,
|
|
_ => Some(
|
|
line.parse::<u64>()
|
|
.expect(&format!("expected * or int: {:?}", line)),
|
|
),
|
|
};
|
|
let target_family = match target_family {
|
|
"*" => None,
|
|
_ => Some(target_family.to_string()),
|
|
};
|
|
let target_arch = match target_arch {
|
|
"*" => None,
|
|
_ => Some(target_arch.to_string()),
|
|
};
|
|
Exclude {
|
|
backend,
|
|
exclude_kind,
|
|
file: file.to_string(),
|
|
line,
|
|
target_family,
|
|
target_arch,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))]
|
|
fn get_compiler_name() -> &'static str {
|
|
panic!("compiler not specified, activate a compiler via features");
|
|
"unknown"
|
|
}
|
|
|
|
fn with_instance<F, R>(
|
|
maybe_instance: Option<Arc<Mutex<Instance>>>,
|
|
named_modules: &HashMap<String, Arc<Mutex<Instance>>>,
|
|
module: &Option<String>,
|
|
f: F,
|
|
) -> Option<R>
|
|
where
|
|
R: Sized,
|
|
F: FnOnce(&Instance) -> R,
|
|
{
|
|
let ref ins = module
|
|
.as_ref()
|
|
.and_then(|name| named_modules.get(name).cloned())
|
|
.or(maybe_instance)?;
|
|
let guard = ins.lock().unwrap();
|
|
Some(f(guard.borrow()))
|
|
}
|
|
|
|
use glob::glob;
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::panic::AssertUnwindSafe;
|
|
use std::path::PathBuf;
|
|
use wabt::script::{Action, Command, CommandKind, ScriptParser, Value};
|
|
use wasmer_runtime::{
|
|
compile_with_config,
|
|
error::CompileError,
|
|
func, imports,
|
|
types::{ElementType, MemoryDescriptor, TableDescriptor},
|
|
units::Pages,
|
|
CompilerConfig, Ctx, Export, Features, Global, ImportObject, Instance, LikeNamespace,
|
|
Memory, Table,
|
|
};
|
|
|
|
fn parse_and_run(
|
|
path: &PathBuf,
|
|
file_excludes: &HashSet<String>,
|
|
excludes: &HashMap<String, Vec<Exclude>>,
|
|
) -> Result<TestReport, String> {
|
|
let mut test_report = TestReport {
|
|
failures: vec![],
|
|
passed: 0,
|
|
failed: 0,
|
|
allowed_failure: 0,
|
|
};
|
|
|
|
let filename = path.file_name().unwrap().to_str().unwrap();
|
|
let source = fs::read(&path).unwrap();
|
|
|
|
// Entire file is excluded by line * and skip
|
|
if file_excludes.contains(filename) {
|
|
return Ok(test_report);
|
|
}
|
|
|
|
let mut features = wabt::Features::new();
|
|
features.enable_simd();
|
|
features.enable_threads();
|
|
features.enable_sign_extension();
|
|
let mut parser: ScriptParser =
|
|
ScriptParser::from_source_and_name_with_features(&source, filename, features)
|
|
.expect(&format!("Failed to parse script {}", &filename));
|
|
|
|
use std::panic;
|
|
let mut instance: Option<Arc<Mutex<Instance>>> = None;
|
|
|
|
let mut named_modules: HashMap<String, Arc<Mutex<Instance>>> = HashMap::new();
|
|
|
|
let mut registered_modules: HashMap<String, Arc<Mutex<Instance>>> = HashMap::new();
|
|
//
|
|
let empty_excludes = vec![];
|
|
let excludes = if excludes.contains_key(filename) {
|
|
excludes.get(filename).unwrap()
|
|
} else {
|
|
&empty_excludes
|
|
};
|
|
|
|
let backend = get_compiler_name();
|
|
|
|
while let Some(Command { kind, line }) =
|
|
parser.next().map_err(|e| format!("Parse err: {:?}", e))?
|
|
{
|
|
let test_key = format!("{}:{}:{}", backend, filename, line);
|
|
// Use this line to debug which test is running
|
|
println!("Running test: {}", test_key);
|
|
|
|
// Skip tests that match this line
|
|
if excludes
|
|
.iter()
|
|
.any(|e| e.line_exact_match(line) && e.exclude_kind == ExcludeKind::Skip)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
match kind {
|
|
CommandKind::Module { module, name } => {
|
|
// println!("Module");
|
|
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
let spectest_import_object =
|
|
get_spectest_import_object(®istered_modules);
|
|
let config = CompilerConfig {
|
|
features: Features {
|
|
simd: true,
|
|
threads: true,
|
|
},
|
|
..Default::default()
|
|
};
|
|
let module = compile_with_config(&module.into_vec(), config)
|
|
.expect("WASM can't be compiled");
|
|
let i = module
|
|
.instantiate(&spectest_import_object)
|
|
.expect("WASM can't be instantiated");
|
|
i
|
|
}));
|
|
match result {
|
|
Err(e) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "Module"),
|
|
message: format!("caught panic {:?}", e),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
instance = None;
|
|
}
|
|
Ok(i) => {
|
|
let i = Arc::new(Mutex::new(i));
|
|
if name.is_some() {
|
|
named_modules.insert(name.unwrap(), Arc::clone(&i));
|
|
}
|
|
instance = Some(i);
|
|
}
|
|
}
|
|
}
|
|
CommandKind::AssertReturn { action, expected } => {
|
|
match action {
|
|
Action::Invoke {
|
|
module,
|
|
field,
|
|
args,
|
|
} => {
|
|
let maybe_call_result = with_instance(
|
|
instance.clone(),
|
|
&named_modules,
|
|
&module,
|
|
|instance| {
|
|
let params: Vec<wasmer_runtime::types::Value> =
|
|
args.iter().cloned().map(convert_value).collect();
|
|
instance.call(&field, ¶ms[..])
|
|
},
|
|
);
|
|
if maybe_call_result.is_none() {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertReturn"),
|
|
message: format!("No instance available: {:?}", &module),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
} else {
|
|
let call_result = maybe_call_result.unwrap();
|
|
match call_result {
|
|
Err(e) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "AssertReturn"),
|
|
message: format!("Call failed {:?}", e),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
Ok(values) => {
|
|
for (i, v) in values.iter().enumerate() {
|
|
let expected_value =
|
|
convert_wabt_value(*expected.get(i).unwrap());
|
|
let v = convert_wasmer_value(v.clone());
|
|
if v != expected_value {
|
|
test_report.add_failure(SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "AssertReturn"),
|
|
message: format!(
|
|
"result {:?} ({:?}) does not match expected {:?} ({:?})",
|
|
v, to_hex(v.clone()), expected_value, to_hex(expected_value.clone())
|
|
),
|
|
}, &test_key, excludes, line);
|
|
} else {
|
|
test_report.count_passed();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Action::Get { module, field } => {
|
|
let maybe_call_result = with_instance(
|
|
instance.clone(),
|
|
&named_modules,
|
|
&module,
|
|
|instance| {
|
|
instance
|
|
.get_export(&field)
|
|
.expect(&format!("missing global {:?}", &field))
|
|
},
|
|
);
|
|
if maybe_call_result.is_none() {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertReturn Get"),
|
|
message: format!("No instance available {:?}", &module),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
} else {
|
|
let export: Export = maybe_call_result.unwrap();
|
|
match export {
|
|
Export::Global(g) => {
|
|
let value = g.get();
|
|
let expected_value =
|
|
convert_value(*expected.get(0).unwrap());
|
|
if value == expected_value {
|
|
test_report.count_passed();
|
|
} else {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertReturn Get"),
|
|
message: format!(
|
|
"Expected Global {:?} got: {:?}",
|
|
expected_value, value
|
|
),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
_ => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertReturn Get"),
|
|
message: format!("Expected Global"),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// println!("in assert return");
|
|
}
|
|
CommandKind::AssertReturnCanonicalNan { action } => match action {
|
|
Action::Invoke {
|
|
module,
|
|
field,
|
|
args,
|
|
} => {
|
|
let maybe_call_result =
|
|
with_instance(instance.clone(), &named_modules, &module, |instance| {
|
|
let params: Vec<wasmer_runtime::types::Value> =
|
|
args.iter().cloned().map(convert_value).collect();
|
|
instance.call(&field, ¶ms[..])
|
|
});
|
|
if maybe_call_result.is_none() {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertReturnCanonicalNan"),
|
|
message: format!("No instance available {:?}", &module),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
} else {
|
|
let call_result = maybe_call_result.unwrap();
|
|
match call_result {
|
|
Err(e) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "AssertReturnCanonicalNan"),
|
|
message: format!("Call failed {:?}", e),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
Ok(values) => {
|
|
for v in values.iter() {
|
|
if is_canonical_nan(v.clone()) {
|
|
test_report.count_passed();
|
|
} else {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!(
|
|
"{:?}",
|
|
"AssertReturnCanonicalNan"
|
|
),
|
|
message: format!(
|
|
"value is not canonical nan {:?} ({:?})",
|
|
v,
|
|
value_to_hex(v.clone()),
|
|
),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => panic!("unexpected action in assert return canonical nan"),
|
|
},
|
|
CommandKind::AssertReturnArithmeticNan { action } => match action {
|
|
Action::Invoke {
|
|
module,
|
|
field,
|
|
args,
|
|
} => {
|
|
let maybe_call_result =
|
|
with_instance(instance.clone(), &named_modules, &module, |instance| {
|
|
let params: Vec<wasmer_runtime::types::Value> =
|
|
args.iter().cloned().map(convert_value).collect();
|
|
instance.call(&field, ¶ms[..])
|
|
});
|
|
if maybe_call_result.is_none() {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertReturnArithmeticNan"),
|
|
message: format!("No instance available"),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
} else {
|
|
let call_result = maybe_call_result.unwrap();
|
|
match call_result {
|
|
Err(e) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "AssertReturnArithmeticNan"),
|
|
message: format!("Call failed {:?}", e),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
Ok(values) => {
|
|
for v in values.iter() {
|
|
if is_arithmetic_nan(v.clone()) {
|
|
test_report.count_passed();
|
|
} else {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!(
|
|
"{:?}",
|
|
"AssertReturnArithmeticNan"
|
|
),
|
|
message: format!(
|
|
"value is not arithmetic nan {:?} ({:?})",
|
|
v,
|
|
value_to_hex(v.clone()),
|
|
),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => panic!("unexpected action in assert return arithmetic nan"),
|
|
},
|
|
CommandKind::AssertTrap { action, message: _ } => match action {
|
|
Action::Invoke {
|
|
module,
|
|
field,
|
|
args,
|
|
} => {
|
|
let maybe_call_result =
|
|
with_instance(instance.clone(), &named_modules, &module, |instance| {
|
|
let params: Vec<wasmer_runtime::types::Value> =
|
|
args.iter().cloned().map(convert_value).collect();
|
|
instance.call(&field, ¶ms[..])
|
|
});
|
|
if maybe_call_result.is_none() {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertTrap"),
|
|
message: format!("No instance available"),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
} else {
|
|
let call_result = maybe_call_result.unwrap();
|
|
use wasmer_runtime::error::{CallError, RuntimeError};
|
|
match call_result {
|
|
Err(e) => {
|
|
match e {
|
|
CallError::Resolve(_) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "AssertTrap"),
|
|
message: format!("expected trap, got {:?}", e),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
CallError::Runtime(r) => {
|
|
match r {
|
|
RuntimeError::Trap { .. } => {
|
|
// TODO assert message?
|
|
test_report.count_passed()
|
|
}
|
|
RuntimeError::Error { .. } => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "AssertTrap"),
|
|
message: format!(
|
|
"expected trap, got Runtime:Error {:?}",
|
|
r
|
|
),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(values) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "AssertTrap"),
|
|
message: format!("expected trap, got {:?}", values),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => println!("unexpected action"),
|
|
},
|
|
CommandKind::AssertInvalid { module, message: _ } => {
|
|
// println!("AssertInvalid");
|
|
let result = panic::catch_unwind(|| {
|
|
let config = CompilerConfig {
|
|
features: Features {
|
|
simd: true,
|
|
threads: true,
|
|
},
|
|
..Default::default()
|
|
};
|
|
compile_with_config(&module.into_vec(), config)
|
|
});
|
|
match result {
|
|
Ok(module) => {
|
|
if let Err(CompileError::InternalError { msg: _ }) = module {
|
|
test_report.count_passed();
|
|
// println!("expected: {:?}", message);
|
|
// println!("actual: {:?}", msg);
|
|
} else if let Err(CompileError::ValidationError { msg: _ }) = module {
|
|
test_report.count_passed();
|
|
// println!("validation expected: {:?}", message);
|
|
// println!("validation actual: {:?}", msg);
|
|
} else {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertInvalid"),
|
|
message: "Should be invalid".to_string(),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
Err(p) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertInvalid"),
|
|
message: format!("caught panic {:?}", p),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
CommandKind::AssertMalformed { module, message: _ } => {
|
|
// println!("AssertMalformed");
|
|
|
|
let result = panic::catch_unwind(|| {
|
|
let config = CompilerConfig {
|
|
features: Features {
|
|
simd: true,
|
|
threads: true,
|
|
},
|
|
..Default::default()
|
|
};
|
|
compile_with_config(&module.into_vec(), config)
|
|
});
|
|
|
|
match result {
|
|
Ok(module) => {
|
|
if let Err(CompileError::InternalError { msg: _ }) = module {
|
|
test_report.count_passed();
|
|
// println!("expected: {:?}", message);
|
|
// println!("actual: {:?}", msg);
|
|
} else if let Err(CompileError::ValidationError { msg: _ }) = module {
|
|
test_report.count_passed();
|
|
// println!("validation expected: {:?}", message);
|
|
// println!("validation actual: {:?}", msg);
|
|
} else {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertMalformed"),
|
|
message: format!("should be malformed"),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
Err(p) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertMalformed"),
|
|
message: format!("caught panic {:?}", p),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
CommandKind::AssertUninstantiable { module, message: _ } => {
|
|
let spectest_import_object = get_spectest_import_object(®istered_modules);
|
|
let config = CompilerConfig {
|
|
features: Features {
|
|
simd: true,
|
|
threads: true,
|
|
},
|
|
..Default::default()
|
|
};
|
|
let module = compile_with_config(&module.into_vec(), config)
|
|
.expect("WASM can't be compiled");
|
|
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
module
|
|
.instantiate(&spectest_import_object)
|
|
.expect("WASM can't be instantiated");
|
|
}));
|
|
match result {
|
|
Err(_) => test_report.count_passed(),
|
|
Ok(_) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertUninstantiable"),
|
|
message: format!(
|
|
"instantiate successful, expected uninstantiable"
|
|
),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
};
|
|
}
|
|
CommandKind::AssertExhaustion { action, message: _ } => {
|
|
match action {
|
|
Action::Invoke {
|
|
module,
|
|
field,
|
|
args,
|
|
} => {
|
|
let maybe_call_result = with_instance(
|
|
instance.clone(),
|
|
&named_modules,
|
|
&module,
|
|
|instance| {
|
|
let params: Vec<wasmer_runtime::types::Value> =
|
|
args.iter().cloned().map(convert_value).collect();
|
|
instance.call(&field, ¶ms[..])
|
|
},
|
|
);
|
|
if maybe_call_result.is_none() {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertExhaustion"),
|
|
message: format!("No instance available"),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
} else {
|
|
let call_result = maybe_call_result.unwrap();
|
|
match call_result {
|
|
Err(_e) => {
|
|
// TODO is specific error required?
|
|
test_report.count_passed();
|
|
}
|
|
Ok(values) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "AssertExhaustion"),
|
|
message: format!(
|
|
"Expected call failure, got {:?}",
|
|
values
|
|
),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => println!("unexpected action in assert exhaustion"),
|
|
}
|
|
}
|
|
CommandKind::AssertUnlinkable { module, message: _ } => {
|
|
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
let spectest_import_object =
|
|
get_spectest_import_object(®istered_modules);
|
|
let config = CompilerConfig {
|
|
features: Features {
|
|
simd: true,
|
|
threads: true,
|
|
},
|
|
..Default::default()
|
|
};
|
|
let module = compile_with_config(&module.into_vec(), config)
|
|
.expect("WASM can't be compiled");
|
|
module.instantiate(&spectest_import_object)
|
|
}));
|
|
match result {
|
|
Err(e) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertUnlinkable"),
|
|
message: format!("caught panic {:?}", e),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
Ok(result) => match result {
|
|
Ok(_) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertUnlinkable"),
|
|
message: format!(
|
|
"instantiate successful, expected unlinkable"
|
|
),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
Err(e) => match e {
|
|
wasmer_runtime::error::Error::LinkError(_) => {
|
|
test_report.count_passed();
|
|
}
|
|
_ => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "AssertUnlinkable"),
|
|
message: format!("expected link error, got {:?}", e),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
},
|
|
},
|
|
}
|
|
}
|
|
CommandKind::Register { name, as_name } => {
|
|
let instance: Option<Arc<Mutex<Instance>>> = match name {
|
|
Some(ref name) => {
|
|
let i = named_modules.get(name);
|
|
match i {
|
|
Some(ins) => Some(Arc::clone(ins)),
|
|
None => None,
|
|
}
|
|
}
|
|
None => match instance {
|
|
Some(ref i) => Some(Arc::clone(i)),
|
|
None => None,
|
|
},
|
|
};
|
|
|
|
if let Some(ins) = instance {
|
|
registered_modules.insert(as_name, ins);
|
|
} else {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "Register"),
|
|
message: format!("No instance available"),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
}
|
|
CommandKind::PerformAction(ref action) => match action {
|
|
Action::Invoke {
|
|
module,
|
|
field,
|
|
args,
|
|
} => {
|
|
let maybe_call_result =
|
|
with_instance(instance.clone(), &named_modules, &module, |instance| {
|
|
let params: Vec<wasmer_runtime::types::Value> =
|
|
args.iter().cloned().map(convert_value).collect();
|
|
instance.call(&field, ¶ms[..])
|
|
});
|
|
if maybe_call_result.is_none() {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line: line,
|
|
kind: format!("{}", "PerformAction"),
|
|
message: format!("No instance available"),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
} else {
|
|
let call_result = maybe_call_result.unwrap();
|
|
match call_result {
|
|
Err(e) => {
|
|
test_report.add_failure(
|
|
SpecFailure {
|
|
file: filename.to_string(),
|
|
line,
|
|
kind: format!("{}", "PerformAction"),
|
|
message: format!("Call failed {:?}", e),
|
|
},
|
|
&test_key,
|
|
excludes,
|
|
line,
|
|
);
|
|
}
|
|
Ok(_values) => {
|
|
test_report.count_passed();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Action::Get { module, field } => println!(
|
|
"Action Get not implemented {:?} {:?} {:?} {:?}",
|
|
module, field, filename, line
|
|
),
|
|
},
|
|
}
|
|
}
|
|
Ok(test_report)
|
|
}
|
|
|
|
fn is_canonical_nan(val: wasmer_runtime::types::Value) -> bool {
|
|
match val {
|
|
wasmer_runtime::types::Value::F32(x) => x.is_canonical_nan(),
|
|
wasmer_runtime::types::Value::F64(x) => x.is_canonical_nan(),
|
|
_ => panic!("value is not a float {:?}", val),
|
|
}
|
|
}
|
|
|
|
fn is_arithmetic_nan(val: wasmer_runtime::types::Value) -> bool {
|
|
match val {
|
|
wasmer_runtime::types::Value::F32(x) => x.is_quiet_nan(),
|
|
wasmer_runtime::types::Value::F64(x) => x.is_quiet_nan(),
|
|
_ => panic!("value is not a float {:?}", val),
|
|
}
|
|
}
|
|
|
|
fn value_to_hex(val: wasmer_runtime::types::Value) -> String {
|
|
match val {
|
|
wasmer_runtime::types::Value::I32(x) => format!("{:#x}", x),
|
|
wasmer_runtime::types::Value::I64(x) => format!("{:#x}", x),
|
|
wasmer_runtime::types::Value::F32(x) => format!("{:#x}", x.to_bits()),
|
|
wasmer_runtime::types::Value::F64(x) => format!("{:#x}", x.to_bits()),
|
|
wasmer_runtime::types::Value::V128(x) => format!("{:#x}", x),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
pub enum SpectestValue {
|
|
I32(i32),
|
|
I64(i64),
|
|
F32(u32),
|
|
F64(u64),
|
|
V128(u128),
|
|
}
|
|
|
|
fn convert_wasmer_value(other: wasmer_runtime::types::Value) -> SpectestValue {
|
|
match other {
|
|
wasmer_runtime::types::Value::I32(v) => SpectestValue::I32(v),
|
|
wasmer_runtime::types::Value::I64(v) => SpectestValue::I64(v),
|
|
wasmer_runtime::types::Value::F32(v) => SpectestValue::F32(v.to_bits()),
|
|
wasmer_runtime::types::Value::F64(v) => SpectestValue::F64(v.to_bits()),
|
|
wasmer_runtime::types::Value::V128(v) => SpectestValue::V128(v),
|
|
}
|
|
}
|
|
|
|
fn convert_wabt_value(other: Value<f32, f64>) -> SpectestValue {
|
|
match other {
|
|
Value::I32(v) => SpectestValue::I32(v),
|
|
Value::I64(v) => SpectestValue::I64(v),
|
|
Value::F32(v) => SpectestValue::F32(v.to_bits()),
|
|
Value::F64(v) => SpectestValue::F64(v.to_bits()),
|
|
Value::V128(v) => SpectestValue::V128(v),
|
|
}
|
|
}
|
|
|
|
fn convert_value(other: Value<f32, f64>) -> wasmer_runtime::types::Value {
|
|
match other {
|
|
Value::I32(v) => wasmer_runtime::types::Value::I32(v),
|
|
Value::I64(v) => wasmer_runtime::types::Value::I64(v),
|
|
Value::F32(v) => wasmer_runtime::types::Value::F32(v),
|
|
Value::F64(v) => wasmer_runtime::types::Value::F64(v),
|
|
Value::V128(v) => wasmer_runtime::types::Value::V128(v),
|
|
}
|
|
}
|
|
|
|
fn to_hex(v: SpectestValue) -> String {
|
|
match v {
|
|
SpectestValue::I32(v) => format!("{:#x}", v),
|
|
SpectestValue::I64(v) => format!("{:#x}", v),
|
|
SpectestValue::F32(v) => format!("{:#x}", v),
|
|
SpectestValue::F64(v) => format!("{:#x}", v),
|
|
SpectestValue::V128(v) => format!("{:#x}", v),
|
|
}
|
|
}
|
|
|
|
fn print(_ctx: &mut Ctx) {
|
|
println!("");
|
|
}
|
|
|
|
fn print_i32(_ctx: &mut Ctx, val: i32) {
|
|
println!("{}", val);
|
|
}
|
|
|
|
fn print_f32(_ctx: &mut Ctx, val: f32) {
|
|
println!("{}", val);
|
|
}
|
|
|
|
fn print_f64(_ctx: &mut Ctx, val: f64) {
|
|
println!("{}", val);
|
|
}
|
|
|
|
fn print_i32_f32(_ctx: &mut Ctx, val: i32, val2: f32) {
|
|
println!("{} {}", val, val2);
|
|
}
|
|
|
|
fn print_f64_f64(_ctx: &mut Ctx, val: f64, val2: f64) {
|
|
println!("{} {}", val, val2);
|
|
}
|
|
|
|
fn get_spectest_import_object(
|
|
registered_modules: &HashMap<String, Arc<Mutex<Instance>>>,
|
|
) -> ImportObject {
|
|
let memory_desc = MemoryDescriptor::new(Pages(1), Some(Pages(2)), false).unwrap();
|
|
let memory = Memory::new(memory_desc).unwrap();
|
|
|
|
let global_i32 = Global::new(wasmer_runtime::types::Value::I32(666));
|
|
let global_f32 = Global::new(wasmer_runtime::types::Value::F32(666.0));
|
|
let global_f64 = Global::new(wasmer_runtime::types::Value::F64(666.0));
|
|
|
|
let table = Table::new(TableDescriptor {
|
|
element: ElementType::Anyfunc,
|
|
minimum: 10,
|
|
maximum: Some(20),
|
|
})
|
|
.unwrap();
|
|
let mut import_object = imports! {
|
|
"spectest" => {
|
|
"print" => func!(print),
|
|
"print_i32" => func!(print_i32),
|
|
"print_f32" => func!(print_f32),
|
|
"print_f64" => func!(print_f64),
|
|
"print_i32_f32" => func!(print_i32_f32),
|
|
"print_f64_f64" => func!(print_f64_f64),
|
|
"table" => table,
|
|
"memory" => memory,
|
|
"global_i32" => global_i32,
|
|
"global_f32" => global_f32,
|
|
"global_f64" => global_f64,
|
|
|
|
},
|
|
};
|
|
|
|
for (name, instance) in registered_modules.iter() {
|
|
import_object.register(name.clone(), Arc::clone(instance));
|
|
}
|
|
import_object
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
enum ExcludeKind {
|
|
Skip,
|
|
Fail,
|
|
}
|
|
|
|
use core::borrow::Borrow;
|
|
use std::fs::File;
|
|
use std::io::{BufRead, BufReader};
|
|
|
|
/// Reads the excludes.txt file into a hash map
|
|
fn read_excludes() -> (HashMap<String, Vec<Exclude>>, HashSet<String>) {
|
|
let mut excludes_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
excludes_path.push("tests");
|
|
excludes_path.push("excludes.txt");
|
|
let input = File::open(excludes_path).unwrap();
|
|
let buffered = BufReader::new(input);
|
|
let mut result = HashMap::new();
|
|
let mut file_excludes = HashSet::new();
|
|
let current_backend = get_compiler_name();
|
|
let current_target_family = get_target_family();
|
|
let current_target_arch = get_target_arch();
|
|
|
|
for line in buffered.lines() {
|
|
let mut line = line.unwrap();
|
|
if line.trim().is_empty() || line.starts_with("#") {
|
|
// ignore line
|
|
} else {
|
|
if line.contains("#") {
|
|
// Allow end of line comment
|
|
let l: Vec<&str> = line.split('#').collect();
|
|
line = l.get(0).unwrap().to_string();
|
|
}
|
|
//println!("exclude line {}", line);
|
|
// <backend>:<exclude-kind>:<test-file-name>:<test-file-line>
|
|
let split: Vec<&str> = line.trim().split(':').collect();
|
|
|
|
let file = *split.get(2).unwrap();
|
|
let exclude = match split.len() {
|
|
0..=3 => panic!("expected at least 4 exclude conditions"),
|
|
4 => Exclude::from(
|
|
*split.get(0).unwrap(),
|
|
*split.get(1).unwrap(),
|
|
*split.get(2).unwrap(),
|
|
*split.get(3).unwrap(),
|
|
"*",
|
|
"*",
|
|
),
|
|
5 => Exclude::from(
|
|
*split.get(0).unwrap(),
|
|
*split.get(1).unwrap(),
|
|
*split.get(2).unwrap(),
|
|
*split.get(3).unwrap(),
|
|
*split.get(4).unwrap(),
|
|
"*",
|
|
),
|
|
6 => Exclude::from(
|
|
*split.get(0).unwrap(),
|
|
*split.get(1).unwrap(),
|
|
*split.get(2).unwrap(),
|
|
*split.get(3).unwrap(),
|
|
*split.get(4).unwrap(),
|
|
*split.get(5).unwrap(),
|
|
),
|
|
_ => panic!("too many exclude conditions {}", split.len()),
|
|
};
|
|
|
|
if exclude.matches_backend(current_backend)
|
|
&& exclude.matches_target_family(current_target_family)
|
|
&& exclude.matches_target_arch(current_target_arch)
|
|
{
|
|
// Skip the whole file for line * and skip
|
|
if exclude.line.is_none() && exclude.exclude_kind == ExcludeKind::Skip {
|
|
file_excludes.insert(file.to_string());
|
|
}
|
|
|
|
if !result.contains_key(file) {
|
|
result.insert(file.to_string(), vec![]);
|
|
}
|
|
result.get_mut(file).unwrap().push(exclude);
|
|
}
|
|
}
|
|
}
|
|
(result, file_excludes)
|
|
}
|
|
|
|
#[test]
|
|
fn test_run_spectests() {
|
|
let mut success = true;
|
|
let mut test_reports = vec![];
|
|
|
|
let (excludes, file_excludes) = read_excludes();
|
|
|
|
let mut glob_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
glob_path.push("spectests");
|
|
glob_path.push("*.wast");
|
|
|
|
let glob_str = glob_path.to_str().unwrap();
|
|
for entry in glob(glob_str).expect("Failed to read glob pattern") {
|
|
match entry {
|
|
Ok(wast_path) => {
|
|
let result = parse_and_run(&wast_path, &file_excludes, &excludes);
|
|
match result {
|
|
Ok(test_report) => {
|
|
if test_report.has_failures() {
|
|
success = false
|
|
}
|
|
test_reports.push(test_report);
|
|
}
|
|
Err(e) => {
|
|
success = false;
|
|
println!("Unexpected test run error: {:?}", e)
|
|
}
|
|
}
|
|
}
|
|
Err(e) => panic!("glob err: {:?}", e),
|
|
}
|
|
}
|
|
|
|
// Print summary
|
|
let mut failures = vec![];
|
|
let mut total_passed = 0;
|
|
let mut total_failed = 0;
|
|
let mut total_allowed_failures = 0;
|
|
for mut test_report in test_reports.into_iter() {
|
|
total_passed += test_report.passed;
|
|
total_failed += test_report.failed;
|
|
total_allowed_failures += test_report.allowed_failure;
|
|
failures.append(&mut test_report.failures);
|
|
}
|
|
|
|
println!("");
|
|
println!("Failures:");
|
|
let backend = get_compiler_name();
|
|
for failure in failures.iter() {
|
|
// To print excludes for all failures:
|
|
println!(
|
|
"{}:fail:{}:{} # {} - {}",
|
|
backend, failure.file, failure.line, failure.kind, failure.message
|
|
);
|
|
}
|
|
println!("");
|
|
println!("");
|
|
println!("Spec tests summary report: ");
|
|
println!(
|
|
"total: {}",
|
|
total_passed + total_failed + total_allowed_failures
|
|
);
|
|
println!("passed: {}", total_passed);
|
|
println!("failed: {}", total_failed);
|
|
println!("allowed failures: {}", total_allowed_failures);
|
|
println!("");
|
|
assert!(success, "tests passed")
|
|
}
|
|
|
|
/// Bit pattern of an f32 value:
|
|
/// 1-bit sign + 8-bit mantissa + 23-bit exponent = 32 bits
|
|
///
|
|
/// Bit pattern of an f64 value:
|
|
/// 1-bit sign + 11-bit mantissa + 52-bit exponent = 64 bits
|
|
///
|
|
/// NOTE: On some old platforms (PA-RISC, some MIPS) quiet NaNs (qNaN) have
|
|
/// their mantissa MSB unset and set for signaling NaNs (sNaN).
|
|
///
|
|
/// Links:
|
|
/// * https://en.wikipedia.org/wiki/Floating-point_arithmetic
|
|
/// * https://github.com/WebAssembly/spec/issues/286
|
|
/// * https://en.wikipedia.org/wiki/NaN
|
|
///
|
|
pub trait NaNCheck {
|
|
fn is_quiet_nan(&self) -> bool;
|
|
fn is_canonical_nan(&self) -> bool;
|
|
}
|
|
|
|
impl NaNCheck for f32 {
|
|
/// The MSB of the mantissa must be set for a NaN to be a quiet NaN.
|
|
fn is_quiet_nan(&self) -> bool {
|
|
let mantissa_msb = 0b1 << 22;
|
|
self.is_nan() && (self.to_bits() & mantissa_msb) != 0
|
|
}
|
|
|
|
/// For a NaN to be canonical, the MSB of the mantissa must be set and
|
|
/// all other mantissa bits must be unset.
|
|
fn is_canonical_nan(&self) -> bool {
|
|
return self.to_bits() == 0xFFC0_0000 || self.to_bits() == 0x7FC0_0000;
|
|
}
|
|
}
|
|
|
|
impl NaNCheck for f64 {
|
|
/// The MSB of the mantissa must be set for a NaN to be a quiet NaN.
|
|
fn is_quiet_nan(&self) -> bool {
|
|
let mantissa_msb = 0b1 << 51;
|
|
self.is_nan() && (self.to_bits() & mantissa_msb) != 0
|
|
}
|
|
|
|
/// For a NaN to be canonical, the MSB of the mantissa must be set and
|
|
/// all other mantissa bits must be unset.
|
|
fn is_canonical_nan(&self) -> bool {
|
|
self.to_bits() == 0x7FF8_0000_0000_0000 || self.to_bits() == 0xFFF8_0000_0000_0000
|
|
}
|
|
}
|
|
}
|