Skip to main content

cairo_lang_sierra/simulation/
mod.rs

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