use crate::utils::{iterate_files, read_to_string};
use anyhow::Result;
use clap::Parser;
use cranelift_codegen::isa::{CallConv, OwnedTargetIsa};
use cranelift_filetests::TestFileCompiler;
use cranelift_native::builder as host_isa_builder;
use cranelift_reader::{Details, IsaSpec, ParseOptions, parse_run_command, parse_test};
use std::path::{Path, PathBuf};
use target_lexicon::{HOST, Triple};
#[derive(Parser)]
pub struct Options {
#[arg(required = true)]
files: Vec<PathBuf>,
#[arg(short, long)]
verbose: bool,
}
pub fn run(options: &Options) -> Result<()> {
let stdin_exist = options
.files
.iter()
.find(|file| *file == Path::new("-"))
.is_some();
let filtered_files = options
.files
.iter()
.cloned()
.filter(|file| *file != Path::new("-"))
.collect::<Vec<_>>();
let mut total = 0;
let mut errors = 0;
let mut special_files: Vec<PathBuf> = vec![];
if stdin_exist {
special_files.push("-".into());
}
for file in iterate_files(&filtered_files).chain(special_files) {
total += 1;
match run_single_file(&file) {
Ok(_) => {
if options.verbose {
println!("{}", file.to_string_lossy());
}
}
Err(e) => {
if options.verbose {
println!("{}: {}", file.to_string_lossy(), e);
}
errors += 1;
}
}
}
if options.verbose {
match total {
0 => println!("0 files"),
1 => println!("1 file"),
n => println!("{n} files"),
}
}
match errors {
0 => Ok(()),
1 => anyhow::bail!("1 failure"),
n => anyhow::bail!("{} failures", n),
}
}
fn run_single_file(path: &PathBuf) -> Result<()> {
let file_contents = read_to_string(&path)?;
run_file_contents(file_contents)
}
fn run_file_contents(file_contents: String) -> Result<()> {
let options = ParseOptions {
default_calling_convention: CallConv::triple_default(&Triple::host()),
..ParseOptions::default()
};
let test_file = parse_test(&file_contents, options)?;
let isa = create_target_isa(&test_file.isa_spec)?;
let mut tfc = TestFileCompiler::new(isa);
tfc.add_testfile(&test_file)?;
let compiled = tfc.compile()?;
for (func, Details { comments, .. }) in test_file.functions {
for comment in comments {
if let Some(command) = parse_run_command(comment.text, &func.signature)? {
let trampoline = compiled.get_trampoline(&func).unwrap();
command
.run(|_, args| Ok(trampoline.call(&compiled, args)))
.map_err(|s| anyhow::anyhow!("{}", s))?;
}
}
}
Ok(())
}
fn create_target_isa(isa_spec: &IsaSpec) -> Result<OwnedTargetIsa> {
let builder = host_isa_builder().map_err(|s| anyhow::anyhow!("{}", s))?;
match *isa_spec {
IsaSpec::None(ref flags) => {
Ok(builder.finish(flags.clone())?)
}
IsaSpec::Some(ref isas) => {
for isa in isas {
if isa.triple().architecture == HOST.architecture {
return Ok(builder.finish(isa.flags().clone())?);
}
}
anyhow::bail!(
"The target ISA specified in the file is not compatible with the host ISA"
)
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn nop() {
if cranelift_native::builder().is_err() {
return;
}
let code = String::from(
"
function %test() -> i8 {
block0:
nop
v1 = iconst.i8 -1
return v1
}
; run
",
);
run_file_contents(code).unwrap()
}
}