1 //! CLI tool to compile Cranelift IR files to native code in memory and execute them.
2
3 use crate::utils::{iterate_files, read_to_string};
4 use anyhow::Result;
5 use clap::Parser;
6 use cranelift_codegen::isa::{CallConv, OwnedTargetIsa};
7 use cranelift_filetests::TestFileCompiler;
8 use cranelift_native::builder as host_isa_builder;
9 use cranelift_reader::{Details, IsaSpec, ParseOptions, parse_run_command, parse_test};
10 use std::path::{Path, PathBuf};
11 use target_lexicon::{HOST, Triple};
12
13 /// Execute clif code and verify with test expressions
14 #[derive(Parser)]
15 pub struct Options {
16 /// Specify an input file to be used. Use '-' for stdin.
17 #[arg(required = true)]
18 files: Vec<PathBuf>,
19
20 /// Be more verbose
21 #[arg(short, long)]
22 verbose: bool,
23 }
24
run(options: &Options) -> Result<()>25 pub 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!("{n} failures"),
72 }
73 }
74
75 /// Run all functions in a file that are succeeded by "run:" comments
run_single_file(path: &PathBuf) -> Result<()>76 fn run_single_file(path: &PathBuf) -> Result<()> {
77 let file_contents = read_to_string(&path)?;
78 run_file_contents(file_contents)
79 }
80
81 /// Main body of `run_single_file` separated for testing
run_file_contents(file_contents: String) -> Result<()>82 fn run_file_contents(file_contents: String) -> Result<()> {
83 let options = ParseOptions {
84 default_calling_convention: CallConv::triple_default(&Triple::host()), // use the host's default calling convention
85 ..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(&compiled, args)))
100 .map_err(|s| anyhow::anyhow!("{s}"))?;
101 }
102 }
103 }
104 Ok(())
105 }
106
107 /// Build an ISA based on the current machine running this code (the host)
create_target_isa(isa_spec: &IsaSpec) -> Result<OwnedTargetIsa>108 fn 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 // build an ISA for the current machine
113 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)]
129 mod test {
130 use super::*;
131
132 #[test]
nop()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 }
151