diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index fbe0760d94..debc9980b8 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -44,7 +44,7 @@ use cairo_vm::{ }, vm::{ errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError}, - runners::cairo_runner::{CairoRunner, RunResources, RunnerMode}, + runners::cairo_runner::{CairoRunner, CairoRunnerBuilder, RunResources, RunnerMode}, vm_core::VirtualMachine, }, Felt252, @@ -250,16 +250,25 @@ pub fn cairo_run_program( RunnerMode::ExecutionMode }; - let mut runner = CairoRunner::new_v2( + let mut runner_builder = CairoRunnerBuilder::new( &program, cairo_run_config.layout, cairo_run_config.dynamic_layout_params.clone(), runner_mode, - cairo_run_config.trace_enabled, - false, )?; - let end = runner.initialize(cairo_run_config.proof_mode)?; - load_arguments(&mut runner, &cairo_run_config, main_func)?; + runner_builder.enable_trace(cairo_run_config.trace_enabled); + runner_builder.allow_missing_builtins(cairo_run_config.proof_mode); + runner_builder.initialize_builtin_runners_for_layout()?; + runner_builder.initialize_base_segments(); + runner_builder.load_program()?; + runner_builder.initialize_builtin_segments(); + runner_builder.initialize_builtin_zero_segments(); + let end = runner_builder.initialize_main_entrypoint()?; + runner_builder.initialize_validation_rules()?; + + load_arguments(&mut runner_builder, &cairo_run_config, main_func)?; + + let mut runner = runner_builder.build()?; // Run it until the end / infinite loop in proof_mode runner.run_until_pc(end, &mut hint_processor)?; @@ -460,7 +469,7 @@ After the entry_code (up until calling main) has been ran by the VM: (*4) if gas builtin is present */ fn load_arguments( - runner: &mut CairoRunner, + runner: &mut CairoRunnerBuilder, cairo_run_config: &Cairo1RunConfig, main_func: &Function, ) -> Result<(), Error> { @@ -500,25 +509,28 @@ fn load_arguments( for arg in cairo_run_config.args { match arg { FuncArg::Array(args) => { - let array_start = runner.vm.add_memory_segment(); - let array_end = runner.vm.load_data( + let array_start = runner.add_memory_segment(); + let array_end = runner.load_memory_array( array_start, &args.iter().map(|f| f.into()).collect::>(), )?; - runner.vm.insert_value( - (runner.vm.get_ap() + ap_offset).map_err(VirtualMachineError::Math)?, + runner.load_memory_value( + (runner.get_initial_ap().ok_or(RunnerError::NoAP)? + ap_offset) + .map_err(VirtualMachineError::Math)?, array_start, )?; ap_offset += 1; - runner.vm.insert_value( - (runner.vm.get_ap() + ap_offset).map_err(VirtualMachineError::Math)?, + runner.load_memory_value( + (runner.get_initial_ap().ok_or(RunnerError::NoAP)? + ap_offset) + .map_err(VirtualMachineError::Math)?, array_end, )?; ap_offset += 1; } FuncArg::Single(arg) => { - runner.vm.insert_value( - (runner.vm.get_ap() + ap_offset).map_err(VirtualMachineError::Math)?, + runner.load_memory_value( + (runner.get_initial_ap().ok_or(RunnerError::NoAP)? + ap_offset) + .map_err(VirtualMachineError::Math)?, arg, )?; ap_offset += 1; diff --git a/vm/src/cairo_run.rs b/vm/src/cairo_run.rs index eb63334e4c..886435e2a4 100644 --- a/vm/src/cairo_run.rs +++ b/vm/src/cairo_run.rs @@ -8,7 +8,10 @@ use crate::{ errors::{ cairo_run_errors::CairoRunError, runner_errors::RunnerError, vm_exception::VmException, }, - runners::{cairo_pie::CairoPie, cairo_runner::CairoRunner}, + runners::{ + cairo_pie::CairoPie, + cairo_runner::{CairoRunner, CairoRunnerBuilder, RunnerMode}, + }, security::verify_secure_runner, }, }; @@ -77,18 +80,31 @@ pub fn cairo_run_program_with_initial_scope( .allow_missing_builtins .unwrap_or(cairo_run_config.proof_mode); - let mut cairo_runner = CairoRunner::new( + let mut runner_builder = CairoRunnerBuilder::new( program, cairo_run_config.layout, cairo_run_config.dynamic_layout_params.clone(), - cairo_run_config.proof_mode, - cairo_run_config.trace_enabled, - cairo_run_config.disable_trace_padding, + if cairo_run_config.proof_mode { + RunnerMode::ProofModeCanonical + } else { + RunnerMode::ExecutionMode + }, )?; + runner_builder.enable_trace(cairo_run_config.trace_enabled); + runner_builder.disable_trace_padding(cairo_run_config.disable_trace_padding); + runner_builder.allow_missing_builtins(allow_missing_builtins); + runner_builder.initialize_builtin_runners_for_layout()?; + runner_builder.initialize_base_segments(); + runner_builder.load_program()?; + runner_builder.initialize_builtin_segments(); + runner_builder.initialize_builtin_zero_segments(); + let end = runner_builder.initialize_main_entrypoint()?; + runner_builder.initialize_validation_rules()?; + + let mut cairo_runner = runner_builder.build()?; cairo_runner.exec_scopes = exec_scopes; - let end = cairo_runner.initialize(allow_missing_builtins)?; // check step calculation cairo_runner @@ -171,16 +187,26 @@ pub fn cairo_run_pie( let allow_missing_builtins = cairo_run_config.allow_missing_builtins.unwrap_or_default(); let program = Program::from_stripped_program(&pie.metadata.program); - let mut cairo_runner = CairoRunner::new( + + let mut runner_builder = CairoRunnerBuilder::new( &program, cairo_run_config.layout, cairo_run_config.dynamic_layout_params.clone(), - false, - cairo_run_config.trace_enabled, - cairo_run_config.disable_trace_padding, + RunnerMode::ExecutionMode, )?; + runner_builder.enable_trace(cairo_run_config.trace_enabled); + runner_builder.disable_trace_padding(cairo_run_config.disable_trace_padding); + runner_builder.allow_missing_builtins(allow_missing_builtins); + runner_builder.initialize_builtin_runners_for_layout()?; + runner_builder.initialize_base_segments(); + runner_builder.load_program()?; + runner_builder.initialize_builtin_segments(); + runner_builder.initialize_builtin_zero_segments(); + let end = runner_builder.initialize_main_entrypoint()?; + runner_builder.initialize_validation_rules()?; + + let mut cairo_runner = runner_builder.build()?; - let end = cairo_runner.initialize(allow_missing_builtins)?; cairo_runner.vm.finalize_segments_by_cairo_pie(pie); // Load builtin additional data for (name, data) in pie.additional_data.0.iter() { @@ -246,16 +272,27 @@ pub fn cairo_run_fuzzed_program( .allow_missing_builtins .unwrap_or(cairo_run_config.proof_mode); - let mut cairo_runner = CairoRunner::new( + let mut runner_builder = CairoRunnerBuilder::new( &program, cairo_run_config.layout, cairo_run_config.dynamic_layout_params.clone(), - cairo_run_config.proof_mode, - cairo_run_config.trace_enabled, - cairo_run_config.disable_trace_padding, + if cairo_run_config.proof_mode { + RunnerMode::ProofModeCanonical + } else { + RunnerMode::ExecutionMode + }, )?; - - let _end = cairo_runner.initialize(allow_missing_builtins)?; + runner_builder.enable_trace(cairo_run_config.trace_enabled); + runner_builder.allow_missing_builtins(allow_missing_builtins); + runner_builder.disable_trace_padding(cairo_run_config.disable_trace_padding); + runner_builder.initialize_builtin_runners_for_layout()?; + runner_builder.initialize_base_segments(); + runner_builder.load_program()?; + runner_builder.initialize_builtin_segments(); + runner_builder.initialize_builtin_zero_segments(); + runner_builder.initialize_main_entrypoint()?; + runner_builder.initialize_validation_rules()?; + let mut cairo_runner = runner_builder.build()?; let res = match cairo_runner.run_until_steps(steps_limit, hint_processor) { Err(VirtualMachineError::EndOfProgram(_remaining)) => Ok(()), // program ran OK but ended before steps limit diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index 03758cc285..2f548d9e5c 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -4,7 +4,10 @@ use crate::{tests::*, types::layout_name::LayoutName}; use crate::{ utils::test_utils::Program, vm::{ - runners::{builtin_runner::BuiltinRunner, cairo_runner::CairoRunner}, + runners::{ + builtin_runner::BuiltinRunner, + cairo_runner::{CairoRunnerBuilder, RunnerMode}, + }, security::verify_secure_runner, }, }; @@ -1213,17 +1216,32 @@ fn run_program_with_custom_mod_builtin_params( }; let mut hint_processor = BuiltinHintProcessor::new_empty(); let program = Program::from_bytes(data, Some(cairo_run_config.entrypoint)).unwrap(); - let mut cairo_runner = CairoRunner::new( + let mut runner_builder = CairoRunnerBuilder::new( &program, cairo_run_config.layout, - cairo_run_config.dynamic_layout_params, - cairo_run_config.proof_mode, - cairo_run_config.trace_enabled, - cairo_run_config.disable_trace_padding, + cairo_run_config.dynamic_layout_params.clone(), + if cairo_run_config.proof_mode { + RunnerMode::ProofModeCanonical + } else { + RunnerMode::ExecutionMode + }, ) .unwrap(); + runner_builder.enable_trace(cairo_run_config.trace_enabled); + runner_builder.allow_missing_builtins(false); + runner_builder.disable_trace_padding(cairo_run_config.disable_trace_padding); + runner_builder + .initialize_builtin_runners_for_layout() + .unwrap(); + runner_builder.initialize_base_segments(); + runner_builder.load_program().unwrap(); + runner_builder.initialize_builtin_segments(); + runner_builder.initialize_builtin_zero_segments(); + let end = runner_builder.initialize_main_entrypoint().unwrap(); + runner_builder.initialize_validation_rules().unwrap(); + + let mut cairo_runner = runner_builder.build().unwrap(); - let end = cairo_runner.initialize(false).unwrap(); // Modify add_mod & mul_mod params for runner in cairo_runner.vm.get_builtin_runners_as_mut() { if let BuiltinRunner::Mod(runner) = runner { diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index 49358c29e1..676beeab15 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -10,7 +10,11 @@ use crate::Felt252; use crate::{ hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, types::{builtin_name::BuiltinName, relocatable::MaybeRelocatable}, - vm::runners::cairo_runner::{CairoArg, CairoRunner}, + vm::{ + errors::vm_exception::VmException, + runners::cairo_runner::{CairoArg, CairoRunnerBuilder, RunnerMode}, + security::verify_secure_runner, + }, }; #[cfg(feature = "cairo-1-hints")] use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; @@ -110,32 +114,31 @@ fn run_cairo_1_entrypoint( let contract_class: CasmContractClass = serde_json::from_slice(program_content).unwrap(); let mut hint_processor = Cairo1HintProcessor::new(&contract_class.hints, RunResources::default(), false); + let program_builtins = get_casm_contract_builtins(&contract_class, entrypoint_offset); + + let program = contract_class.clone().try_into().unwrap(); - let mut runner = CairoRunner::new( - &(contract_class.clone().try_into().unwrap()), + let mut runner_builder = CairoRunnerBuilder::new( + &program, LayoutName::all_cairo, None, - false, - false, - false, + RunnerMode::ExecutionMode, ) .unwrap(); - - let program_builtins = get_casm_contract_builtins(&contract_class, entrypoint_offset); - runner - .initialize_function_runner_cairo_1(&program_builtins) + runner_builder.initialize_base_segments(); + runner_builder + .initialize_builtin_runners(&program_builtins) .unwrap(); + runner_builder.initialize_builtin_segments(); + runner_builder.load_program().unwrap(); // Implicit Args - let syscall_segment = MaybeRelocatable::from(runner.vm.add_memory_segment()); - - let builtins = runner.get_program_builtins(); + let syscall_segment = MaybeRelocatable::from(runner_builder.add_memory_segment()); - let builtin_segment: Vec = runner - .vm + let builtin_segment: Vec = runner_builder .get_builtin_runners() .iter() - .filter(|b| builtins.contains(&b.name())) + .filter(|b| program_builtins.contains(&b.name())) .flat_map(|b| b.initial_stack()) .collect(); @@ -150,28 +153,28 @@ fn run_cairo_1_entrypoint( // Load builtin costs let builtin_costs: Vec = vec![0.into(), 0.into(), 0.into(), 0.into(), 0.into()]; - let builtin_costs_ptr = runner.vm.add_memory_segment(); - runner - .vm - .load_data(builtin_costs_ptr, &builtin_costs) + let builtin_costs_ptr = runner_builder.add_memory_segment(); + runner_builder + .load_memory_array(builtin_costs_ptr, &builtin_costs) .unwrap(); // Load extra data - let core_program_end_ptr = - (runner.program_base.unwrap() + runner.program.shared_program_data.data.len()).unwrap(); + let core_program_end_ptr = (runner_builder.get_program_base().unwrap() + + program.shared_program_data.data.len()) + .unwrap(); let program_extra_data: Vec = vec![0x208B7FFF7FFF7FFE.into(), builtin_costs_ptr.into()]; - runner - .vm - .load_data(core_program_end_ptr, &program_extra_data) + runner_builder + .load_memory_array(core_program_end_ptr, &program_extra_data) .unwrap(); // Load calldata - let calldata_start = runner.vm.add_memory_segment(); - let calldata_end = runner.vm.load_data(calldata_start, args).unwrap(); + let calldata_start = runner_builder.add_memory_segment(); + let calldata_end = runner_builder + .load_memory_array(calldata_start, args) + .unwrap(); // Create entrypoint_args - let mut entrypoint_args: Vec = implicit_args .iter() .map(|m| CairoArg::from(m.clone())) @@ -182,17 +185,26 @@ fn run_cairo_1_entrypoint( ]); let entrypoint_args: Vec<&CairoArg> = entrypoint_args.iter().collect(); - // Run contract entrypoint + // Initialize entrypoint + let end = runner_builder + .initialize_function_entrypoint_with_args(entrypoint_offset, &entrypoint_args) + .unwrap(); + runner_builder.initialize_validation_rules().unwrap(); + + let mut runner = runner_builder.build().unwrap(); + // Run contract entrypoint runner - .run_from_entrypoint( - entrypoint_offset, - &entrypoint_args, - true, - Some(runner.program.shared_program_data.data.len() + program_extra_data.len()), - &mut hint_processor, - ) + .run_until_pc(end, &mut hint_processor) + .map_err(|err| VmException::from_vm_error(&runner, err)) .unwrap(); + runner.end_run(true, false, &mut hint_processor).unwrap(); + verify_secure_runner( + &runner, + false, + Some(program.shared_program_data.data.len() + program_extra_data.len()), + ) + .unwrap(); // Check return values let return_values = runner.vm.get_return_values(5).unwrap(); @@ -218,31 +230,27 @@ fn run_cairo_1_entrypoint_with_run_resources( hint_processor: &mut Cairo1HintProcessor, args: &[MaybeRelocatable], ) -> Result, CairoRunError> { - let mut runner = CairoRunner::new( - &(contract_class.clone().try_into().unwrap()), + let program_builtins = get_casm_contract_builtins(&contract_class, entrypoint_offset); + + let program = contract_class.clone().try_into()?; + let mut runner_builder = CairoRunnerBuilder::new( + &program, LayoutName::all_cairo, None, - false, - false, - false, - ) - .unwrap(); - - let program_builtins = get_casm_contract_builtins(&contract_class, entrypoint_offset); - runner - .initialize_function_runner_cairo_1(&program_builtins) - .unwrap(); + RunnerMode::ExecutionMode, + )?; + runner_builder.initialize_base_segments(); + runner_builder.initialize_builtin_runners(&program_builtins)?; + runner_builder.initialize_builtin_segments(); + runner_builder.load_program()?; // Implicit Args - let syscall_segment = MaybeRelocatable::from(runner.vm.add_memory_segment()); + let syscall_segment = MaybeRelocatable::from(runner_builder.add_memory_segment()); - let builtins = runner.get_program_builtins(); - - let builtin_segment: Vec = runner - .vm + let builtin_segment: Vec = runner_builder .get_builtin_runners() .iter() - .filter(|b| builtins.contains(&b.name())) + .filter(|b| program_builtins.contains(&b.name())) .flat_map(|b| b.initial_stack()) .collect(); @@ -257,28 +265,21 @@ fn run_cairo_1_entrypoint_with_run_resources( // Load builtin costs let builtin_costs: Vec = vec![0.into(), 0.into(), 0.into(), 0.into(), 0.into()]; - let builtin_costs_ptr = runner.vm.add_memory_segment(); - runner - .vm - .load_data(builtin_costs_ptr, &builtin_costs) - .unwrap(); + let builtin_costs_ptr = runner_builder.add_memory_segment(); + runner_builder.load_memory_array(builtin_costs_ptr, &builtin_costs)?; // Load extra data - let core_program_end_ptr = - (runner.program_base.unwrap() + runner.program.shared_program_data.data.len()).unwrap(); + let core_program_end_ptr = (runner_builder.get_program_base().unwrap() + + program.shared_program_data.data.len()) + .unwrap(); let program_extra_data: Vec = vec![0x208B7FFF7FFF7FFE.into(), builtin_costs_ptr.into()]; - runner - .vm - .load_data(core_program_end_ptr, &program_extra_data) - .unwrap(); + runner_builder.load_memory_array(core_program_end_ptr, &program_extra_data)?; // Load calldata - let calldata_start = runner.vm.add_memory_segment(); - let calldata_end = runner.vm.load_data(calldata_start, args).unwrap(); - + let calldata_start = runner_builder.add_memory_segment(); + let calldata_end = runner_builder.load_memory_array(calldata_start, args)?; // Create entrypoint_args - let mut entrypoint_args: Vec = implicit_args .iter() .map(|m| CairoArg::from(m.clone())) @@ -289,14 +290,22 @@ fn run_cairo_1_entrypoint_with_run_resources( ]); let entrypoint_args: Vec<&CairoArg> = entrypoint_args.iter().collect(); - // Run contract entrypoint + // Initialize entrypoint + let end = runner_builder + .initialize_function_entrypoint_with_args(entrypoint_offset, &entrypoint_args)?; + runner_builder.initialize_validation_rules()?; + + let mut runner = runner_builder.build()?; - runner.run_from_entrypoint( - entrypoint_offset, - &entrypoint_args, - true, - Some(runner.program.shared_program_data.data.len() + program_extra_data.len()), - hint_processor, + // Run contract entrypoint + runner + .run_until_pc(end, hint_processor) + .map_err(|err| VmException::from_vm_error(&runner, err))?; + runner.end_run(true, false, hint_processor)?; + verify_secure_runner( + &runner, + false, + Some(program.shared_program_data.data.len() + program_extra_data.len()), )?; // Check return values diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs index 85a8fe8070..ac82c629c1 100644 --- a/vm/src/vm/runners/builtin_runner/modulo.rs +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -811,7 +811,7 @@ mod tests { hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, types::layout_name::LayoutName, utils::test_utils::Program, - vm::runners::cairo_runner::CairoRunner, + vm::runners::cairo_runner::{CairoRunnerBuilder, RunnerMode}, Felt252, }; @@ -821,11 +821,24 @@ mod tests { let mut hint_processor = BuiltinHintProcessor::new_empty(); let program = Program::from_bytes(program_data, Some("main")).unwrap(); - let mut runner = - CairoRunner::new(&program, LayoutName::all_cairo, None, true, false, false).unwrap(); - let end = runner.initialize(false).unwrap(); - // Modify add_mod & mul_mod params + let mut runner_builder = CairoRunnerBuilder::new( + &program, + LayoutName::all_cairo, + None, + RunnerMode::ProofModeCanonical, + ) + .unwrap(); + runner_builder + .initialize_builtin_runners_for_layout() + .unwrap(); + runner_builder.initialize_base_segments(); + runner_builder.load_program().unwrap(); + runner_builder.initialize_builtin_segments(); + runner_builder.initialize_builtin_zero_segments(); + let end = runner_builder.initialize_main_entrypoint().unwrap(); + runner_builder.initialize_validation_rules().unwrap(); + let mut runner = runner_builder.build().unwrap(); runner.run_until_pc(end, &mut hint_processor).unwrap(); runner.run_for_steps(1, &mut hint_processor).unwrap(); diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index f6eb61670e..7c28ddf81a 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -152,10 +152,25 @@ 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: +/// and execute it many times. +/// +/// The following elements must be initialized in order: +/// - Base segments (program & execution segment) +/// - Builtin runners +/// - Builtin segments +/// - Entrypoint +/// +/// The following elements must be initialized in no particular order: +/// - Validation rules +/// - Program +/// +/// The following elements don't need to be initialized, but can be cached to +/// improve performance: /// - Compiled hints /// - Decoded instructions /// - Loaded program segment +/// +/// TODO: Add support for Cairo PIE? pub struct CairoRunnerBuilder { program: Program, layout: CairoLayout, @@ -170,6 +185,12 @@ pub struct CairoRunnerBuilder { program_base: Option, execution_base: Option, memory: MemorySegmentManager, + // Set after initializing entrypoint. + initial_pc: Option, + initial_fp: Option, + initial_ap: Option, + final_pc: Option, + execution_public_memory: Option>, // Set after loading program. loaded_program: bool, // Set after compiling hints. @@ -215,7 +236,7 @@ impl CairoRunnerBuilder { enable_trace: false, disable_trace_padding: false, allow_missing_builtins: false, - runner_mode, + runner_mode: runner_mode.clone(), builtin_runners: Vec::new(), program_base: None, execution_base: None, @@ -223,6 +244,15 @@ impl CairoRunnerBuilder { loaded_program: false, hints: None, instructions: Vec::new(), + initial_pc: None, + initial_fp: None, + initial_ap: None, + final_pc: None, + execution_public_memory: if runner_mode != RunnerMode::ExecutionMode { + Some(Vec::new()) + } else { + None + }, }) } @@ -241,14 +271,42 @@ impl CairoRunnerBuilder { || self.runner_mode == RunnerMode::ProofModeCairo1 } + pub fn get_program(&self) -> &Program { + &self.program + } + pub fn get_program_base(&self) -> Option { self.program_base } + pub fn get_initial_ap(&self) -> Option { + self.initial_ap + } + + pub fn get_builtin_runners(&self) -> &[BuiltinRunner] { + &self.builtin_runners + } + pub fn add_memory_segment(&mut self) -> Relocatable { self.memory.add() } + pub fn load_memory_array( + &mut self, + ptr: Relocatable, + data: &[MaybeRelocatable], + ) -> Result { + self.memory.load_data(ptr, data) + } + + pub fn load_memory_value( + &mut self, + ptr: Relocatable, + data: impl Into, + ) -> Result<(), MemoryError> { + self.memory.memory.insert_value(ptr, data) + } + /// *Initializes* all the builtin supported by the current layout, but only /// *includes* the builtins required by the program. /// @@ -430,19 +488,212 @@ impl CairoRunnerBuilder { /// 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) + /// - Base segments initialization + /// - Builtin runners initialization pub fn initialize_builtin_segments(&mut self) { for builtin_runner in self.builtin_runners.iter_mut() { builtin_runner.initialize_segments(&mut self.memory); } } + /// Initializing the builtin segments. + /// + /// Depends on: + /// - Builtin segment initialization + pub fn initialize_builtin_zero_segments(&mut self) { + for builtin_runner in self.builtin_runners.iter_mut() { + if let BuiltinRunner::Mod(runner) = builtin_runner { + runner.initialize_zero_segment(&mut self.memory); + } + } + } + + pub fn initialize_validation_rules(&mut self) -> Result<(), RunnerError> { + for builtin in self.builtin_runners.iter() { + builtin.add_validation_rule(&mut self.memory.memory); + } + + self.memory + .memory + .validate_existing_memory() + .map_err(RunnerError::MemoryValidationError) + } + + /// Initializes the stack for executing the main entrypoint. + /// + /// How the stack and registers are initialized depend on the execution mode. + /// + /// Depends on: + /// - Segment initialization + pub fn initialize_main_entrypoint(&mut self) -> Result { + let mut stack = Vec::new(); + + // Initialize stack for builtin runners + { + let builtin_runner_map = self + .builtin_runners + .iter() + .map(|b| (b.name(), b)) + .collect::>(); + for builtin_name in &self.program.builtins { + if let Some(builtin_runner) = builtin_runner_map.get(builtin_name) { + stack.append(&mut builtin_runner.initial_stack()); + } else { + stack.push(Felt252::ZERO.into()) + } + } + } + + match self.runner_mode { + RunnerMode::ExecutionMode => { + // On ExecutionMode, we execute until control flow is returned. + // The stack is arranged as if we are at the start of a function call. + // Builtin bases are set as input arguments to the the function. + // + // <----- BUILTIN BASES ---- + // ┌────┬────┬────┬────┬────────┬────────┬ ─ ─ ─ ─ ┐ + // ... │ │ │ │ │ RET FP │ RET PC │ + // └────┴────┴────┴────┴────────┴────────┴ ─ ─ ─ ─ ┘ + // INIT FP + // + // The initial fp points to the cell after the return pc. + + let entrypoint = self + .program + .shared_program_data + .main + .ok_or(RunnerError::MissingMain)?; + let end_pc = self.initialize_function_entrypoint_with_stack(entrypoint, stack)?; + Ok(end_pc) + } + RunnerMode::ProofModeCairo1 => { + // On ProofModeCairo1, initialization is similar to + // ExecutionMode, but we execute until a fixed address, instead + // of until control is returned. + + let entrypoint = self + .program + .shared_program_data + .start + .ok_or(RunnerError::NoProgramStart)?; + + self.initialize_function_entrypoint_with_stack(entrypoint, stack)?; + + // We don't return final_pc, as we are not executing until + // control flow is returned, but rather until a fixed address. + let program_base = self.program_base.ok_or(RunnerError::NoProgBase)?; + let program_end_offset = self + .program + .shared_program_data + .end + .ok_or(RunnerError::NoProgramEnd)?; + Ok((program_base + program_end_offset)?) + } + RunnerMode::ProofModeCanonical => { + // On standalone, we execute until a fixed address. + // Builtin bases are set as local variables to the current frame. + // + // ---- BUILTIN BASES -------------------> + // ┌─────────┬─────────┬─────────┬────┬────┬────┬────┬────┐ + // │ INIT FP │ ZERO │ │ │ │ │ │ │ ... + // └─────────┴─────────┴─────────┴────┴────┴────┴────┴────┘ + // INIT FP + // + // The initial fp variable points to the third cell. We write + // INIT FP in the first cell, so that the verifier can enforce + // [fp - 2] = fp. + + let execution_base = self.execution_base.ok_or(RunnerError::NoExecBase)?; + let program_base = self.program_base.ok_or(RunnerError::NoProgBase)?; + let program_end_offset = self + .program + .shared_program_data + .end + .ok_or(RunnerError::NoProgramEnd)?; + + let target_offset: u32 = 2; + + let stack_prefix = [ + (execution_base + target_offset)?.into(), + Felt252::ZERO.into(), + ]; + stack.splice(0..0, stack_prefix); + + self.execution_public_memory = Some(Vec::from_iter(0..stack.len())); + + let entrypoint = self + .program + .shared_program_data + .start + .ok_or(RunnerError::NoProgramStart)?; + self.initialize_pc(entrypoint)?; + self.initialize_stack(&stack)?; + self.initial_fp = Some((execution_base + target_offset)?); + self.initial_ap = self.initial_fp; + Ok((program_base + program_end_offset)?) + } + } + } + + /// Depends on: + /// - Segment initialization + pub fn initialize_function_entrypoint_with_args( + &mut self, + entrypoint: usize, + args: &[&CairoArg], + ) -> Result { + let stack = args + .iter() + .map(|arg| self.memory.gen_cairo_arg(arg)) + .collect::, VirtualMachineError>>()?; + let end_pc = self.initialize_function_entrypoint_with_stack(entrypoint, stack)?; + Ok(end_pc) + } + + fn initialize_function_entrypoint_with_stack( + &mut self, + entrypoint: usize, + mut stack: Vec, + ) -> Result { + let return_fp = self.add_memory_segment(); + let final_pc = self.add_memory_segment(); + + stack.append(&mut vec![ + MaybeRelocatable::RelocatableValue(return_fp), + MaybeRelocatable::RelocatableValue(final_pc), + ]); + + self.initialize_pc(entrypoint)?; + self.initialize_stack(&stack)?; + + let execution_base = self.execution_base.ok_or(RunnerError::NoExecBase)?; + self.initial_fp = Some((execution_base + stack.len())?); + self.initial_ap = self.initial_fp; + self.final_pc = Some(final_pc); + Ok(final_pc) + } + + fn initialize_pc(&mut self, entrypoint: usize) -> Result<(), RunnerError> { + let program_base = self.program_base.ok_or(RunnerError::NoProgBase)?; + self.initial_pc = Some((program_base + entrypoint)?); + Ok(()) + } + + fn initialize_stack(&mut self, stack: &[MaybeRelocatable]) -> Result<(), RunnerError> { + let execution_base = self.execution_base.ok_or(RunnerError::NoExecBase)?; + self.memory + .load_data(execution_base, stack) + .map_err(RunnerError::MemoryInitializationError)?; + Ok(()) + } + /// Loads the program into the program segment. /// - /// If this function is not called, the program will be loaded - /// automatically when initializing the entrypoint. + /// If the entrypoint is initialized from this builder, then this + /// function must be called in other to load the program. + /// + /// If the entrypoint is initialized from the runner instead, and this + /// functions is not called, the program will be loaded by the runner. 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; @@ -526,29 +777,41 @@ impl CairoRunnerBuilder { } pub fn build(self) -> Result { - let vm = VirtualMachineBuilder::default() + let mut vm = VirtualMachineBuilder::default() .builtin_runners(self.builtin_runners) .segments(self.memory) .instruction_cache(self.instructions) + .disable_trace_padding(self.disable_trace_padding) + .trace(if self.enable_trace { + Some(Vec::::new()) + } else { + None + }) .build(); + if let Some(initial_pc) = self.initial_pc { + vm.run_context.pc = initial_pc; + } + if let Some(initial_fp) = self.initial_fp { + vm.run_context.fp = initial_fp.offset; + } + if let Some(initial_ap) = self.initial_ap { + vm.run_context.ap = initial_ap.offset; + } + 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, + initial_ap: self.initial_ap, + initial_fp: self.initial_fp, + initial_pc: self.initial_pc, + final_pc: self.final_pc, run_ended: false, segments_finalized: false, - execution_public_memory: if self.runner_mode != RunnerMode::ExecutionMode { - Some(Vec::new()) - } else { - None - }, + execution_public_memory: self.execution_public_memory, runner_mode: self.runner_mode, relocated_memory: Vec::new(), exec_scopes: ExecutionScopes::new(), @@ -597,6 +860,11 @@ impl Clone for CairoRunnerBuilder { loaded_program: self.loaded_program, hints: self.hints.clone(), instructions: self.instructions.clone(), + initial_pc: self.initial_pc, + initial_fp: self.initial_fp, + initial_ap: self.initial_ap, + final_pc: self.final_pc, + execution_public_memory: self.execution_public_memory.clone(), } } } diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index cee428153a..1a165978e3 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1363,6 +1363,7 @@ pub struct VirtualMachineBuilder { instruction_cache: Vec>, skip_instruction_execution: bool, run_finished: bool, + disable_trace_padding: bool, #[cfg(feature = "test_utils")] pub(crate) hooks: crate::vm::hooks::Hooks, } @@ -1386,6 +1387,7 @@ impl Default for VirtualMachineBuilder { #[cfg(feature = "test_utils")] hooks: Default::default(), instruction_cache: Vec::new(), + disable_trace_padding: false, } } } @@ -1411,6 +1413,11 @@ impl VirtualMachineBuilder { self } + pub fn disable_trace_padding(mut self, value: bool) -> VirtualMachineBuilder { + self.disable_trace_padding = value; + self + } + pub fn current_step(mut self, current_step: usize) -> VirtualMachineBuilder { self.current_step = current_step; self @@ -1458,7 +1465,7 @@ impl VirtualMachineBuilder { #[cfg(feature = "test_utils")] hooks: self.hooks, relocation_table: None, - disable_trace_padding: false, + disable_trace_padding: self.disable_trace_padding, } } }