1 //! Run commands. 2 //! 3 //! Functions in a `.clif` file can have *run commands* appended that control how a function is 4 //! invoked and tested within the `test run` context. The general syntax is: 5 //! 6 //! - `; run`: this assumes the function has a signature like `() -> b*`. 7 //! - `; run: %fn(42, 4.2) == false`: this syntax specifies the parameters and return values. 8 9 use cranelift_codegen::ir::immediates::{Ieee32, Ieee64}; 10 use cranelift_codegen::ir::{self, ConstantData, Type}; 11 use std::fmt::{self, Display, Formatter}; 12 13 /// A run command appearing in a test file. 14 /// 15 /// For parsing, see 16 /// [Parser::parse_run_command](crate::parser::Parser::parse_run_command). 17 #[derive(PartialEq, Debug)] 18 pub enum RunCommand { 19 /// Invoke a function and print its result. 20 Print(Invocation), 21 /// Invoke a function and compare its result to a value sequence. 22 Run(Invocation, Comparison, Vec<DataValue>), 23 } 24 25 impl RunCommand { 26 /// Run the [RunCommand]: 27 /// - for [RunCommand::Print], print the returned values from invoking the function. 28 /// - for [RunCommand::Run], compare the returned values from the invoked function and 29 /// return an `Err` with a descriptive string if the comparison fails. 30 pub fn run<F>(&self, invoke_fn: F) -> Result<(), String> 31 where 32 F: FnOnce(&[DataValue]) -> Vec<DataValue>, 33 { 34 match self { 35 RunCommand::Print(invoke) => { 36 let actual = invoke_fn(&invoke.args); 37 println!("{:?} -> {:?}", invoke, actual) 38 } 39 RunCommand::Run(invoke, compare, expected) => { 40 let actual = invoke_fn(&invoke.args); 41 let matched = match compare { 42 Comparison::Equals => *expected == actual, 43 Comparison::NotEquals => *expected != actual, 44 }; 45 if !matched { 46 return Err(format!("Failed test: {:?}, actual: {:?}", self, actual)); 47 } 48 } 49 } 50 Ok(()) 51 } 52 } 53 54 impl Display for RunCommand { 55 fn fmt(&self, f: &mut Formatter) -> fmt::Result { 56 match self { 57 RunCommand::Print(invocation) => write!(f, "print: {}", invocation), 58 RunCommand::Run(invocation, comparison, expected) => { 59 write!(f, "run: {} {} ", invocation, comparison)?; 60 if expected.len() == 1 { 61 write!(f, "{}", expected[0]) 62 } else { 63 write!(f, "[")?; 64 write_data_value_list(f, expected)?; 65 write!(f, "]") 66 } 67 } 68 } 69 } 70 } 71 72 /// Represent a function call; [RunCommand]s invoke a CLIF function using an [Invocation]. 73 #[derive(Debug, PartialEq)] 74 pub struct Invocation { 75 /// The name of the function to call. Note: this field is for mostly included for informational 76 /// purposes and may not always be necessary for identifying which function to call. 77 pub func: String, 78 /// The arguments to be passed to the function when invoked. 79 pub args: Vec<DataValue>, 80 } 81 82 impl Invocation { 83 pub(crate) fn new(func: &str, args: Vec<DataValue>) -> Self { 84 let func = func.to_string(); 85 Self { func, args } 86 } 87 } 88 89 impl Display for Invocation { 90 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 91 write!(f, "%{}(", self.func)?; 92 write_data_value_list(f, &self.args)?; 93 write!(f, ")") 94 } 95 } 96 97 /// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value 98 /// that would be referred to by a [Value]. 99 /// 100 /// [Value]: cranelift_codegen::ir::Value 101 #[allow(missing_docs)] 102 #[derive(Clone, Debug, PartialEq)] 103 pub enum DataValue { 104 B(bool), 105 I8(i8), 106 I16(i16), 107 I32(i32), 108 I64(i64), 109 F32(f32), 110 F64(f64), 111 V128([u8; 16]), 112 } 113 114 impl DataValue { 115 /// Return the Cranelift IR [Type] for this [DataValue]. 116 pub fn ty(&self) -> Type { 117 match self { 118 DataValue::B(_) => ir::types::B8, 119 DataValue::I8(_) => ir::types::I8, 120 DataValue::I16(_) => ir::types::I16, 121 DataValue::I32(_) => ir::types::I32, 122 DataValue::I64(_) => ir::types::I64, 123 DataValue::F32(_) => ir::types::F32, 124 DataValue::F64(_) => ir::types::F64, 125 DataValue::V128(_) => ir::types::I8X16, 126 } 127 } 128 } 129 130 /// Helper for creating [From] implementations for [DataValue] 131 macro_rules! from_data { 132 ( $ty:ty, $variant:ident ) => { 133 impl From<$ty> for DataValue { 134 fn from(data: $ty) -> Self { 135 DataValue::$variant(data) 136 } 137 } 138 }; 139 } 140 from_data!(bool, B); 141 from_data!(i8, I8); 142 from_data!(i16, I16); 143 from_data!(i32, I32); 144 from_data!(i64, I64); 145 from_data!(f32, F32); 146 from_data!(f64, F64); 147 from_data!([u8; 16], V128); 148 149 impl Display for DataValue { 150 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 151 match self { 152 DataValue::B(dv) => write!(f, "{}", dv), 153 DataValue::I8(dv) => write!(f, "{}", dv), 154 DataValue::I16(dv) => write!(f, "{}", dv), 155 DataValue::I32(dv) => write!(f, "{}", dv), 156 DataValue::I64(dv) => write!(f, "{}", dv), 157 // Use the Ieee* wrappers here to maintain a consistent syntax. 158 DataValue::F32(dv) => write!(f, "{}", Ieee32::from(*dv)), 159 DataValue::F64(dv) => write!(f, "{}", Ieee64::from(*dv)), 160 // Again, for syntax consistency, use ConstantData, which in this case displays as hex. 161 DataValue::V128(dv) => write!(f, "{}", ConstantData::from(&dv[..])), 162 } 163 } 164 } 165 166 /// Helper function for displaying `Vec<DataValue>`. 167 fn write_data_value_list(f: &mut Formatter<'_>, list: &[DataValue]) -> fmt::Result { 168 match list.len() { 169 0 => Ok(()), 170 1 => write!(f, "{}", list[0]), 171 _ => { 172 write!(f, "{}", list[0])?; 173 for dv in list.iter().skip(1) { 174 write!(f, ", {}", dv)?; 175 } 176 Ok(()) 177 } 178 } 179 } 180 181 /// A CLIF comparison operation; e.g. `==`. 182 #[allow(missing_docs)] 183 #[derive(Debug, PartialEq)] 184 pub enum Comparison { 185 Equals, 186 NotEquals, 187 } 188 189 impl Display for Comparison { 190 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 191 match self { 192 Comparison::Equals => write!(f, "=="), 193 Comparison::NotEquals => write!(f, "!="), 194 } 195 } 196 } 197 198 #[cfg(test)] 199 mod test { 200 use super::*; 201 use crate::parse_run_command; 202 use cranelift_codegen::ir::{types, AbiParam, Signature}; 203 use cranelift_codegen::isa::CallConv; 204 205 #[test] 206 fn run_a_command() { 207 let mut signature = Signature::new(CallConv::Fast); 208 signature.returns.push(AbiParam::new(types::I32)); 209 let command = parse_run_command(";; run: %return42() == 42 ", &signature) 210 .unwrap() 211 .unwrap(); 212 213 assert!(command.run(|_| vec![DataValue::I32(42)]).is_ok()); 214 assert!(command.run(|_| vec![DataValue::I32(43)]).is_err()); 215 } 216 } 217