Add test run to cranelift-filetests to allow executing CLIF (#890)

* Add ability to run CLIF IR using `clif-util run [-v] {file}` and add `test run` to cranelift-filetests to allow executing CLIF

This re-factors the compile/execute parts to a FunctionRunner that is shared between cranelift-filetests and clif-util. CLIF can be now be run using `clif-util run` as well as during `clif-util test` for files with a `test run` header. As before, only functions suffixed with a `run` comment are executed. The `run: fn(...) == ...` expression syntax is left for a subsequent change.
This commit is contained in:
Andrew Brown
2019-08-21 09:03:09 -07:00
committed by Benjamin Bouvier
parent 276bb5e26d
commit ff3c44385c
10 changed files with 330 additions and 2 deletions

View File

@@ -33,6 +33,7 @@ mod cat;
mod compile;
mod disasm;
mod print_cfg;
mod run;
mod utils;
/// A command either succeeds or fails with an error message.
@@ -162,6 +163,13 @@ fn main() {
.arg(add_input_file_arg())
.arg(add_debug_flag()),
)
.subcommand(
SubCommand::with_name("run")
.about("Execute CLIF code and verify with test expressions")
.arg(add_verbose_flag())
.arg(add_input_file_arg())
.arg(add_debug_flag()),
)
.subcommand(
SubCommand::with_name("cat")
.about("Outputs .clif file")
@@ -224,6 +232,14 @@ fn main() {
)
.map(|_time| ())
}
("run", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug"));
run::run(
get_vec(rest_cmd.values_of("file")),
rest_cmd.is_present("verbose"),
)
.map(|_time| ())
}
("pass", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug"));

118
cranelift/src/run.rs Normal file
View File

@@ -0,0 +1,118 @@
//! CLI tool to compile Cranelift IR files to native code in memory and execute them.
use crate::utils::read_to_string;
use cranelift_codegen::isa::TargetIsa;
use cranelift_filetests::FunctionRunner;
use cranelift_native::builder as host_isa_builder;
use cranelift_reader::{parse_test, Details, IsaSpec};
use std::path::PathBuf;
use walkdir::WalkDir;
pub fn run(files: Vec<String>, flag_print: bool) -> Result<(), String> {
let mut total = 0;
let mut errors = 0;
for file in iterate_files(files) {
total += 1;
match run_single_file(&file) {
Ok(_) => {
if flag_print {
println!("{}", file.to_string_lossy());
}
}
Err(e) => {
if flag_print {
println!("{}: {}", file.to_string_lossy(), e);
}
errors += 1;
}
}
}
if flag_print {
match total {
0 => println!("0 files"),
1 => println!("1 file"),
n => println!("{} files", n),
}
}
match errors {
0 => Ok(()),
1 => Err(String::from("1 failure")),
n => Err(format!("{} failures", n)),
}
}
/// Iterate over all of the files passed as arguments, recursively iterating through directories
fn iterate_files(files: Vec<String>) -> impl Iterator<Item = PathBuf> {
files
.into_iter()
.flat_map(WalkDir::new)
.filter(|f| match f {
Ok(d) => {
// filter out hidden files (starting with .)
!d.file_name().to_str().map_or(false, |s| s.starts_with("."))
// filter out directories
&& !d.file_type().is_dir()
}
Err(e) => {
println!("Unable to read file: {}", e);
false
}
})
.map(|f| {
f.expect("This should not happen: we have already filtered out the errors")
.into_path()
})
}
/// Run all functions in a file that are succeeded by "run:" comments
fn run_single_file(path: &PathBuf) -> Result<(), String> {
let file_contents = read_to_string(&path).map_err(|e| e.to_string())?;
run_file_contents(file_contents)
}
/// Main body of `run_single_file` separated for testing
fn run_file_contents(file_contents: String) -> Result<(), String> {
let test_file = parse_test(&file_contents, None, None).map_err(|e| e.to_string())?;
for (func, Details { comments, .. }) in test_file.functions {
if comments.iter().any(|c| c.text.contains("run")) {
let isa = create_target_isa(&test_file.isa_spec)?;
FunctionRunner::new(func, isa).run()?
}
}
Ok(())
}
/// Build an ISA based on the current machine running this code (the host)
fn create_target_isa(isa_spec: &IsaSpec) -> Result<Box<dyn TargetIsa>, String> {
if let IsaSpec::None(flags) = isa_spec {
// build an ISA for the current machine
let builder = host_isa_builder()?;
Ok(builder.finish(flags.clone()))
} else {
Err(String::from("A target ISA was specified in the file but should not have been--only the host ISA can be used for running CLIF files"))?
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn nop() {
let code = String::from(
"
function %test() -> b8 system_v {
ebb0:
nop
v1 = bconst.b8 true
return v1
}
; run
",
);
run_file_contents(code).unwrap()
}
}