From fd093e742eb75cd167330d8adaebb7168aa4c708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Mon, 6 Oct 2025 18:24:16 -0300 Subject: [PATCH 01/22] Make fields clonable --- vm/src/types/instance_definitions/builtins_instance_def.rs | 2 +- .../instance_definitions/diluted_pool_instance_def.rs | 2 +- .../types/instance_definitions/range_check_instance_def.rs | 2 +- vm/src/types/layout.rs | 2 +- vm/src/vm/runners/builtin_runner/range_check.rs | 3 ++- vm/src/vm/runners/builtin_runner/signature.rs | 2 +- vm/src/vm/vm_memory/memory.rs | 7 +++++-- vm/src/vm/vm_memory/memory_segments.rs | 1 + 8 files changed, 13 insertions(+), 8 deletions(-) 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/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index a4800bcf78..65b59194d4 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -3,6 +3,7 @@ use crate::{ stdlib::{ cmp::{max, min}, prelude::*, + rc::Rc, }, types::{builtin_name::BuiltinName, instance_definitions::LowRatio}, }; @@ -106,7 +107,7 @@ impl RangeCheckBuiltinRunner { } pub fn add_validation_rule(&self, memory: &mut Memory) { - let rule = ValidationRule(Box::new( + let rule = ValidationRule(Rc::new( |memory: &Memory, address: Relocatable| -> Result, MemoryError> { let num = memory .get_integer(address) diff --git a/vm/src/vm/runners/builtin_runner/signature.rs b/vm/src/vm/runners/builtin_runner/signature.rs index 8f2bda7510..ce0643a312 100644 --- a/vm/src/vm/runners/builtin_runner/signature.rs +++ b/vm/src/vm/runners/builtin_runner/signature.rs @@ -96,7 +96,7 @@ impl SignatureBuiltinRunner { pub fn add_validation_rule(&self, memory: &mut Memory) { let cells_per_instance = CELLS_PER_SIGNATURE; let signatures = Rc::clone(&self.signatures); - let rule: ValidationRule = ValidationRule(Box::new( + let rule: ValidationRule = ValidationRule(Rc::new( move |memory: &Memory, addr: Relocatable| -> Result, MemoryError> { let cell_index = addr.offset % cells_per_instance as usize; diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index c3546f7d00..567acd71cd 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; @@ -12,9 +12,10 @@ use bitvec::prelude as bv; use core::cmp::Ordering; use num_traits::ToPrimitive; +#[derive(Clone)] pub struct ValidationRule( #[allow(clippy::type_complexity)] - pub Box Result, MemoryError>>, + pub Rc Result, MemoryError>>, ); /// [`MemoryCell`] represents an optimized storage layout for the VM memory. @@ -106,6 +107,7 @@ impl From for MaybeRelocatable { } } +#[derive(Clone)] pub struct AddressSet(Vec); impl AddressSet { @@ -156,6 +158,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 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>, From 47c140af6f5f7e55c738a154cbe1b984fd36eb26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Tue, 7 Oct 2025 15:06:00 -0300 Subject: [PATCH 02/22] Add basic cairo runner builder --- vm/src/vm/runners/cairo_runner.rs | 259 ++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 2a0ecfe964..96f2f15b03 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -13,6 +13,7 @@ use crate::{ vm::{ runners::builtin_runner::SegmentArenaBuiltinRunner, trace::trace_entry::{relocate_trace_register, RelocatedTraceEntry, TraceEntry}, + vm_memory::memory_segments::MemorySegmentManager, }, Felt252, }; @@ -141,6 +142,264 @@ impl ResourceTracker for RunResources { } } +#[derive(Clone)] +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 instruction cache. + // instructions: Vec>, + // Set after compiling hints. + // hints: Vec>, +} + +impl CairoRunnerBuilder { + 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(), + }) + } + + 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 + } + + fn add_memory_segment(&mut self) -> Relocatable { + self.memory.add() + } + + 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(()) + } + + pub fn initialize_segments(&mut self) { + self.program_base = Some(self.add_memory_segment()); + self.execution_base = Some(self.add_memory_segment()); + for builtin_runner in self.builtin_runners.iter_mut() { + builtin_runner.initialize_segments(&mut self.memory); + } + } + + pub fn load_program(&mut self) -> Result<(), RunnerError> { + let program_base = self.program_base.ok_or(RunnerError::NoProgBase)?; + self.memory + .load_data(program_base, &self.program.shared_program_data.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)?); + } + Ok(()) + } + + pub fn build(self) -> Result { + let mut vm = VirtualMachine::new(self.enable_trace, self.disable_trace_padding); + + vm.builtin_runners = self.builtin_runners; + vm.segments = self.memory; + + 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, + }) + } +} + pub struct CairoRunner { pub vm: VirtualMachine, pub(crate) program: Program, From f17b0e39f994254b15023f17c0c58498e1e0d231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Tue, 7 Oct 2025 15:14:03 -0300 Subject: [PATCH 03/22] Add support for caching loaded program --- vm/src/vm/runners/cairo_runner.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 96f2f15b03..6bbc2dc31f 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -157,6 +157,8 @@ pub struct CairoRunnerBuilder { program_base: Option, execution_base: Option, memory: MemorySegmentManager, + // Set after loading program. + loaded_program: bool, // Set after loading instruction cache. // instructions: Vec>, // Set after compiling hints. @@ -200,6 +202,7 @@ impl CairoRunnerBuilder { program_base: None, execution_base: None, memory: MemorySegmentManager::new(), + loaded_program: false, }) } @@ -365,6 +368,7 @@ impl CairoRunnerBuilder { for i in 0..self.program.shared_program_data.data.len() { self.memory.memory.mark_as_accessed((program_base + i)?); } + self.loaded_program = true; Ok(()) } @@ -396,6 +400,7 @@ impl CairoRunnerBuilder { exec_scopes: ExecutionScopes::new(), relocated_trace: None, program: self.program, + loaded_program: self.loaded_program, }) } } @@ -418,6 +423,7 @@ pub struct CairoRunner { pub relocated_memory: Vec>, pub exec_scopes: ExecutionScopes, pub relocated_trace: Option>, + loaded_program: bool, } #[derive(Clone, Debug, PartialEq)] @@ -479,6 +485,7 @@ impl CairoRunner { None }, relocated_trace: None, + loaded_program: false, }) } @@ -745,13 +752,16 @@ 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.loaded_program = true } self.vm .segments From a93adb9ca7dfbfd94769e45211b427ce071e6e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Wed, 8 Oct 2025 14:47:16 -0300 Subject: [PATCH 04/22] Add support for precompiling hints --- vm/src/vm/runners/cairo_runner.rs | 91 ++++++++++++++++++++++++++++++- vm/src/vm/vm_core.rs | 71 +++++++++++++++++++++++- 2 files changed, 158 insertions(+), 4 deletions(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 6bbc2dc31f..3f2a3cb218 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -162,7 +162,7 @@ pub struct CairoRunnerBuilder { // Set after loading instruction cache. // instructions: Vec>, // Set after compiling hints. - // hints: Vec>, + hints: Option>>>, } impl CairoRunnerBuilder { @@ -203,6 +203,7 @@ impl CairoRunnerBuilder { execution_base: None, memory: MemorySegmentManager::new(), loaded_program: false, + hints: None, }) } @@ -221,7 +222,7 @@ impl CairoRunnerBuilder { || self.runner_mode == RunnerMode::ProofModeCairo1 } - fn add_memory_segment(&mut self) -> Relocatable { + pub fn add_memory_segment(&mut self) -> Relocatable { self.memory.add() } @@ -352,9 +353,12 @@ impl CairoRunnerBuilder { Ok(()) } - pub fn initialize_segments(&mut self) { + pub fn initialize_base_segments(&mut self) { self.program_base = Some(self.add_memory_segment()); self.execution_base = Some(self.add_memory_segment()); + } + + pub fn initialize_builtin_segments(&mut self) { for builtin_runner in self.builtin_runners.iter_mut() { builtin_runner.initialize_segments(&mut self.memory); } @@ -372,6 +376,33 @@ impl CairoRunnerBuilder { Ok(()) } + pub fn compile_hints( + &mut self, + hint_processor: &mut dyn HintProcessor, + ) -> Result<(), VirtualMachineError> { + let constants = Rc::new(self.program.constants.clone()); + 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 mut vm = VirtualMachine::new(self.enable_trace, self.disable_trace_padding); @@ -401,6 +432,7 @@ impl CairoRunnerBuilder { relocated_trace: None, program: self.program, loaded_program: self.loaded_program, + hints: self.hints, }) } } @@ -424,6 +456,7 @@ pub struct CairoRunner { pub exec_scopes: ExecutionScopes, pub relocated_trace: Option>, loaded_program: bool, + hints: Option>>>, } #[derive(Clone, Debug, PartialEq)] @@ -486,6 +519,7 @@ impl CairoRunner { }, relocated_trace: None, loaded_program: false, + hints: None, }) } @@ -991,6 +1025,57 @@ impl CairoRunner { Ok(()) } + 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, diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 0807eb5016..d7d6cdba97 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, prelude::*, rc::Rc}; use crate::types::builtin_name::BuiltinName; #[cfg(feature = "extensive_hints")] use crate::types::program::HintRange; @@ -535,6 +535,21 @@ impl VirtualMachine { Ok(()) } + #[cfg(not(feature = "extensive_hints"))] + pub 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, @@ -568,6 +583,39 @@ impl VirtualMachine { Ok(()) } + #[cfg(feature = "extensive_hints")] + pub 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((s, l)) = hint_ranges.get(&self.run_context.pc) { + // Re-binding to avoid mutability problems + let s = *s; + // Execute each hint for the given range + for idx in s..(s + l.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 - s, 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 +681,27 @@ impl VirtualMachine { Ok(()) } + pub 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, From 73afcc1684c0aec56fe6f2b5f8a5dd42c4540c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Wed, 8 Oct 2025 14:53:06 -0300 Subject: [PATCH 05/22] Take constants when precomipling hints, as they are not used --- vm/src/vm/runners/cairo_runner.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 3f2a3cb218..012809a6bf 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -5,6 +5,7 @@ use crate::{ stdlib::{ any::Any, collections::{BTreeMap, HashMap, HashSet}, + mem, ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, prelude::*, rc::Rc, @@ -380,7 +381,7 @@ impl CairoRunnerBuilder { &mut self, hint_processor: &mut dyn HintProcessor, ) -> Result<(), VirtualMachineError> { - let constants = Rc::new(self.program.constants.clone()); + let constants = Rc::new(mem::take(&mut self.program.constants)); let references = &self.program.shared_program_data.reference_manager; let compiled_hints = self .program From 2a4792cb13d154e0665ee878e478d517a6cf34ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Wed, 8 Oct 2025 15:05:45 -0300 Subject: [PATCH 06/22] Add run_from_entrypoint_v2 --- vm/src/vm/runners/cairo_runner.rs | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 012809a6bf..3bbd161d17 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1523,6 +1523,38 @@ impl CairoRunner { Ok(()) } + #[allow(clippy::result_large_err)] + /// Runs a cairo program from a give entrypoint, indicated by its pc offset, with the given arguments. + /// If `verify_secure` is set to true, [verify_secure_runner] will be called to run extra verifications. + /// `program_segment_size` is only used by the [verify_secure_runner] function and will be ignored if `verify_secure` is set to false. + 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> { From 55a7ea53914bd3ad30ff3abdf3d5ad1f0c55ef37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Wed, 8 Oct 2025 18:05:04 -0300 Subject: [PATCH 07/22] make `ValidationRule`s clonable, without API breakage --- vm/src/vm/runners/builtin_runner/range_check.rs | 3 +-- vm/src/vm/runners/builtin_runner/signature.rs | 2 +- vm/src/vm/vm_memory/memory.rs | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/vm/src/vm/runners/builtin_runner/range_check.rs b/vm/src/vm/runners/builtin_runner/range_check.rs index 65b59194d4..a4800bcf78 100644 --- a/vm/src/vm/runners/builtin_runner/range_check.rs +++ b/vm/src/vm/runners/builtin_runner/range_check.rs @@ -3,7 +3,6 @@ use crate::{ stdlib::{ cmp::{max, min}, prelude::*, - rc::Rc, }, types::{builtin_name::BuiltinName, instance_definitions::LowRatio}, }; @@ -107,7 +106,7 @@ impl RangeCheckBuiltinRunner { } pub fn add_validation_rule(&self, memory: &mut Memory) { - let rule = ValidationRule(Rc::new( + let rule = ValidationRule(Box::new( |memory: &Memory, address: Relocatable| -> Result, MemoryError> { let num = memory .get_integer(address) diff --git a/vm/src/vm/runners/builtin_runner/signature.rs b/vm/src/vm/runners/builtin_runner/signature.rs index ce0643a312..8f2bda7510 100644 --- a/vm/src/vm/runners/builtin_runner/signature.rs +++ b/vm/src/vm/runners/builtin_runner/signature.rs @@ -96,7 +96,7 @@ impl SignatureBuiltinRunner { pub fn add_validation_rule(&self, memory: &mut Memory) { let cells_per_instance = CELLS_PER_SIGNATURE; let signatures = Rc::clone(&self.signatures); - let rule: ValidationRule = ValidationRule(Rc::new( + let rule: ValidationRule = ValidationRule(Box::new( move |memory: &Memory, addr: Relocatable| -> Result, MemoryError> { let cell_index = addr.offset % cells_per_instance as usize; diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index 567acd71cd..592dbb6a58 100644 --- a/vm/src/vm/vm_memory/memory.rs +++ b/vm/src/vm/vm_memory/memory.rs @@ -12,10 +12,9 @@ use bitvec::prelude as bv; use core::cmp::Ordering; use num_traits::ToPrimitive; -#[derive(Clone)] pub struct ValidationRule( #[allow(clippy::type_complexity)] - pub Rc Result, MemoryError>>, + pub Box Result, MemoryError>>, ); /// [`MemoryCell`] represents an optimized storage layout for the VM memory. @@ -174,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 { @@ -501,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> { From 85cccab1cfcd4a0a989c183cbebc3c7ca3b38889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Wed, 8 Oct 2025 18:07:57 -0300 Subject: [PATCH 08/22] If you call initialize_state twice, it should load the program twice To maintain behaviour backwards compatibility --- vm/src/vm/runners/cairo_runner.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 3bbd161d17..7af871ccb7 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -796,7 +796,6 @@ impl CairoRunner { for i in 0..self.program.shared_program_data.data.len() { self.vm.segments.memory.mark_as_accessed((prog_base + i)?); } - self.loaded_program = true } self.vm .segments From 9728017c090e74d84b88fb6b1af9b744b9406b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Wed, 8 Oct 2025 18:18:53 -0300 Subject: [PATCH 09/22] Add more comments --- vm/src/vm/runners/cairo_runner.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 7af871ccb7..3baffb48e2 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -160,13 +160,15 @@ pub struct CairoRunnerBuilder { memory: MemorySegmentManager, // Set after loading program. loaded_program: bool, - // Set after loading instruction cache. - // instructions: Vec>, // Set after compiling hints. hints: Option>>>, + // TODO: 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, @@ -227,6 +229,8 @@ impl CairoRunnerBuilder { self.memory.add() } + // TODO: Cloning the builder after calling this function leads to bad + // behaviour (transactions revert). Why? pub fn initialize_builtin_runners_for_layout(&mut self) -> Result<(), RunnerError> { let builtin_ordered_list = vec![ BuiltinName::output, @@ -377,6 +381,20 @@ impl CairoRunnerBuilder { 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, From c6e707786322d9467cbfcbe637ccbc6ca538319d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Thu, 9 Oct 2025 12:24:30 -0300 Subject: [PATCH 10/22] Add support for caching instructions, and improve documentation --- vm/src/vm/runners/cairo_runner.rs | 61 ++++++++++++++++++++++++++----- vm/src/vm/vm_core.rs | 18 ++++++++- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 3baffb48e2..1ac83436c3 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -10,10 +10,14 @@ use crate::{ 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, @@ -162,8 +166,8 @@ pub struct CairoRunnerBuilder { loaded_program: bool, // Set after compiling hints. hints: Option>>>, - // TODO: Set after loading instruction cache. - // instructions: Vec>, + // Set after loading instruction cache. + instructions: Vec>, } impl CairoRunnerBuilder { @@ -207,6 +211,7 @@ impl CairoRunnerBuilder { memory: MemorySegmentManager::new(), loaded_program: false, hints: None, + instructions: Vec::new(), }) } @@ -229,8 +234,15 @@ impl CairoRunnerBuilder { self.memory.add() } - // TODO: Cloning the builder after calling this function leads to bad - // behaviour (transactions revert). Why? + /// *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`. + /// + /// TODO: Cloning the builder after calling this function leads to bad + /// behaviour (transactions revert). Why? pub fn initialize_builtin_runners_for_layout(&mut self) -> Result<(), RunnerError> { let builtin_ordered_list = vec![ BuiltinName::output, @@ -363,21 +375,49 @@ impl CairoRunnerBuilder { 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) 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 + /// automataically 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, &self.program.shared_program_data.data) + .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(()) + } + + /// Predecodes the program's 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]: crate::vm::vm_core::VirtualMachine::take_instruction_cache + pub fn load_cached_instructions( + &mut self, + instructions: Vec>, + ) -> Result<(), RunnerError> { + self.instructions = instructions; Ok(()) } @@ -423,10 +463,11 @@ impl CairoRunnerBuilder { } pub fn build(self) -> Result { - let mut vm = VirtualMachine::new(self.enable_trace, self.disable_trace_padding); - - vm.builtin_runners = self.builtin_runners; - vm.segments = self.memory; + let vm = VirtualMachineBuilder::default() + .builtin_runners(self.builtin_runners) + .segments(self.memory) + .instruction_cache(self.instructions) + .build(); Ok(CairoRunner { vm, diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index d7d6cdba97..67c7595b56 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::*, rc::Rc}; +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; @@ -1326,6 +1326,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 { @@ -1334,6 +1338,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")] @@ -1358,6 +1363,7 @@ impl Default for VirtualMachineBuilder { run_finished: false, #[cfg(feature = "test_utils")] hooks: Default::default(), + instruction_cache: Vec::new(), } } } @@ -1407,6 +1413,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, @@ -1418,7 +1432,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, From ddd8e971e1ffac83a76a9c156867feb2151f9f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Thu, 9 Oct 2025 14:21:52 -0300 Subject: [PATCH 11/22] Add comment --- vm/src/vm/runners/cairo_runner.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 1ac83436c3..e6bab49df1 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -165,6 +165,9 @@ pub struct CairoRunnerBuilder { // 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>, From 513bf4bf5091870da33b669be45b3735f22a72ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Thu, 9 Oct 2025 14:47:21 -0300 Subject: [PATCH 12/22] Improve documentation --- vm/src/vm/runners/cairo_runner.rs | 11 ++++++++--- vm/src/vm/vm_core.rs | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index e6bab49df1..3cabad80c3 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1087,6 +1087,12 @@ 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. pub fn run_until_pc_v2( &mut self, address: Relocatable, @@ -1584,10 +1590,9 @@ 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)] - /// Runs a cairo program from a give entrypoint, indicated by its pc offset, with the given arguments. - /// If `verify_secure` is set to true, [verify_secure_runner] will be called to run extra verifications. - /// `program_segment_size` is only used by the [verify_secure_runner] function and will be ignored if `verify_secure` is set to false. pub fn run_from_entrypoint_v2( &mut self, entrypoint: usize, diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 67c7595b56..80ad694d66 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -535,6 +535,11 @@ 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 fn step_hint_v2( &mut self, @@ -583,6 +588,11 @@ 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 fn step_hint_v2( &mut self, @@ -681,6 +691,11 @@ 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. + /// + /// [CairoRunner::run_until_pc_v2]: crate::vm::runners::cairo_runner::CairoRunner::run_until_pc_v2 pub fn step_v2( &mut self, hint_processor: &mut dyn HintProcessor, From 3cbae884cd7d6f7fb1ca031306e54fb045fb8774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Thu, 9 Oct 2025 14:52:39 -0300 Subject: [PATCH 13/22] Improve documentation on hook API --- vm/src/vm/runners/cairo_runner.rs | 3 +++ vm/src/vm/vm_core.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 3cabad80c3..98bcd0e77f 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1093,6 +1093,9 @@ impl CairoRunner { /// 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, diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 80ad694d66..f949d1ecd6 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -695,6 +695,9 @@ impl VirtualMachine { /// [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 fn step_v2( &mut self, From 767e5d3d534727e3bcbd68d77fb5b410fbc2b7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Thu, 9 Oct 2025 14:57:32 -0300 Subject: [PATCH 14/22] Improve docs, make functions internal --- vm/src/vm/runners/cairo_runner.rs | 4 ++-- vm/src/vm/vm_core.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 98bcd0e77f..bef30f88a4 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -407,7 +407,7 @@ impl CairoRunnerBuilder { Ok(()) } - /// Predecodes the program's instructions. + /// Loads decoded program instructions. /// /// # Safety /// @@ -415,7 +415,7 @@ impl CairoRunnerBuilder { /// obtain them, call [VirtualMachine::take_instruction_cache] at the end of /// the execution. /// - /// [VirtualMachine::take_instruction_cache]: crate::vm::vm_core::VirtualMachine::take_instruction_cache + /// [VirtualMachine::take_instruction_cache]: VirtualMachine::take_instruction_cache pub fn load_cached_instructions( &mut self, instructions: Vec>, diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index f949d1ecd6..a2b887b681 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -541,7 +541,7 @@ impl VirtualMachine { /// /// [CairoRunner::run_until_pc_v2]: crate::vm::runners::cairo_runner::CairoRunner::run_until_pc_v2 #[cfg(not(feature = "extensive_hints"))] - pub fn step_hint_v2( + pub(crate) fn step_hint_v2( &mut self, hint_processor: &mut dyn HintProcessor, exec_scopes: &mut ExecutionScopes, @@ -594,7 +594,7 @@ impl VirtualMachine { /// /// [CairoRunner::run_until_pc_v2]: crate::vm::runners::cairo_runner::CairoRunner::run_until_pc_v2 #[cfg(feature = "extensive_hints")] - pub fn step_hint_v2( + pub(crate) fn step_hint_v2( &mut self, hint_processor: &mut dyn HintProcessor, exec_scopes: &mut ExecutionScopes, @@ -699,7 +699,7 @@ impl VirtualMachine { /// `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 fn step_v2( + pub(crate) fn step_v2( &mut self, hint_processor: &mut dyn HintProcessor, exec_scopes: &mut ExecutionScopes, From c001e4bff8a1f8333aa977a90a005bbea3a75c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Thu, 9 Oct 2025 16:39:35 -0300 Subject: [PATCH 15/22] Implement clone manually, add docs --- vm/src/vm/runners/cairo_runner.rs | 54 ++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index bef30f88a4..f64d79342d 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -4,6 +4,7 @@ use crate::{ math_utils::safe_div_usize, stdlib::{ any::Any, + cell::RefCell, collections::{BTreeMap, HashMap, HashSet}, mem, ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, @@ -55,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::{ @@ -147,7 +149,13 @@ impl ResourceTracker for RunResources { } } -#[derive(Clone)] +/// 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, @@ -243,9 +251,6 @@ impl CairoRunnerBuilder { /// Note that *initializing* a builtin implies creating a runner for it, /// and *including* a builtin refers to enabling the builtin runner flag: /// `included`. - /// - /// TODO: Cloning the builder after calling this function leads to bad - /// behaviour (transactions revert). Why? pub fn initialize_builtin_runners_for_layout(&mut self) -> Result<(), RunnerError> { let builtin_ordered_list = vec![ BuiltinName::output, @@ -500,6 +505,47 @@ impl CairoRunnerBuilder { } } +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, From 99c50815336b10eaaa7d3315a7a782fc44323d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Mon, 13 Oct 2025 12:43:58 -0300 Subject: [PATCH 16/22] Add support for caching Cairo 1 runners --- vm/src/vm/runners/cairo_runner.rs | 49 +++++++++++++++++++++++++++++++ vm/src/vm/vm_memory/memory.rs | 16 ++++++++++ 2 files changed, 65 insertions(+) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index f64d79342d..3a88e8fac7 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -241,6 +241,10 @@ impl CairoRunnerBuilder { || 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() } @@ -378,6 +382,41 @@ impl CairoRunnerBuilder { Ok(()) } + /// *Initializes* and *includes* all the given builtins. + /// + /// Doesn't take the current layout into account. + 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()); @@ -412,6 +451,16 @@ impl CairoRunnerBuilder { 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 diff --git a/vm/src/vm/vm_memory/memory.rs b/vm/src/vm/vm_memory/memory.rs index 592dbb6a58..02ee64d424 100644 --- a/vm/src/vm/vm_memory/memory.rs +++ b/vm/src/vm/vm_memory/memory.rs @@ -747,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 { From 84d2bf2e1db1d62e4ed7a54583b647a376df4e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Mon, 13 Oct 2025 15:03:32 -0300 Subject: [PATCH 17/22] Pin generic-array --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1e6d8339a4..8b8c843e64 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.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 } From 43b6b9d10dbb4b9dc7155ebd59fdc443802545b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Mon, 13 Oct 2025 15:30:37 -0300 Subject: [PATCH 18/22] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) 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) From 8df8954d6bafa79e15d52be0f2a4bf0b06941f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Tue, 14 Oct 2025 15:56:18 -0300 Subject: [PATCH 19/22] Improve naming --- vm/src/vm/vm_core.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index a2b887b681..cee428153a 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -564,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()) { @@ -602,18 +604,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_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 - 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()) { From 70ad9fd1e8c176bc0250bac39602d02549b55969 Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Wed, 15 Oct 2025 12:28:19 -0300 Subject: [PATCH 20/22] Fix typo Co-authored-by: DiegoC <90105443+DiegoCivi@users.noreply.github.com> --- vm/src/vm/runners/cairo_runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 3a88e8fac7..248f354289 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -436,7 +436,7 @@ impl CairoRunnerBuilder { /// Loads the program into the program segment. /// /// If this function is not called, the program will be loaded - /// automataically when initializing the entrypoint. + /// 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; From 84d3e77f2afb00ab17075a32199dd75c1fbb457a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Wed, 15 Oct 2025 12:39:27 -0300 Subject: [PATCH 21/22] Less restrictive generic-array pin --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8b8c843e64..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 } From 77801e5ee06df40152fc17cc83da4d4b9621b528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Thu, 16 Oct 2025 12:05:22 -0300 Subject: [PATCH 22/22] Add more comments --- vm/src/vm/runners/cairo_runner.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index 248f354289..f6eb61670e 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -255,6 +255,8 @@ impl CairoRunnerBuilder { /// 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, @@ -385,6 +387,9 @@ impl CairoRunnerBuilder { /// *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], @@ -426,7 +431,8 @@ impl CairoRunnerBuilder { /// /// Depends on: /// - [initialize_base_segments](Self::initialize_base_segments) - /// - [initialize_builtin_runners_for_layout](Self::initialize_builtin_runners_for_layout) + /// - [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);