cairo_lang_sierra/simulation/
mod.rs

1use std::collections::HashMap;
2
3use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
4use itertools::izip;
5use thiserror::Error;
6
7use self::value::CoreValue;
8use crate::edit_state::{EditStateError, put_results, take_args};
9use crate::extensions::core::{CoreConcreteLibfunc, CoreLibfunc, CoreType};
10use crate::ids::{FunctionId, VarId};
11use crate::program::{Program, Statement, StatementIdx};
12use crate::program_registry::{ProgramRegistry, ProgramRegistryError};
13
14pub mod core;
15#[cfg(test)]
16mod test;
17pub mod value;
18
19/// Error occurring while simulating a libfunc.
20#[derive(Error, Debug, Eq, PartialEq)]
21pub enum LibfuncSimulationError {
22    #[error("Expected different number of arguments")]
23    WrongNumberOfArgs,
24    #[error("Expected a different type of an argument")]
25    WrongArgType,
26    #[error("Could not resolve requested symbol value")]
27    UnresolvedStatementGasInfo,
28    #[error("Error occurred during user function call")]
29    FunctionSimulationError(FunctionId, Box<SimulationError>),
30}
31
32/// Error occurring while simulating a program function.
33#[derive(Error, Debug, Eq, PartialEq)]
34pub enum SimulationError {
35    #[error("error from the program registry")]
36    ProgramRegistryError(#[from] Box<ProgramRegistryError>),
37    #[error("error from editing a variable state")]
38    EditStateError(EditStateError, StatementIdx),
39    #[error("error from simulating a libfunc")]
40    LibfuncSimulationError(LibfuncSimulationError, StatementIdx),
41    #[error("jumped out of bounds during simulation")]
42    StatementOutOfBounds(StatementIdx),
43    #[error("unexpected number of arguments to function")]
44    FunctionArgumentCountMismatch { function_id: FunctionId, expected: usize, actual: usize },
45    #[error("identifiers left at function return")]
46    FunctionDidNotConsumeAllArgs(FunctionId, StatementIdx),
47}
48
49/// Runs a function from the program with the given inputs.
50pub fn run(
51    program: &Program,
52    statement_gas_info: &HashMap<StatementIdx, i64>,
53    function_id: &FunctionId,
54    inputs: Vec<CoreValue>,
55) -> Result<Vec<CoreValue>, SimulationError> {
56    let context = SimulationContext {
57        program,
58        statement_gas_info,
59        registry: &ProgramRegistry::new(program)?,
60    };
61    context.simulate_function(function_id, inputs)
62}
63
64/// Helper class for running the simulation.
65struct SimulationContext<'a> {
66    pub program: &'a Program,
67    pub statement_gas_info: &'a HashMap<StatementIdx, i64>,
68    pub registry: &'a ProgramRegistry<CoreType, CoreLibfunc>,
69}
70impl SimulationContext<'_> {
71    /// Simulates the run of a function, even recursively.
72    fn simulate_function(
73        &self,
74        function_id: &FunctionId,
75        inputs: Vec<CoreValue>,
76    ) -> Result<Vec<CoreValue>, SimulationError> {
77        let func = self.registry.get_function(function_id)?;
78        let mut current_statement_id = func.entry_point;
79        if func.params.len() != inputs.len() {
80            return Err(SimulationError::FunctionArgumentCountMismatch {
81                function_id: func.id.clone(),
82                expected: func.params.len(),
83                actual: inputs.len(),
84            });
85        }
86        let mut state = OrderedHashMap::<VarId, CoreValue>::from_iter(
87            izip!(func.params.iter(), inputs).map(|(param, input)| (param.id.clone(), input)),
88        );
89        loop {
90            let statement = self
91                .program
92                .get_statement(&current_statement_id)
93                .ok_or(SimulationError::StatementOutOfBounds(current_statement_id))?;
94            match statement {
95                Statement::Return(ids) => {
96                    let (remaining, outputs) = take_args(state, ids.iter()).map_err(|error| {
97                        SimulationError::EditStateError(error, current_statement_id)
98                    })?;
99                    return if remaining.is_empty() {
100                        Ok(outputs)
101                    } else {
102                        Err(SimulationError::FunctionDidNotConsumeAllArgs(
103                            func.id.clone(),
104                            current_statement_id,
105                        ))
106                    };
107                }
108                Statement::Invocation(invocation) => {
109                    let (remaining, inputs) =
110                        take_args(state, invocation.args.iter()).map_err(|error| {
111                            SimulationError::EditStateError(error, current_statement_id)
112                        })?;
113                    let libfunc = self.registry.get_libfunc(&invocation.libfunc_id)?;
114                    let (outputs, chosen_branch) = self.simulate_libfunc(
115                        &current_statement_id,
116                        libfunc,
117                        inputs,
118                        current_statement_id,
119                    )?;
120                    let branch_info = &invocation.branches[chosen_branch];
121                    state = put_results(remaining, izip!(branch_info.results.iter(), outputs))
122                        .map_err(|error| {
123                            SimulationError::EditStateError(error, current_statement_id)
124                        })?;
125                    current_statement_id = current_statement_id.next(&branch_info.target);
126                }
127            }
128        }
129    }
130    /// Simulates the run of libfuncs. Returns the memory representations of the outputs given the
131    /// inputs.
132    fn simulate_libfunc(
133        &self,
134        idx: &StatementIdx,
135        libfunc: &CoreConcreteLibfunc,
136        inputs: Vec<CoreValue>,
137        current_statement_id: StatementIdx,
138    ) -> Result<(Vec<CoreValue>, usize), SimulationError> {
139        core::simulate(
140            libfunc,
141            inputs,
142            || self.statement_gas_info.get(idx).copied(),
143            |function_id, inputs| {
144                self.simulate_function(function_id, inputs).map_err(|error| {
145                    LibfuncSimulationError::FunctionSimulationError(
146                        function_id.clone(),
147                        Box::new(error),
148                    )
149                })
150            },
151        )
152        .map_err(|error| SimulationError::LibfuncSimulationError(error, current_statement_id))
153    }
154}