* 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
142 lines
4.5 KiB
Rust
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");
|
|
}
|
|
})
|
|
}
|