Use the capstone library to disassemble precise-output tests, in addition to pretty-printing their vcode.
135 lines
4.5 KiB
Rust
135 lines
4.5 KiB
Rust
//! Test runner for `.wat` files to exercise CLIF-to-Wasm translations.
|
|
|
|
mod config;
|
|
mod env;
|
|
|
|
use anyhow::{bail, ensure, Context, Result};
|
|
use config::TestConfig;
|
|
use env::ModuleEnv;
|
|
use similar::TextDiff;
|
|
use std::{fmt::Write, path::Path};
|
|
|
|
/// Run one `.wat` test.
|
|
pub fn run(path: &Path, wat: &str) -> Result<()> {
|
|
debug_assert_eq!(path.extension().unwrap_or_default(), "wat");
|
|
|
|
// The test config source is the leading lines of the WAT file that are
|
|
// prefixed with `;;!`.
|
|
let config_lines: Vec<_> = wat
|
|
.lines()
|
|
.take_while(|l| l.starts_with(";;!"))
|
|
.map(|l| &l[3..])
|
|
.collect();
|
|
let config_text = config_lines.join("\n");
|
|
|
|
let config: TestConfig =
|
|
toml::from_str(&config_text).context("failed to parse the test configuration")?;
|
|
log::debug!("Wasm test config = {config:#?}");
|
|
|
|
config
|
|
.validate()
|
|
.context("test configuration is malformed")?;
|
|
|
|
let parsed = cranelift_reader::parse_sets_and_triple(&config.settings, &config.target)
|
|
.context("invalid ISA target or Cranelift settings")?;
|
|
let flags_or_isa = parsed.as_fisa();
|
|
ensure!(
|
|
flags_or_isa.isa.is_some(),
|
|
"Running `.wat` tests requires specifying an ISA"
|
|
);
|
|
let isa = flags_or_isa.isa.unwrap();
|
|
|
|
let mut env = ModuleEnv::new(isa, config.clone());
|
|
|
|
let wasm = wat::parse_str(wat).context("failed to parse the test WAT")?;
|
|
let mut validator = wasmparser::Validator::new_with_features(
|
|
cranelift_wasm::ModuleEnvironment::wasm_features(&env),
|
|
);
|
|
validator
|
|
.validate_all(&wasm)
|
|
.context("test WAT failed to validate")?;
|
|
|
|
cranelift_wasm::translate_module(&wasm, &mut env)
|
|
.context("failed to translate the test case into CLIF")?;
|
|
|
|
let mut actual = String::new();
|
|
for (_index, func) in env.inner.info.function_bodies.iter() {
|
|
if config.compile {
|
|
let mut ctx = cranelift_codegen::Context::for_function(func.clone());
|
|
ctx.set_disasm(true);
|
|
let code = ctx
|
|
.compile(isa)
|
|
.map_err(|e| crate::pretty_anyhow_error(&e.func, e.inner))?;
|
|
writeln!(&mut actual, "function {}:", func.name).unwrap();
|
|
writeln!(&mut actual, "{}", code.vcode.as_ref().unwrap()).unwrap();
|
|
} else if config.optimize {
|
|
let mut ctx = cranelift_codegen::Context::for_function(func.clone());
|
|
ctx.optimize(isa)
|
|
.map_err(|e| crate::pretty_anyhow_error(&ctx.func, e))?;
|
|
writeln!(&mut actual, "{}", ctx.func.display()).unwrap();
|
|
} else {
|
|
writeln!(&mut actual, "{}", func.display()).unwrap();
|
|
}
|
|
}
|
|
let actual = actual.trim();
|
|
log::debug!("=== actual ===\n{actual}");
|
|
|
|
// The test's expectation is the final comment.
|
|
let mut expected_lines: Vec<_> = wat
|
|
.lines()
|
|
.rev()
|
|
.take_while(|l| l.starts_with(";;"))
|
|
.map(|l| {
|
|
if l.starts_with(";; ") {
|
|
&l[3..]
|
|
} else {
|
|
&l[2..]
|
|
}
|
|
})
|
|
.collect();
|
|
expected_lines.reverse();
|
|
let expected = expected_lines.join("\n");
|
|
let expected = expected.trim();
|
|
log::debug!("=== expected ===\n{expected}");
|
|
|
|
if actual == expected {
|
|
return Ok(());
|
|
}
|
|
|
|
if std::env::var("CRANELIFT_TEST_BLESS").unwrap_or_default() == "1" {
|
|
let old_expectation_line_count = wat
|
|
.lines()
|
|
.rev()
|
|
.take_while(|l| l.starts_with(";;"))
|
|
.count();
|
|
let old_wat_line_count = wat.lines().count();
|
|
let new_wat_lines: Vec<_> = wat
|
|
.lines()
|
|
.take(old_wat_line_count - old_expectation_line_count)
|
|
.map(|l| l.to_string())
|
|
.chain(actual.lines().map(|l| {
|
|
if l.is_empty() {
|
|
";;".to_string()
|
|
} else {
|
|
format!(";; {l}")
|
|
}
|
|
}))
|
|
.collect();
|
|
let mut new_wat = new_wat_lines.join("\n");
|
|
new_wat.push('\n');
|
|
std::fs::write(path, new_wat)
|
|
.with_context(|| format!("failed to write file: {}", path.display()))?;
|
|
return Ok(());
|
|
}
|
|
|
|
bail!(
|
|
"Did not get the expected CLIF translation:\n\n\
|
|
{}\n\n\
|
|
Note: You can re-run with the `CRANELIFT_TEST_BLESS=1` environment\n\
|
|
variable set to update test expectations.",
|
|
TextDiff::from_lines(expected, actual)
|
|
.unified_diff()
|
|
.header("expected", "actual")
|
|
)
|
|
}
|