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, DisplayDataValues(&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                     let actual = DisplayDataValues(&actual);
47                     return Err(format!("Failed test: {}, actual: {}", self, actual));
48                 }
49             }
50         }
51         Ok(())
52     }
53 }
54 
55 impl Display for RunCommand {
56     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
57         match self {
58             RunCommand::Print(invocation) => write!(f, "print: {}", invocation),
59             RunCommand::Run(invocation, comparison, expected) => {
60                 let expected = DisplayDataValues(expected);
61                 write!(f, "run: {} {} {}", invocation, comparison, expected)
62             }
63         }
64     }
65 }
66 
67 /// Represent a function call; [RunCommand]s invoke a CLIF function using an [Invocation].
68 #[derive(Debug, PartialEq)]
69 pub struct Invocation {
70     /// The name of the function to call. Note: this field is for mostly included for informational
71     /// purposes and may not always be necessary for identifying which function to call.
72     pub func: String,
73     /// The arguments to be passed to the function when invoked.
74     pub args: Vec<DataValue>,
75 }
76 
77 impl Invocation {
78     pub(crate) fn new(func: &str, args: Vec<DataValue>) -> Self {
79         let func = func.to_string();
80         Self { func, args }
81     }
82 }
83 
84 impl Display for Invocation {
85     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
86         write!(f, "%{}(", self.func)?;
87         write_data_value_list(f, &self.args)?;
88         write!(f, ")")
89     }
90 }
91 
92 /// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value
93 /// that would be referred to by a [Value].
94 ///
95 /// [Value]: cranelift_codegen::ir::Value
96 #[allow(missing_docs)]
97 #[derive(Clone, Debug, PartialEq)]
98 pub enum DataValue {
99     B(bool),
100     I8(i8),
101     I16(i16),
102     I32(i32),
103     I64(i64),
104     F32(f32),
105     F64(f64),
106     V128([u8; 16]),
107 }
108 
109 impl DataValue {
110     /// Return the Cranelift IR [Type] for this [DataValue].
111     pub fn ty(&self) -> Type {
112         match self {
113             DataValue::B(_) => ir::types::B8,
114             DataValue::I8(_) => ir::types::I8,
115             DataValue::I16(_) => ir::types::I16,
116             DataValue::I32(_) => ir::types::I32,
117             DataValue::I64(_) => ir::types::I64,
118             DataValue::F32(_) => ir::types::F32,
119             DataValue::F64(_) => ir::types::F64,
120             DataValue::V128(_) => ir::types::I8X16,
121         }
122     }
123 
124     /// Return true if the value is a vector (i.e. `DataValue::V128`).
125     pub fn is_vector(&self) -> bool {
126         match self {
127             DataValue::V128(_) => true,
128             _ => false,
129         }
130     }
131 }
132 
133 /// Helper for creating [From] implementations for [DataValue]
134 macro_rules! from_data {
135     ( $ty:ty, $variant:ident ) => {
136         impl From<$ty> for DataValue {
137             fn from(data: $ty) -> Self {
138                 DataValue::$variant(data)
139             }
140         }
141     };
142 }
143 from_data!(bool, B);
144 from_data!(i8, I8);
145 from_data!(i16, I16);
146 from_data!(i32, I32);
147 from_data!(i64, I64);
148 from_data!(f32, F32);
149 from_data!(f64, F64);
150 from_data!([u8; 16], V128);
151 
152 impl Display for DataValue {
153     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
154         match self {
155             DataValue::B(dv) => write!(f, "{}", dv),
156             DataValue::I8(dv) => write!(f, "{}", dv),
157             DataValue::I16(dv) => write!(f, "{}", dv),
158             DataValue::I32(dv) => write!(f, "{}", dv),
159             DataValue::I64(dv) => write!(f, "{}", dv),
160             // Use the Ieee* wrappers here to maintain a consistent syntax.
161             DataValue::F32(dv) => write!(f, "{}", Ieee32::from(*dv)),
162             DataValue::F64(dv) => write!(f, "{}", Ieee64::from(*dv)),
163             // Again, for syntax consistency, use ConstantData, which in this case displays as hex.
164             DataValue::V128(dv) => write!(f, "{}", ConstantData::from(&dv[..])),
165         }
166     }
167 }
168 
169 /// Helper structure for printing bracket-enclosed vectors of [DataValue]s.
170 /// - for empty vectors, display `[]`
171 /// - for single item vectors, display `42`, e.g.
172 /// - for multiple item vectors, display `[42, 43, 44]`, e.g.
173 struct DisplayDataValues<'a>(&'a [DataValue]);
174 
175 impl<'a> Display for DisplayDataValues<'a> {
176     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
177         if self.0.len() == 1 {
178             write!(f, "{}", self.0[0])
179         } else {
180             write!(f, "[")?;
181             write_data_value_list(f, &self.0)?;
182             write!(f, "]")
183         }
184     }
185 }
186 
187 /// Helper function for displaying `Vec<DataValue>`.
188 fn write_data_value_list(f: &mut Formatter<'_>, list: &[DataValue]) -> fmt::Result {
189     match list.len() {
190         0 => Ok(()),
191         1 => write!(f, "{}", list[0]),
192         _ => {
193             write!(f, "{}", list[0])?;
194             for dv in list.iter().skip(1) {
195                 write!(f, ", {}", dv)?;
196             }
197             Ok(())
198         }
199     }
200 }
201 
202 /// A CLIF comparison operation; e.g. `==`.
203 #[allow(missing_docs)]
204 #[derive(Debug, PartialEq)]
205 pub enum Comparison {
206     Equals,
207     NotEquals,
208 }
209 
210 impl Display for Comparison {
211     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
212         match self {
213             Comparison::Equals => write!(f, "=="),
214             Comparison::NotEquals => write!(f, "!="),
215         }
216     }
217 }
218 
219 #[cfg(test)]
220 mod test {
221     use super::*;
222     use crate::parse_run_command;
223     use cranelift_codegen::ir::{types, AbiParam, Signature};
224     use cranelift_codegen::isa::CallConv;
225 
226     #[test]
227     fn run_a_command() {
228         let mut signature = Signature::new(CallConv::Fast);
229         signature.returns.push(AbiParam::new(types::I32));
230         let command = parse_run_command(";; run: %return42() == 42 ", &signature)
231             .unwrap()
232             .unwrap();
233 
234         assert!(command.run(|_| vec![DataValue::I32(42)]).is_ok());
235         assert!(command.run(|_| vec![DataValue::I32(43)]).is_err());
236     }
237 }
238