1use crate::utils::{iterate_files, read_to_string};
4use anyhow::Result;
5use clap::Parser;
6use cranelift_codegen::isa::{CallConv, OwnedTargetIsa};
7use cranelift_filetests::TestFileCompiler;
8use cranelift_native::builder as host_isa_builder;
9use cranelift_reader::{parse_run_command, parse_test, Details, IsaSpec, ParseOptions};
10use std::path::{Path, PathBuf};
11use target_lexicon::{Triple, HOST};
12
13#[derive(Parser)]
15pub struct Options {
16 #[arg(required = true)]
18 files: Vec<PathBuf>,
19
20 #[arg(short, long)]
22 verbose: bool,
23}
24
25pub fn run(options: &Options) -> Result<()> {
26 let stdin_exist = options
27 .files
28 .iter()
29 .find(|file| *file == Path::new("-"))
30 .is_some();
31 let filtered_files = options
32 .files
33 .iter()
34 .cloned()
35 .filter(|file| *file != Path::new("-"))
36 .collect::<Vec<_>>();
37 let mut total = 0;
38 let mut errors = 0;
39 let mut special_files: Vec<PathBuf> = vec![];
40 if stdin_exist {
41 special_files.push("-".into());
42 }
43 for file in iterate_files(&filtered_files).chain(special_files) {
44 total += 1;
45 match run_single_file(&file) {
46 Ok(_) => {
47 if options.verbose {
48 println!("{}", file.to_string_lossy());
49 }
50 }
51 Err(e) => {
52 if options.verbose {
53 println!("{}: {}", file.to_string_lossy(), e);
54 }
55 errors += 1;
56 }
57 }
58 }
59
60 if options.verbose {
61 match total {
62 0 => println!("0 files"),
63 1 => println!("1 file"),
64 n => println!("{n} files"),
65 }
66 }
67
68 match errors {
69 0 => Ok(()),
70 1 => anyhow::bail!("1 failure"),
71 n => anyhow::bail!("{} failures", n),
72 }
73}
74
75fn run_single_file(path: &PathBuf) -> Result<()> {
77 let file_contents = read_to_string(&path)?;
78 run_file_contents(file_contents)
79}
80
81fn run_file_contents(file_contents: String) -> Result<()> {
83 let options = ParseOptions {
84 default_calling_convention: CallConv::triple_default(&Triple::host()), ..ParseOptions::default()
86 };
87 let test_file = parse_test(&file_contents, options)?;
88 let isa = create_target_isa(&test_file.isa_spec)?;
89 let mut tfc = TestFileCompiler::new(isa);
90 tfc.add_testfile(&test_file)?;
91 let compiled = tfc.compile()?;
92
93 for (func, Details { comments, .. }) in test_file.functions {
94 for comment in comments {
95 if let Some(command) = parse_run_command(comment.text, &func.signature)? {
96 let trampoline = compiled.get_trampoline(&func).unwrap();
97
98 command
99 .run(|_, args| Ok(trampoline.call(args)))
100 .map_err(|s| anyhow::anyhow!("{}", s))?;
101 }
102 }
103 }
104 Ok(())
105}
106
107fn create_target_isa(isa_spec: &IsaSpec) -> Result<OwnedTargetIsa> {
109 let builder = host_isa_builder().map_err(|s| anyhow::anyhow!("{}", s))?;
110 match *isa_spec {
111 IsaSpec::None(ref flags) => {
112 Ok(builder.finish(flags.clone())?)
114 }
115 IsaSpec::Some(ref isas) => {
116 for isa in isas {
117 if isa.triple().architecture == HOST.architecture {
118 return Ok(builder.finish(isa.flags().clone())?);
119 }
120 }
121 anyhow::bail!(
122 "The target ISA specified in the file is not compatible with the host ISA"
123 )
124 }
125 }
126}
127
128#[cfg(test)]
129mod test {
130 use super::*;
131
132 #[test]
133 fn nop() {
134 if cranelift_native::builder().is_err() {
135 return;
136 }
137 let code = String::from(
138 "
139 function %test() -> i8 {
140 block0:
141 nop
142 v1 = iconst.i8 -1
143 return v1
144 }
145 ; run
146 ",
147 );
148 run_file_contents(code).unwrap()
149 }
150}