1 //! CLI tool to interpret Cranelift IR files. 2 3 use crate::utils::iterate_files; 4 use clap::Parser; 5 use cranelift_interpreter::environment::FunctionStore; 6 use cranelift_interpreter::interpreter::{Interpreter, InterpreterState}; 7 use cranelift_interpreter::step::ControlFlow; 8 use cranelift_reader::{parse_run_command, parse_test, ParseError, ParseOptions}; 9 use std::path::PathBuf; 10 use std::{fs, io}; 11 use thiserror::Error; 12 13 /// Interpret clif code 14 #[derive(Parser)] 15 pub struct Options { 16 /// Specify an input file to be used. Use '-' for stdin. 17 #[clap(required = true)] 18 files: Vec<PathBuf>, 19 20 /// Enable debug output on stderr/stdout 21 #[clap(short)] 22 debug: bool, 23 24 /// Be more verbose 25 #[clap(short, long)] 26 verbose: bool, 27 } 28 29 /// Run files through the Cranelift interpreter, interpreting any functions with annotations. 30 pub fn run(options: &Options) -> anyhow::Result<()> { 31 crate::handle_debug_flag(options.debug); 32 33 let mut total = 0; 34 let mut errors = 0; 35 for file in iterate_files(&options.files) { 36 total += 1; 37 let runner = FileInterpreter::from_path(file)?; 38 match runner.run() { 39 Ok(_) => { 40 if options.verbose { 41 println!("{}", runner.path()); 42 } 43 } 44 Err(e) => { 45 if options.verbose { 46 println!("{}: {}", runner.path(), e.to_string()); 47 } 48 errors += 1; 49 } 50 } 51 } 52 53 if options.verbose { 54 match total { 55 0 => println!("0 files"), 56 1 => println!("1 file"), 57 n => println!("{} files", n), 58 } 59 } 60 61 match errors { 62 0 => Ok(()), 63 1 => anyhow::bail!("1 failure"), 64 n => anyhow::bail!("{} failures", n), 65 } 66 } 67 68 /// Contains CLIF code that can be executed with [FileInterpreter::run]. 69 pub struct FileInterpreter { 70 path: Option<PathBuf>, 71 contents: String, 72 } 73 74 impl FileInterpreter { 75 /// Construct a file runner from a CLIF file path. 76 pub fn from_path(path: impl Into<PathBuf>) -> Result<Self, io::Error> { 77 let path = path.into(); 78 log::trace!("New file runner from path: {}:", path.to_string_lossy()); 79 let contents = fs::read_to_string(&path)?; 80 Ok(Self { 81 path: Some(path), 82 contents, 83 }) 84 } 85 86 /// Construct a file runner from a CLIF code string. Currently only used for testing. 87 #[cfg(test)] 88 pub fn from_inline_code(contents: String) -> Self { 89 log::trace!("New file runner from inline code: {}:", &contents[..20]); 90 Self { 91 path: None, 92 contents, 93 } 94 } 95 96 /// Return the path of the file runner or `[inline code]`. 97 pub fn path(&self) -> String { 98 match self.path { 99 None => "[inline code]".to_string(), 100 Some(ref p) => p.to_string_lossy().to_string(), 101 } 102 } 103 104 /// Run the file; this searches for annotations like `; run: %fn0(42)` or 105 /// `; test: %fn0(42) == 2` and executes them, performing any test comparisons if necessary. 106 pub fn run(&self) -> Result<(), FileInterpreterFailure> { 107 // parse file 108 let test = parse_test(&self.contents, ParseOptions::default()) 109 .map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?; 110 111 // collect functions 112 let mut env = FunctionStore::default(); 113 let mut commands = vec![]; 114 for (func, details) in test.functions.iter() { 115 for comment in &details.comments { 116 if let Some(command) = parse_run_command(comment.text, &func.signature) 117 .map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))? 118 { 119 commands.push(command); 120 } 121 } 122 // Note: func.name may truncate the function name 123 env.add(func.name.to_string(), func); 124 } 125 126 // Run assertion commands 127 for command in commands { 128 command 129 .run(|func_name, args| { 130 // Because we have stored function names with a leading %, we need to re-add it. 131 let func_name = &format!("%{}", func_name); 132 let state = InterpreterState::default().with_function_store(env.clone()); 133 match Interpreter::new(state).call_by_name(func_name, args) { 134 Ok(ControlFlow::Return(results)) => Ok(results.to_vec()), 135 Ok(_) => panic!("Unexpected returned control flow--this is likely a bug."), 136 Err(t) => Err(t.to_string()), 137 } 138 }) 139 .map_err(|s| FileInterpreterFailure::FailedExecution(s))?; 140 } 141 142 Ok(()) 143 } 144 } 145 146 /// Possible sources of failure in this file. 147 #[derive(Error, Debug)] 148 pub enum FileInterpreterFailure { 149 #[error("failure reading file")] 150 Io(#[from] io::Error), 151 #[error("failure parsing file {0}: {1}")] 152 ParsingClif(String, ParseError), 153 #[error("failed to run function: {0}")] 154 FailedExecution(String), 155 } 156 157 #[cfg(test)] 158 mod test { 159 use super::*; 160 161 #[test] 162 fn nop() { 163 let code = String::from( 164 " 165 function %test() -> b8 { 166 block0: 167 nop 168 v1 = bconst.b8 true 169 v2 = iconst.i8 42 170 return v1 171 } 172 ; run: %test() == true 173 ", 174 ); 175 FileInterpreter::from_inline_code(code).run().unwrap() 176 } 177 178 #[test] 179 fn filetests() { 180 run(&Options { 181 files: vec![PathBuf::from("../filetests/filetests/interpreter")], 182 debug: true, 183 verbose: true, 184 }) 185 .unwrap() 186 } 187 } 188