Files
wasmtime/cranelift/filetests/src/test_compile.rs
Benjamin Bouvier ff37c9d8a4 [cranelift] Rejigger the compile API (#4540)
* Move `emit_to_memory` to `MachCompileResult`

This small refactoring makes it clearer to me that emitting to memory
doesn't require anything else from the compilation `Context`. While it's
a trivial change, it's a small public API change that shouldn't cause
too much trouble, and doesn't seem RFC-worthy. Happy to hear different
opinions about this, though!

* hide the MachCompileResult behind a method

* Add a `CompileError` wrapper type that references a `Function`

* Rename MachCompileResult to CompiledCode

* Additionally remove the last unsafe API in cranelift-codegen
2022-08-02 12:05:40 -07:00

142 lines
4.5 KiB
Rust

//! Test command for testing the code generator pipeline
//!
//! The `compile` test command runs each function through the full code generator pipeline
use crate::subtest::{run_filecheck, Context, SubTest};
use anyhow::{bail, Result};
use cranelift_codegen::ir;
use cranelift_reader::{TestCommand, TestOption};
use log::info;
use similar::TextDiff;
use std::borrow::Cow;
use std::env;
struct TestCompile {
/// Flag indicating that the text expectation, comments after the function,
/// must be a precise 100% match on the compiled output of the function.
/// This test assertion is also automatically-update-able to allow tweaking
/// the code generator and easily updating all affected tests.
precise_output: bool,
}
pub fn subtest(parsed: &TestCommand) -> Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "compile");
let mut test = TestCompile {
precise_output: false,
};
for option in parsed.options.iter() {
match option {
TestOption::Flag("precise-output") => test.precise_output = true,
_ => anyhow::bail!("unknown option on {}", parsed),
}
}
Ok(Box::new(test))
}
impl SubTest for TestCompile {
fn name(&self) -> &'static str {
"compile"
}
fn is_mutating(&self) -> bool {
true
}
fn needs_isa(&self) -> bool {
true
}
fn run(&self, func: Cow<ir::Function>, context: &Context) -> Result<()> {
let isa = context.isa.expect("compile needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
// With `MachBackend`s, we need to explicitly request dissassembly results.
comp_ctx.set_disasm(true);
let compiled_code = comp_ctx
.compile(isa)
.map_err(|e| crate::pretty_anyhow_error(&e.func, e.inner))?;
let total_size = compiled_code.code_info().total_size;
let disasm = compiled_code.disasm.as_ref().unwrap();
info!("Generated {} bytes of code:\n{}", total_size, disasm);
if self.precise_output {
check_precise_output(&disasm, context)
} else {
run_filecheck(&disasm, context)
}
}
}
fn check_precise_output(text: &str, context: &Context) -> Result<()> {
let actual = text.lines().collect::<Vec<_>>();
// Use the comments after the function to build the test expectation.
let expected = context
.details
.comments
.iter()
.filter(|c| !c.text.starts_with(";;"))
.map(|c| c.text.strip_prefix("; ").unwrap_or(c.text))
.collect::<Vec<_>>();
// If the expectation matches what we got, then there's nothing to do.
if actual == expected {
return Ok(());
}
// If we're supposed to automatically update the test, then do so here.
if env::var("CRANELIFT_TEST_BLESS").unwrap_or(String::new()) == "1" {
return update_test(&actual, context);
}
// Otherwise this test has failed, and we can print out as such.
bail!(
"compilation of function on line {} does not match\n\
the text expectation\n\
\n\
{}\n\
\n\
This test assertion can be automatically updated by setting the\n\
CRANELIFT_TEST_BLESS=1 environment variable when running this test.
",
context.details.location.line_number,
TextDiff::from_slices(&expected, &actual)
.unified_diff()
.header("expected", "actual")
)
}
fn update_test(output: &[&str], context: &Context) -> Result<()> {
context
.file_update
.update_at(&context.details.location, |new_test, old_test| {
// blank newline after the function
new_test.push_str("\n");
// Splice in the test output
for output in output {
new_test.push_str("; ");
new_test.push_str(output);
new_test.push_str("\n");
}
// blank newline after test assertion
new_test.push_str("\n");
// Drop all remaining commented lines (presumably the old test expectation),
// but after we hit a real line then we push all remaining lines.
let mut in_next_function = false;
for line in old_test {
if !in_next_function && (line.trim().is_empty() || line.starts_with(";")) {
continue;
}
in_next_function = true;
new_test.push_str(line);
new_test.push_str("\n");
}
})
}