cairo_lang_sierra/simulation/
mod.rs1#[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#[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#[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#[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#[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 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 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}