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).
113 lines
3.3 KiB
Rust
113 lines
3.3 KiB
Rust
use anyhow::{Context as _, Result};
|
|
use clap::Parser;
|
|
use cranelift_codegen::Context;
|
|
use cranelift_reader::parse_sets_and_triple;
|
|
use cranelift_wasm::DummyEnvironment;
|
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
|
use std::path::{Path, PathBuf};
|
|
use std::{fs, io};
|
|
|
|
static WASM_MAGIC: &[u8] = &[0x00, 0x61, 0x73, 0x6D];
|
|
|
|
/// Harvest candidates for superoptimization from a Wasm or Clif file.
|
|
///
|
|
/// Candidates are emitted in Souper's text format:
|
|
/// <https://github.com/google/souper>
|
|
#[derive(Parser)]
|
|
pub struct Options {
|
|
/// Specify an input file to be used. Use '-' for stdin.
|
|
input: PathBuf,
|
|
|
|
/// Specify the output file to be used. Use '-' for stdout.
|
|
#[clap(short, long, default_value("-"))]
|
|
output: PathBuf,
|
|
|
|
/// Configure Cranelift settings
|
|
#[clap(long = "set")]
|
|
settings: Vec<String>,
|
|
|
|
/// Specify the Cranelift target
|
|
#[clap(long = "target")]
|
|
target: String,
|
|
}
|
|
|
|
pub fn run(options: &Options) -> Result<()> {
|
|
let parsed = parse_sets_and_triple(&options.settings, &options.target)?;
|
|
let fisa = parsed.as_fisa();
|
|
if fisa.isa.is_none() {
|
|
anyhow::bail!("`souper-harvest` requires a target isa");
|
|
}
|
|
|
|
let stdin = io::stdin();
|
|
let mut input: Box<dyn io::BufRead> = if options.input == Path::new("-") {
|
|
Box::new(stdin.lock())
|
|
} else {
|
|
Box::new(io::BufReader::new(
|
|
fs::File::open(&options.input).context("failed to open input file")?,
|
|
))
|
|
};
|
|
|
|
let mut output: Box<dyn io::Write + Send> = if options.output == Path::new("-") {
|
|
Box::new(io::stdout())
|
|
} else {
|
|
Box::new(io::BufWriter::new(
|
|
fs::File::create(&options.output).context("failed to create output file")?,
|
|
))
|
|
};
|
|
|
|
let mut contents = vec![];
|
|
input
|
|
.read_to_end(&mut contents)
|
|
.context("failed to read input file")?;
|
|
|
|
let funcs = if &contents[..WASM_MAGIC.len()] == WASM_MAGIC {
|
|
let mut dummy_environ = DummyEnvironment::new(fisa.isa.unwrap().frontend_config(), false);
|
|
cranelift_wasm::translate_module(&contents, &mut dummy_environ)
|
|
.context("failed to translate Wasm module to clif")?;
|
|
dummy_environ
|
|
.info
|
|
.function_bodies
|
|
.iter()
|
|
.map(|(_, f)| f.clone())
|
|
.collect()
|
|
} else {
|
|
let contents = String::from_utf8(contents)?;
|
|
cranelift_reader::parse_functions(&contents)?
|
|
};
|
|
|
|
let (send, recv) = std::sync::mpsc::channel::<String>();
|
|
|
|
let writing_thread = std::thread::spawn(move || -> Result<()> {
|
|
for lhs in recv {
|
|
output
|
|
.write_all(lhs.as_bytes())
|
|
.context("failed to write to output file")?;
|
|
}
|
|
Ok(())
|
|
});
|
|
|
|
funcs
|
|
.into_par_iter()
|
|
.map_with(send, move |send, func| {
|
|
let mut ctx = Context::new();
|
|
ctx.func = func;
|
|
|
|
ctx.compute_cfg();
|
|
ctx.preopt(fisa.isa.unwrap())
|
|
.context("failed to run preopt")?;
|
|
|
|
ctx.souper_harvest(send)
|
|
.context("failed to run souper harvester")?;
|
|
|
|
Ok(())
|
|
})
|
|
.collect::<Result<()>>()?;
|
|
|
|
match writing_thread.join() {
|
|
Ok(result) => result?,
|
|
Err(e) => std::panic::resume_unwind(e),
|
|
}
|
|
|
|
Ok(())
|
|
}
|