cranelift-filetest: Add support for Wasm-to-CLIF translation filetests (#5412)
This adds support for `.wat` tests in `cranelift-filetest`. The test runner translates the WAT to Wasm and then uses `cranelift-wasm` to translate the Wasm to CLIF. These tests are always precise output tests. The test expectations can be updated by running tests with the `CRANELIFT_TEST_BLESS=1` environment variable set, similar to our compile precise output tests. The test's expected output is contained in the last comment in the test file. The tests allow for configuring the kinds of heaps used to implement Wasm linear memory via TOML in a `;;!` comment at the start of the test. To get ISA and Cranelift flags parsing available in the filetests crate, I had to move the `parse_sets_and_triple` helper from the `cranelift-tools` binary crate to the `cranelift-reader` crate, where I think it logically fits. Additionally, I had to make some more bits of `cranelift-wasm`'s dummy environment `pub` so that I could properly wrap and compose it with the environment used for the `.wat` tests. I don't think this is a big deal, but if we eventually want to clean this stuff up, we can probably remove the dummy environments completely, remove `translate_module`, and fold them into these new test environments and test runner (since Wasmtime isn't using those things anyways).
This commit is contained in:
110
cranelift/filetests/src/test_wasm.rs
Normal file
110
cranelift/filetests/src/test_wasm.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
//! 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")?;
|
||||
|
||||
config
|
||||
.validate()
|
||||
.context("test configuration is malformed")?;
|
||||
|
||||
let wasm = wat::parse_str(wat).context("failed to parse the test WAT")?;
|
||||
wasmparser::validate(&wasm).context("test WAT failed to validate")?;
|
||||
|
||||
let parsed = cranelift_reader::parse_sets_and_triple(&config.settings, &config.target)
|
||||
.context("invalid ISA target or Cranelift settings")?;
|
||||
let fisa = parsed.as_fisa();
|
||||
ensure!(
|
||||
fisa.isa.is_some(),
|
||||
"Running `.wat` tests requires specifying an ISA"
|
||||
);
|
||||
|
||||
let mut env = ModuleEnv::new(fisa.isa.as_ref().unwrap().frontend_config(), config);
|
||||
|
||||
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() {
|
||||
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();
|
||||
std::fs::write(path, new_wat_lines.join("\n"))
|
||||
.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")
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user