cranelift_reader/
run_command.rs

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
9use cranelift_codegen::data_value::{self, DataValue, DisplayDataValues};
10use std::fmt::{self, Display, Formatter};
11
12/// A run command appearing in a test file.
13///
14/// For parsing, see `Parser::parse_run_command`
15#[derive(PartialEq, Debug)]
16pub enum RunCommand {
17    /// Invoke a function and print its result.
18    Print(Invocation),
19    /// Invoke a function and compare its result to a value sequence.
20    Run(Invocation, Comparison, Vec<DataValue>),
21}
22
23impl RunCommand {
24    /// Run the [RunCommand]:
25    ///  - for [RunCommand::Print], print the returned values from invoking the function.
26    ///  - for [RunCommand::Run], compare the returned values from the invoked function and
27    ///    return an `Err` with a descriptive string if the comparison fails.
28    ///
29    /// Accepts a function used for invoking the actual execution of the command. This function,
30    /// `invoked_fn`, is passed the _function name_ and _function arguments_ of the [Invocation].
31    pub fn run<F>(&self, invoke_fn: F) -> Result<(), String>
32    where
33        F: FnOnce(&str, &[DataValue]) -> Result<Vec<DataValue>, String>,
34    {
35        match self {
36            RunCommand::Print(invoke) => {
37                let actual = invoke_fn(&invoke.func, &invoke.args)?;
38                println!("{} -> {}", invoke, DisplayDataValues(&actual))
39            }
40            RunCommand::Run(invoke, compare, expected) => {
41                let actual = invoke_fn(&invoke.func, &invoke.args)?;
42                let matched = Self::compare_results(compare, &actual, expected);
43                if !matched {
44                    let actual = DisplayDataValues(&actual);
45                    return Err(format!("Failed test: {self}, actual: {actual}"));
46                }
47            }
48        }
49        Ok(())
50    }
51
52    fn compare_results(
53        compare: &Comparison,
54        actual: &Vec<DataValue>,
55        expected: &Vec<DataValue>,
56    ) -> bool {
57        let are_equal = actual.len() == expected.len()
58            && actual
59                .into_iter()
60                .zip(expected.into_iter())
61                .all(|(a, b)| a.bitwise_eq(b));
62
63        match compare {
64            Comparison::Equals => are_equal,
65            Comparison::NotEquals => !are_equal,
66        }
67    }
68}
69
70impl Display for RunCommand {
71    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
72        match self {
73            RunCommand::Print(invocation) => write!(f, "print: {invocation}"),
74            RunCommand::Run(invocation, comparison, expected) => {
75                let expected = DisplayDataValues(expected);
76                write!(f, "run: {invocation} {comparison} {expected}")
77            }
78        }
79    }
80}
81
82/// Represent a function call; [RunCommand]s invoke a CLIF function using an [Invocation].
83#[derive(Debug, PartialEq)]
84pub struct Invocation {
85    /// The name of the function to call. Note: this field is for mostly included for informational
86    /// purposes and may not always be necessary for identifying which function to call.
87    pub func: String,
88    /// The arguments to be passed to the function when invoked.
89    pub args: Vec<DataValue>,
90}
91
92impl Invocation {
93    pub(crate) fn new(func: &str, args: Vec<DataValue>) -> Self {
94        let func = func.to_string();
95        Self { func, args }
96    }
97}
98
99impl Display for Invocation {
100    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
101        write!(f, "%{}(", self.func)?;
102        data_value::write_data_value_list(f, &self.args)?;
103        write!(f, ")")
104    }
105}
106
107/// A CLIF comparison operation; e.g. `==`.
108#[expect(missing_docs, reason = "self-describing variants")]
109#[derive(Debug, PartialEq)]
110pub enum Comparison {
111    Equals,
112    NotEquals,
113}
114
115impl Display for Comparison {
116    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117        match self {
118            Comparison::Equals => write!(f, "=="),
119            Comparison::NotEquals => write!(f, "!="),
120        }
121    }
122}
123
124#[cfg(test)]
125mod test {
126    use super::*;
127    use crate::parse_run_command;
128    use cranelift_codegen::ir::{types, AbiParam, Signature};
129    use cranelift_codegen::isa::CallConv;
130
131    #[test]
132    fn run_a_command() {
133        let mut signature = Signature::new(CallConv::Fast);
134        signature.returns.push(AbiParam::new(types::I32));
135        let command = parse_run_command(";; run: %return42() == 42 ", &signature)
136            .unwrap()
137            .unwrap();
138
139        assert!(command.run(|_, _| Ok(vec![DataValue::I32(42)])).is_ok());
140        assert!(command.run(|_, _| Ok(vec![DataValue::I32(43)])).is_err());
141    }
142}