xref: /wasmtime-44.0.1/cranelift/src/run.rs (revision 557cc2d6)
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