diff --git a/CHANGELOG.md b/CHANGELOG.md index a8c10f0fbc..fe8c80670b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: Add CairoRunnerBuilder [#2223](https://github.com/lambdaclass/cairo-vm/pull/2223) + #### [2.5.0] - 2025-09-11 * breaking: Store constants in Hint Data [#2191](https://github.com/lambdaclass/cairo-vm/pull/2191) diff --git a/Cargo.toml b/Cargo.toml index 1e6d8339a4..c9bb82d50e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ lazy_static = { version = "1.4.0", default-features = false, features = [ ] } nom = { version = "7", default-features = false } sha2 = { version = "0.10.7", features = ["compress"], default-features = false } -generic-array = { version = "0.14.7", default-features = false } +generic-array = { version = ">=0.14.0, <=0.14.7", default-features = false } keccak = { version = "0.1.2", default-features = false } hashbrown = { version = "0.15.2", features = ["serde"] } anyhow = { version = "1.0.94", default-features = false } diff --git a/vm/src/types/instance_definitions/builtins_instance_def.rs b/vm/src/types/instance_definitions/builtins_instance_def.rs index d98b3b3a3f..2eaf0ab405 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -13,7 +13,7 @@ pub(crate) const BUILTIN_INSTANCES_PER_COMPONENT: u32 = 1; use serde::Serialize; -#[derive(Serialize, Debug, PartialEq)] +#[derive(Clone, Serialize, Debug, PartialEq)] pub(crate) struct BuiltinsInstanceDef { pub(crate) output: bool, pub(crate) pedersen: Option, diff --git a/vm/src/types/instance_definitions/diluted_pool_instance_def.rs b/vm/src/types/instance_definitions/diluted_pool_instance_def.rs index d3c109b27d..8cd73db51b 100644 --- a/vm/src/types/instance_definitions/diluted_pool_instance_def.rs +++ b/vm/src/types/instance_definitions/diluted_pool_instance_def.rs @@ -1,6 +1,6 @@ use serde::Serialize; -#[derive(Serialize, Debug, PartialEq)] +#[derive(Clone, Serialize, Debug, PartialEq)] pub(crate) struct DilutedPoolInstanceDef { pub(crate) units_per_step: u32, // 2 ^ log_units_per_step (for cairo_lang comparison) pub(crate) fractional_units_per_step: bool, // true when log_units_per_step is negative diff --git a/vm/src/types/instance_definitions/range_check_instance_def.rs b/vm/src/types/instance_definitions/range_check_instance_def.rs index ed09d7cfb3..24e9070acc 100644 --- a/vm/src/types/instance_definitions/range_check_instance_def.rs +++ b/vm/src/types/instance_definitions/range_check_instance_def.rs @@ -3,7 +3,7 @@ use serde::Serialize; use super::LowRatio; pub(crate) const CELLS_PER_RANGE_CHECK: u32 = 1; -#[derive(Serialize, Debug, PartialEq)] +#[derive(Clone, Serialize, Debug, PartialEq)] pub(crate) struct RangeCheckInstanceDef { pub(crate) ratio: Option, } diff --git a/vm/src/types/layout.rs b/vm/src/types/layout.rs index 1c7fe138a8..4c686f1f96 100644 --- a/vm/src/types/layout.rs +++ b/vm/src/types/layout.rs @@ -13,7 +13,7 @@ pub(crate) const DEFAULT_CPU_COMPONENT_STEP: u32 = 1; use serde::{Deserialize, Deserializer, Serialize}; -#[derive(Serialize, Debug)] +#[derive(Clone, Serialize, Debug)] pub struct CairoLayout { pub(crate) name: LayoutName, pub(crate) cpu_component_step: u32, diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 2a0ecfe964..f6eb61670e 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -4,15 +4,22 @@ use crate::{ math_utils::safe_div_usize, stdlib::{ any::Any, + cell::RefCell, collections::{BTreeMap, HashMap, HashSet}, + mem, ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, prelude::*, rc::Rc, }, - types::{builtin_name::BuiltinName, layout::CairoLayoutParams, layout_name::LayoutName}, + types::{ + builtin_name::BuiltinName, instruction::Instruction, layout::CairoLayoutParams, + layout_name::LayoutName, + }, vm::{ runners::builtin_runner::SegmentArenaBuiltinRunner, trace::trace_entry::{relocate_trace_register, RelocatedTraceEntry, TraceEntry}, + vm_core::VirtualMachineBuilder, + vm_memory::memory_segments::MemorySegmentManager, }, Felt252, }; @@ -49,6 +56,7 @@ use crate::{ use num_integer::div_rem; use num_traits::{ToPrimitive, Zero}; use serde::{Deserialize, Serialize}; +use starknet_crypto::Signature; use super::{builtin_runner::ModBuiltinRunner, cairo_pie::CairoPieAdditionalData}; use super::{ @@ -141,6 +149,458 @@ impl ResourceTracker for RunResources { } } +/// Handles the creation of a CairoRunner +/// +/// This structure can be cloned. This allows to compute the initial state once, +/// and execute it many times. The following elements can be cached: +/// - Compiled hints +/// - Decoded instructions +/// - Loaded program segment +pub struct CairoRunnerBuilder { + program: Program, + layout: CairoLayout, + runner_mode: RunnerMode, + // Flags. + enable_trace: bool, + disable_trace_padding: bool, + allow_missing_builtins: bool, + // Set after initializing builtin runners. + builtin_runners: Vec, + // Set after initializing segments. + program_base: Option, + execution_base: Option, + memory: MemorySegmentManager, + // Set after loading program. + loaded_program: bool, + // Set after compiling hints. + // NOTE: To avoid breaking the API, we are wrapping the hint in an + // Rc>. This is because the current API expects a Box, but we need an + // Rc to make it clonable + hints: Option>>>, + // Set after loading instruction cache. + instructions: Vec>, +} + +impl CairoRunnerBuilder { + // TODO: Determine if these fields should be set with different functions, + // instead of passing them to `new`. + pub fn new( + program: &Program, + layout_name: LayoutName, + dynamic_layout_params: Option, + runner_mode: RunnerMode, + ) -> Result { + let layout = match layout_name { + LayoutName::plain => CairoLayout::plain_instance(), + LayoutName::small => CairoLayout::small_instance(), + LayoutName::dex => CairoLayout::dex_instance(), + LayoutName::recursive => CairoLayout::recursive_instance(), + LayoutName::starknet => CairoLayout::starknet_instance(), + LayoutName::starknet_with_keccak => CairoLayout::starknet_with_keccak_instance(), + LayoutName::recursive_large_output => CairoLayout::recursive_large_output_instance(), + LayoutName::recursive_with_poseidon => CairoLayout::recursive_with_poseidon(), + LayoutName::all_cairo => CairoLayout::all_cairo_instance(), + LayoutName::all_cairo_stwo => CairoLayout::all_cairo_stwo_instance(), + LayoutName::all_solidity => CairoLayout::all_solidity_instance(), + LayoutName::dynamic => { + let params = + dynamic_layout_params.ok_or(RunnerError::MissingDynamicLayoutParams)?; + CairoLayout::dynamic_instance(params) + } + }; + + Ok(CairoRunnerBuilder { + program: program.clone(), + layout, + enable_trace: false, + disable_trace_padding: false, + allow_missing_builtins: false, + runner_mode, + builtin_runners: Vec::new(), + program_base: None, + execution_base: None, + memory: MemorySegmentManager::new(), + loaded_program: false, + hints: None, + instructions: Vec::new(), + }) + } + + pub fn enable_trace(&mut self, v: bool) { + self.enable_trace = v; + } + pub fn disable_trace_padding(&mut self, v: bool) { + self.disable_trace_padding = v; + } + pub fn allow_missing_builtins(&mut self, v: bool) { + self.allow_missing_builtins = v; + } + + fn is_proof_mode(&self) -> bool { + self.runner_mode == RunnerMode::ProofModeCanonical + || self.runner_mode == RunnerMode::ProofModeCairo1 + } + + pub fn get_program_base(&self) -> Option { + self.program_base + } + + pub fn add_memory_segment(&mut self) -> Relocatable { + self.memory.add() + } + + /// *Initializes* all the builtin supported by the current layout, but only + /// *includes* the builtins required by the program. + /// + /// Note that *initializing* a builtin implies creating a runner for it, + /// and *including* a builtin refers to enabling the builtin runner flag: + /// `included`. + /// + /// Analogue to [CairoRunner::initialize_builtins] + pub fn initialize_builtin_runners_for_layout(&mut self) -> Result<(), RunnerError> { + let builtin_ordered_list = vec![ + BuiltinName::output, + BuiltinName::pedersen, + BuiltinName::range_check, + BuiltinName::ecdsa, + BuiltinName::bitwise, + BuiltinName::ec_op, + BuiltinName::keccak, + BuiltinName::poseidon, + BuiltinName::range_check96, + BuiltinName::add_mod, + BuiltinName::mul_mod, + ]; + if !is_subsequence(&self.program.builtins, &builtin_ordered_list) { + return Err(RunnerError::DisorderedBuiltins); + }; + let mut program_builtins: HashSet<&BuiltinName> = self.program.builtins.iter().collect(); + + if self.layout.builtins.output { + let included = program_builtins.remove(&BuiltinName::output); + if included || self.is_proof_mode() { + self.builtin_runners + .push(OutputBuiltinRunner::new(included).into()); + } + } + + if let Some(instance_def) = self.layout.builtins.pedersen.as_ref() { + let included = program_builtins.remove(&BuiltinName::pedersen); + if included || self.is_proof_mode() { + self.builtin_runners + .push(HashBuiltinRunner::new(instance_def.ratio, included).into()); + } + } + + if let Some(instance_def) = self.layout.builtins.range_check.as_ref() { + let included = program_builtins.remove(&BuiltinName::range_check); + if included || self.is_proof_mode() { + self.builtin_runners.push( + RangeCheckBuiltinRunner::::new_with_low_ratio( + instance_def.ratio, + included, + ) + .into(), + ); + } + } + + if let Some(instance_def) = self.layout.builtins.ecdsa.as_ref() { + let included = program_builtins.remove(&BuiltinName::ecdsa); + if included || self.is_proof_mode() { + self.builtin_runners + .push(SignatureBuiltinRunner::new(instance_def.ratio, included).into()); + } + } + + if let Some(instance_def) = self.layout.builtins.bitwise.as_ref() { + let included = program_builtins.remove(&BuiltinName::bitwise); + if included || self.is_proof_mode() { + self.builtin_runners + .push(BitwiseBuiltinRunner::new(instance_def.ratio, included).into()); + } + } + + if let Some(instance_def) = self.layout.builtins.ec_op.as_ref() { + let included = program_builtins.remove(&BuiltinName::ec_op); + if included || self.is_proof_mode() { + self.builtin_runners + .push(EcOpBuiltinRunner::new(instance_def.ratio, included).into()); + } + } + + if let Some(instance_def) = self.layout.builtins.keccak.as_ref() { + let included = program_builtins.remove(&BuiltinName::keccak); + if included || self.is_proof_mode() { + self.builtin_runners + .push(KeccakBuiltinRunner::new(instance_def.ratio, included).into()); + } + } + + if let Some(instance_def) = self.layout.builtins.poseidon.as_ref() { + let included = program_builtins.remove(&BuiltinName::poseidon); + if included || self.is_proof_mode() { + self.builtin_runners + .push(PoseidonBuiltinRunner::new(instance_def.ratio, included).into()); + } + } + + if let Some(instance_def) = self.layout.builtins.range_check96.as_ref() { + let included = program_builtins.remove(&BuiltinName::range_check96); + if included || self.is_proof_mode() { + self.builtin_runners.push( + RangeCheckBuiltinRunner::::new_with_low_ratio( + instance_def.ratio, + included, + ) + .into(), + ); + } + } + + if let Some(instance_def) = self.layout.builtins.add_mod.as_ref() { + let included = program_builtins.remove(&BuiltinName::add_mod); + if included || self.is_proof_mode() { + self.builtin_runners + .push(ModBuiltinRunner::new_add_mod(instance_def, included).into()); + } + } + + if let Some(instance_def) = self.layout.builtins.mul_mod.as_ref() { + let included = program_builtins.remove(&BuiltinName::mul_mod); + if included || self.is_proof_mode() { + self.builtin_runners + .push(ModBuiltinRunner::new_mul_mod(instance_def, included).into()); + } + } + + if !program_builtins.is_empty() && !self.allow_missing_builtins { + return Err(RunnerError::NoBuiltinForInstance(Box::new(( + program_builtins.iter().map(|n| **n).collect(), + self.layout.name, + )))); + } + + Ok(()) + } + + /// *Initializes* and *includes* all the given builtins. + /// + /// Doesn't take the current layout into account. + /// + /// Analogue to [CairoRunner::initialize_program_builtins], but receives the + /// builtins instead of reusing the program builtins. + pub fn initialize_builtin_runners( + &mut self, + builtins: &[BuiltinName], + ) -> Result<(), RunnerError> { + for builtin_name in builtins { + let builtin_runner = match builtin_name { + BuiltinName::pedersen => HashBuiltinRunner::new(Some(32), true).into(), + BuiltinName::range_check => { + RangeCheckBuiltinRunner::::new(Some(1), true).into() + } + BuiltinName::output => OutputBuiltinRunner::new(true).into(), + BuiltinName::ecdsa => SignatureBuiltinRunner::new(Some(1), true).into(), + BuiltinName::bitwise => BitwiseBuiltinRunner::new(Some(1), true).into(), + BuiltinName::ec_op => EcOpBuiltinRunner::new(Some(1), true).into(), + BuiltinName::keccak => KeccakBuiltinRunner::new(Some(1), true).into(), + BuiltinName::poseidon => PoseidonBuiltinRunner::new(Some(1), true).into(), + BuiltinName::segment_arena => SegmentArenaBuiltinRunner::new(true).into(), + BuiltinName::range_check96 => { + RangeCheckBuiltinRunner::::new(Some(1), true).into() + } + BuiltinName::add_mod => { + ModBuiltinRunner::new_add_mod(&ModInstanceDef::new(Some(1), 1, 96), true).into() + } + BuiltinName::mul_mod => { + ModBuiltinRunner::new_mul_mod(&ModInstanceDef::new(Some(1), 1, 96), true).into() + } + }; + self.builtin_runners.push(builtin_runner); + } + Ok(()) + } + + pub fn initialize_base_segments(&mut self) { + self.program_base = Some(self.add_memory_segment()); + self.execution_base = Some(self.add_memory_segment()); + } + + /// Initializing the builtin segments. + /// + /// Depends on: + /// - [initialize_base_segments](Self::initialize_base_segments) + /// - [initialize_builtin_runners_for_layout](Self::initialize_builtin_runners_for_layout) or + /// [initialize_builtin_runners](Self::initialize_builtin_runners) + pub fn initialize_builtin_segments(&mut self) { + for builtin_runner in self.builtin_runners.iter_mut() { + builtin_runner.initialize_segments(&mut self.memory); + } + } + + /// Loads the program into the program segment. + /// + /// If this function is not called, the program will be loaded + /// automatically when initializing the entrypoint. + pub fn load_program(&mut self) -> Result<(), RunnerError> { + let program_base = self.program_base.ok_or(RunnerError::NoProgBase)?; + let program_data = &self.program.shared_program_data.data; + self.memory + .load_data(program_base, program_data) + .map_err(RunnerError::MemoryInitializationError)?; + for i in 0..self.program.shared_program_data.data.len() { + self.memory.memory.mark_as_accessed((program_base + i)?); + } + self.loaded_program = true; + self.instructions.resize(program_data.len(), None); + Ok(()) + } + + /// Preallocates memory for `n` more elements in the given segment. + pub fn preallocate_segment( + &mut self, + segment: Relocatable, + n: usize, + ) -> Result<(), RunnerError> { + self.memory.memory.preallocate_segment(segment, n)?; + Ok(()) + } + + /// Loads decoded program instructions. + /// + /// # Safety + /// + /// The decoded instructions must belong the the associated program. To + /// obtain them, call [VirtualMachine::take_instruction_cache] at the end of + /// the execution. + /// + /// [VirtualMachine::take_instruction_cache]: VirtualMachine::take_instruction_cache + pub fn load_cached_instructions( + &mut self, + instructions: Vec>, + ) -> Result<(), RunnerError> { + self.instructions = instructions; + Ok(()) + } + + /// Precompiles the program's hints using the given executor. + /// + /// # Safety + /// + /// Use the v2 variants of the execution functions (run_until_pc_v2 or + /// run_from_entrypoint_v2), as those function make use of the precompiled + /// hints. + /// + /// This function consumes the program's constants, so not doing so can + /// lead to errors during execution (missing constants). This was done for + /// performance reasons. + /// + /// The user must make sure to use the same implementation of the + /// HintProcessor for execution. + pub fn compile_hints( + &mut self, + hint_processor: &mut dyn HintProcessor, + ) -> Result<(), VirtualMachineError> { + let constants = Rc::new(mem::take(&mut self.program.constants)); + let references = &self.program.shared_program_data.reference_manager; + let compiled_hints = self + .program + .shared_program_data + .hints_collection + .iter_hints() + .map(|hint| { + let hint = hint_processor.compile_hint( + &hint.code, + &hint.flow_tracking_data.ap_tracking, + &hint.flow_tracking_data.reference_ids, + references, + constants.clone(), + )?; + + Ok(Rc::new(hint)) + }) + .collect::>>, VirtualMachineError>>()?; + self.hints = Some(compiled_hints); + Ok(()) + } + + pub fn build(self) -> Result { + let vm = VirtualMachineBuilder::default() + .builtin_runners(self.builtin_runners) + .segments(self.memory) + .instruction_cache(self.instructions) + .build(); + + Ok(CairoRunner { + vm, + layout: self.layout, + program_base: self.program_base, + execution_base: self.execution_base, + entrypoint: self.program.shared_program_data.main, + initial_ap: None, + initial_fp: None, + initial_pc: None, + final_pc: None, + run_ended: false, + segments_finalized: false, + execution_public_memory: if self.runner_mode != RunnerMode::ExecutionMode { + Some(Vec::new()) + } else { + None + }, + runner_mode: self.runner_mode, + relocated_memory: Vec::new(), + exec_scopes: ExecutionScopes::new(), + relocated_trace: None, + program: self.program, + loaded_program: self.loaded_program, + hints: self.hints, + }) + } +} + +impl Clone for CairoRunnerBuilder { + fn clone(&self) -> Self { + let builtin_runners = self + .builtin_runners + .iter() + .cloned() + .map(|mut builtin_runner| { + // The SignatureBuiltinRunner contains an `Rc`, so deriving clone implies that + // all runners built will share state. To workaround this, clone was implemented + // manually. + if let BuiltinRunner::Signature(signature) = &mut builtin_runner { + let signatures = signature + .signatures + .as_ref() + .borrow() + .iter() + .map(|(k, v)| (*k, Signature { r: v.r, s: v.s })) + .collect(); + signature.signatures = Rc::new(RefCell::new(signatures)) + } + builtin_runner + }) + .collect(); + Self { + program: self.program.clone(), + layout: self.layout.clone(), + runner_mode: self.runner_mode.clone(), + enable_trace: self.enable_trace, + disable_trace_padding: self.disable_trace_padding, + allow_missing_builtins: self.allow_missing_builtins, + builtin_runners, + program_base: self.program_base, + execution_base: self.execution_base, + memory: self.memory.clone(), + loaded_program: self.loaded_program, + hints: self.hints.clone(), + instructions: self.instructions.clone(), + } + } +} + pub struct CairoRunner { pub vm: VirtualMachine, pub(crate) program: Program, @@ -159,6 +619,8 @@ pub struct CairoRunner { pub relocated_memory: Vec>, pub exec_scopes: ExecutionScopes, pub relocated_trace: Option>, + loaded_program: bool, + hints: Option>>>, } #[derive(Clone, Debug, PartialEq)] @@ -220,6 +682,8 @@ impl CairoRunner { None }, relocated_trace: None, + loaded_program: false, + hints: None, }) } @@ -486,13 +950,15 @@ impl CairoRunner { let prog_base = self.program_base.ok_or(RunnerError::NoProgBase)?; let exec_base = self.execution_base.ok_or(RunnerError::NoExecBase)?; self.initial_pc = Some((prog_base + entrypoint)?); - self.vm - .load_data(prog_base, &self.program.shared_program_data.data) - .map_err(RunnerError::MemoryInitializationError)?; - - // Mark all addresses from the program segment as accessed - for i in 0..self.program.shared_program_data.data.len() { - self.vm.segments.memory.mark_as_accessed((prog_base + i)?); + if !self.loaded_program { + self.vm + .load_data(prog_base, &self.program.shared_program_data.data) + .map_err(RunnerError::MemoryInitializationError)?; + + // Mark all addresses from the program segment as accessed + for i in 0..self.program.shared_program_data.data.len() { + self.vm.segments.memory.mark_as_accessed((prog_base + i)?); + } } self.vm .segments @@ -722,6 +1188,66 @@ impl CairoRunner { Ok(()) } + /// Like [run_until_pc](Self::run_until_pc), but makes use of cached compiled hints, which are + /// available after calling [CairoRunnerBuilder::compile_hints]. + /// + /// To make the hints clonable (and cacheable), the hint data type had to + /// be changed. To avoid breaking the API, new v2 functions were added that + /// accept the new hint data. + /// + /// Also, this new function does not call instruction hooks from the + /// `test_utils` features, as doing so would imply breaking the hook API. + pub fn run_until_pc_v2( + &mut self, + address: Relocatable, + hint_processor: &mut dyn HintProcessor, + ) -> Result<(), VirtualMachineError> { + let references = &self.program.shared_program_data.reference_manager; + #[cfg_attr(not(feature = "extensive_hints"), allow(unused_mut))] + let mut hint_data = if let Some(hints) = self.hints.take() { + hints + } else { + self.get_hint_data(references, hint_processor)? + .into_iter() + .map(Rc::new) + .collect() + }; + #[cfg(feature = "extensive_hints")] + let mut hint_ranges = self + .program + .shared_program_data + .hints_collection + .hints_ranges + .clone(); + while self.vm.get_pc() != address && !hint_processor.consumed() { + self.vm.step_v2( + hint_processor, + &mut self.exec_scopes, + #[cfg(feature = "extensive_hints")] + &mut hint_data, + #[cfg(not(feature = "extensive_hints"))] + self.program + .shared_program_data + .hints_collection + .get_hint_range_for_pc(self.vm.get_pc().offset) + .and_then(|range| { + range.and_then(|(start, length)| hint_data.get(start..start + length.get())) + }) + .unwrap_or(&[]), + #[cfg(feature = "extensive_hints")] + &mut hint_ranges, + )?; + + hint_processor.consume_step(); + } + + if self.vm.get_pc() != address { + return Err(VirtualMachineError::UnfinishedExecution); + } + + Ok(()) + } + /// Execute an exact number of steps on the program from the actual position. pub fn run_for_steps( &mut self, @@ -1168,6 +1694,37 @@ impl CairoRunner { Ok(()) } + /// Like [run_from_entrypoint](Self::run_from_entrypoint), but calls + /// [run_until_pc_v2](Self::run_until_pc_v2) instead. + #[allow(clippy::result_large_err)] + pub fn run_from_entrypoint_v2( + &mut self, + entrypoint: usize, + args: &[&CairoArg], + verify_secure: bool, + program_segment_size: Option, + hint_processor: &mut dyn HintProcessor, + ) -> Result<(), CairoRunError> { + let stack = args + .iter() + .map(|arg| self.vm.segments.gen_cairo_arg(arg)) + .collect::, VirtualMachineError>>()?; + let return_fp = MaybeRelocatable::from(0); + let end = self.initialize_function_entrypoint(entrypoint, stack, return_fp)?; + + self.initialize_vm()?; + + self.run_until_pc_v2(end, hint_processor) + .map_err(|err| VmException::from_vm_error(self, err))?; + self.end_run(true, false, hint_processor)?; + + if verify_secure { + verify_secure_runner(self, false, program_segment_size)?; + } + + Ok(()) + } + // Returns Ok(()) if there are enough allocated cells for the builtins. // If not, the number of steps should be increased or a different layout should be used. pub fn check_used_cells(&self) -> Result<(), VirtualMachineError> { diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 0807eb5016..cee428153a 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1,5 +1,5 @@ use crate::math_utils::signed_felt; -use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, prelude::*}; +use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, mem, prelude::*, rc::Rc}; use crate::types::builtin_name::BuiltinName; #[cfg(feature = "extensive_hints")] use crate::types::program::HintRange; @@ -535,6 +535,26 @@ impl VirtualMachine { Ok(()) } + /// Like [step_hint](Self::step_hint), but with a different signature. See + /// [CairoRunner::run_until_pc_v2] for documentation on why the signature + /// had to be changed. + /// + /// [CairoRunner::run_until_pc_v2]: crate::vm::runners::cairo_runner::CairoRunner::run_until_pc_v2 + #[cfg(not(feature = "extensive_hints"))] + pub(crate) fn step_hint_v2( + &mut self, + hint_processor: &mut dyn HintProcessor, + exec_scopes: &mut ExecutionScopes, + hint_datas: &[Rc>], + ) -> Result<(), VirtualMachineError> { + for (hint_index, hint_data) in hint_datas.iter().enumerate() { + hint_processor + .execute_hint(self, exec_scopes, hint_data.as_ref()) + .map_err(|err| VirtualMachineError::Hint(Box::new((hint_index, err))))? + } + Ok(()) + } + #[cfg(feature = "extensive_hints")] pub fn step_hint( &mut self, @@ -544,18 +564,20 @@ impl VirtualMachine { hint_ranges: &mut HashMap, ) -> Result<(), VirtualMachineError> { // Check if there is a hint range for the current pc - if let Some((s, l)) = hint_ranges.get(&self.run_context.pc) { + if let Some((start_hint_idx, n_hints)) = hint_ranges.get(&self.run_context.pc) { // Re-binding to avoid mutability problems - let s = *s; + let start_hint_idx = *start_hint_idx; // Execute each hint for the given range - for idx in s..(s + l.get()) { + for idx in start_hint_idx..(start_hint_idx + n_hints.get()) { let hint_extension = hint_processor .execute_hint_extensive( self, exec_scopes, hint_datas.get(idx).ok_or(VirtualMachineError::Unexpected)?, ) - .map_err(|err| VirtualMachineError::Hint(Box::new((idx - s, err))))?; + .map_err(|err| { + VirtualMachineError::Hint(Box::new((idx - start_hint_idx, err))) + })?; // Update the hint_ranges & hint_datas with the hints added by the executed hint for (hint_pc, hints) in hint_extension { if let Ok(len) = NonZeroUsize::try_from(hints.len()) { @@ -568,6 +590,46 @@ impl VirtualMachine { Ok(()) } + /// Like [step_hint](Self::step_hint), but with a different signature. See + /// [CairoRunner::run_until_pc_v2] for documentation on why the signature + /// had to be changed. + /// + /// [CairoRunner::run_until_pc_v2]: crate::vm::runners::cairo_runner::CairoRunner::run_until_pc_v2 + #[cfg(feature = "extensive_hints")] + pub(crate) fn step_hint_v2( + &mut self, + hint_processor: &mut dyn HintProcessor, + exec_scopes: &mut ExecutionScopes, + hint_datas: &mut Vec>>, + hint_ranges: &mut HashMap, + ) -> Result<(), VirtualMachineError> { + // Check if there is a hint range for the current pc + if let Some((start_hint_idx, n_hints)) = hint_ranges.get(&self.run_context.pc) { + // Re-binding to avoid mutability problems + let start_hint_idx = *start_hint_idx; + // Execute each hint for the given range + for idx in start_hint_idx..(start_hint_idx + n_hints.get()) { + let hint_data = hint_datas + .get(idx) + .ok_or(VirtualMachineError::Unexpected)? + .as_ref(); + let hint_extension = hint_processor + .execute_hint_extensive(self, exec_scopes, hint_data) + .map_err(|err| { + VirtualMachineError::Hint(Box::new((idx - start_hint_idx, err))) + })?; + // Update the hint_ranges & hint_datas with the hints added by the executed hint + for (hint_pc, hints) in hint_extension { + if let Ok(len) = NonZeroUsize::try_from(hints.len()) { + hint_ranges.insert(hint_pc, (hint_datas.len(), len)); + hint_datas.extend(hints.into_iter().map(Rc::new)); + } + } + } + } + Ok(()) + } + pub fn step_instruction(&mut self) -> Result<(), VirtualMachineError> { if self.run_context.pc.segment_index == 0 { // Run instructions from program segment, using instruction cache @@ -633,6 +695,35 @@ impl VirtualMachine { Ok(()) } + /// Like [step](Self::step), but with a different signature. See + /// [CairoRunner::run_until_pc_v2] for documentation on why the signature + /// had to be changed. + /// + /// Also, this new function does not call step hooks from the + /// `test_utils` features, as doing so would imply breaking the hook API. + /// + /// [CairoRunner::run_until_pc_v2]: crate::vm::runners::cairo_runner::CairoRunner::run_until_pc_v2 + pub(crate) fn step_v2( + &mut self, + hint_processor: &mut dyn HintProcessor, + exec_scopes: &mut ExecutionScopes, + #[cfg(feature = "extensive_hints")] hint_datas: &mut Vec>>, + #[cfg(not(feature = "extensive_hints"))] hint_datas: &[Rc>], + #[cfg(feature = "extensive_hints")] hint_ranges: &mut HashMap, + ) -> Result<(), VirtualMachineError> { + self.step_hint_v2( + hint_processor, + exec_scopes, + hint_datas, + #[cfg(feature = "extensive_hints")] + hint_ranges, + )?; + + self.step_instruction()?; + + Ok(()) + } + fn compute_op0_deductions( &self, op0_addr: Relocatable, @@ -1257,6 +1348,10 @@ impl VirtualMachine { .finalize(Some(info.size), info.index as usize, None) } } + + pub fn take_instruction_cache(&mut self) -> Vec> { + mem::take(&mut self.instruction_cache) + } } pub struct VirtualMachineBuilder { @@ -1265,6 +1360,7 @@ pub struct VirtualMachineBuilder { pub(crate) segments: MemorySegmentManager, pub(crate) trace: Option>, pub(crate) current_step: usize, + instruction_cache: Vec>, skip_instruction_execution: bool, run_finished: bool, #[cfg(feature = "test_utils")] @@ -1289,6 +1385,7 @@ impl Default for VirtualMachineBuilder { run_finished: false, #[cfg(feature = "test_utils")] hooks: Default::default(), + instruction_cache: Vec::new(), } } } @@ -1338,6 +1435,14 @@ impl VirtualMachineBuilder { self } + pub fn instruction_cache( + mut self, + instruction_cache: Vec>, + ) -> VirtualMachineBuilder { + self.instruction_cache = instruction_cache; + self + } + pub fn build(self) -> VirtualMachine { VirtualMachine { run_context: self.run_context, @@ -1349,7 +1454,7 @@ impl VirtualMachineBuilder { segments: self.segments, rc_limits: None, run_finished: self.run_finished, - instruction_cache: Vec::new(), + instruction_cache: self.instruction_cache, #[cfg(feature = "test_utils")] hooks: self.hooks, relocation_table: None, diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index c3546f7d00..02ee64d424 100644 --- a/vm/src/vm/vm_memory/memory.rs +++ b/vm/src/vm/vm_memory/memory.rs @@ -1,4 +1,4 @@ -use crate::stdlib::{borrow::Cow, collections::HashMap, fmt, prelude::*}; +use crate::stdlib::{borrow::Cow, collections::HashMap, fmt, prelude::*, rc::Rc}; use crate::types::errors::math_errors::MathError; use crate::vm::runners::cairo_pie::CairoPieMemory; @@ -106,6 +106,7 @@ impl From for MaybeRelocatable { } } +#[derive(Clone)] pub struct AddressSet(Vec); impl AddressSet { @@ -156,6 +157,7 @@ impl AddressSet { } } +#[derive(Clone)] pub struct Memory { pub(crate) data: Vec>, /// Temporary segments are used when it's necessary to write data, but we @@ -171,7 +173,7 @@ pub struct Memory { #[cfg(feature = "extensive_hints")] pub(crate) relocation_rules: HashMap, pub validated_addresses: AddressSet, - validation_rules: Vec>, + validation_rules: Vec>>, } impl Memory { @@ -498,7 +500,8 @@ impl Memory { self.validation_rules .resize_with(segment_index + 1, || None); } - self.validation_rules.insert(segment_index, Some(rule)); + self.validation_rules + .insert(segment_index, Some(Rc::new(rule))); } fn validate_memory_cell(&mut self, addr: Relocatable) -> Result<(), MemoryError> { @@ -744,6 +747,22 @@ impl Memory { self.mark_as_accessed(key); Ok(()) } + + /// Preallocates memory for `n` more elements in the given segment. + /// + /// Why not using using `reserve`? When cloning a vector, the capacity + /// of the clone is not necessary equal to the capacity of the original. + /// Because of this, to actually preallocate memory in the builder, we need + /// to insert empty memory cells. + pub fn preallocate_segment( + &mut self, + segment: Relocatable, + n: usize, + ) -> Result<(), MemoryError> { + let segment = self.get_segment(segment)?; + segment.extend((0..n).map(|_| MemoryCell::NONE)); + Ok(()) + } } impl From<&Memory> for CairoPieMemory { diff --git a/vm/src/vm/vm_memory/memory_segments.rs b/vm/src/vm/vm_memory/memory_segments.rs index e18e739346..a201bf97dd 100644 --- a/vm/src/vm/vm_memory/memory_segments.rs +++ b/vm/src/vm/vm_memory/memory_segments.rs @@ -20,6 +20,7 @@ use crate::{ use super::memory::MemoryCell; +#[derive(Clone)] pub struct MemorySegmentManager { pub segment_sizes: HashMap, pub segment_used_sizes: Option>,