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