diff --git a/CHANGELOG.md b/CHANGELOG.md index dca54a60b1..135feac92b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ #### Upcoming Changes +* feat/BREAKING: Add Cairo 1 proof mode compilation and execution [#1517] (https://github.com/lambdaclass/cairo-vm/pull/1517) + * In the cairo1-run crate, now the Cairo 1 Programs are compiled and executed in proof-mode + * BREAKING: Remove `CairoRunner.proof_mode: bool` field and replace it with `CairoRunner.runner_mode: RunnerMode` + * perf: Add `extensive_hints` feature to prevent performance regression for the common use-case [#1503] (https://github.com/lambdaclass/cairo-vm/pull/1503) * Gates changes added by #1491 under the feature flag `extensive_hints` diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index 3e3103af24..2100e6680e 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -40,11 +40,13 @@ use cairo_vm::serde::deserialize_program::BuiltinName; use cairo_vm::serde::deserialize_program::{ApTracking, FlowTrackingData, HintParams}; use cairo_vm::types::errors::program_errors::ProgramError; use cairo_vm::types::relocatable::Relocatable; +use cairo_vm::vm::decoding::decoder::decode_instruction; use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; use cairo_vm::vm::errors::memory_errors::MemoryError; use cairo_vm::vm::errors::runner_errors::RunnerError; use cairo_vm::vm::errors::trace_errors::TraceError; use cairo_vm::vm::errors::vm_errors::VirtualMachineError; +use cairo_vm::vm::runners::cairo_runner::RunnerMode; use cairo_vm::{ felt::Felt252, serde::deserialize_program::ReferenceManager, @@ -178,6 +180,7 @@ fn run(args: impl Iterator) -> Result, Erro .clone(); let metadata_config = Some(Default::default()); + let gas_usage_check = metadata_config.is_some(); let metadata = create_metadata(&sierra_program, metadata_config)?; let sierra_program_registry = ProgramRegistry::::new(&sierra_program)?; @@ -190,8 +193,8 @@ fn run(args: impl Iterator) -> Result, Erro let initial_gas = 9999999999999_usize; - // Entry code and footer are part of the whole instructions that are - // ran by the VM. + // Modified entry code to be compatible with custom cairo1 Proof Mode. + // This adds code that's needed for dictionaries, adjusts ap for builtin pointers, adds initial gas for the gas builtin if needed, and sets up other necessary code for cairo1 let (entry_code, builtins) = create_entry_code( &sierra_program_registry, &casm_program, @@ -199,19 +202,37 @@ fn run(args: impl Iterator) -> Result, Erro main_func, initial_gas, )?; - let footer = create_code_footer(); - let check_gas_usage = true; - let metadata = calc_metadata(&sierra_program, Default::default(), false)?; - let casm_program = compile(&sierra_program, &metadata, check_gas_usage)?; + println!("Compiling with proof mode and running ..."); + + // This information can be useful for the users using the prover. + println!("Builtins used: {:?}", builtins); + + // Prepare "canonical" proof mode instructions. These are usually added by the compiler in cairo 0 + let mut ctx = casm! {}; + casm_extend! {ctx, + call rel 4; + jmp rel 0; + }; + let proof_mode_header = ctx.instructions; + + // Get the user program instructions + let program_instructions = casm_program.instructions.iter(); + + // This footer is used by lib funcs + let libfunc_footer = create_code_footer(); + // This is the program we are actually proving + // With embedded proof mode, cairo1 header and the libfunc footer let instructions = chain!( + proof_mode_header.iter(), entry_code.iter(), - casm_program.instructions.iter(), - footer.iter() + program_instructions, + libfunc_footer.iter() ); let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); + let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); let data: Vec = instructions @@ -222,10 +243,15 @@ fn run(args: impl Iterator) -> Result, Erro let data_len = data.len(); - let program = Program::new( + let starting_pc = 0; + + let program = Program::new_for_proof( builtins, data, - Some(0), + starting_pc, + // Proof mode is on top + // jmp rel 0 is on PC == 2 + 2, program_hints, ReferenceManager { references: Vec::new(), @@ -235,14 +261,18 @@ fn run(args: impl Iterator) -> Result, Erro None, )?; - let mut runner = CairoRunner::new(&program, &args.layout, false)?; + let mut runner = CairoRunner::new_v2(&program, &args.layout, RunnerMode::ProofModeCairo1)?; + let mut vm = VirtualMachine::new(args.trace_file.is_some()); let end = runner.initialize(&mut vm)?; additional_initialization(&mut vm, data_len)?; + // Run it until the infinite loop runner.run_until_pc(end, &mut vm, &mut hint_processor)?; - runner.end_run(true, false, &mut vm, &mut hint_processor)?; + + // Then pad it to the power of 2 + runner.run_until_next_power_of_2(&mut vm, &mut hint_processor)?; // Fetch return type data let return_type_id = main_func @@ -466,6 +496,8 @@ fn create_entry_code( let ty_size = type_sizes[ty]; let generic_ty = &info.long_id.generic_id; if let Some(offset) = builtin_offset.get(generic_ty) { + // Everything is off by 2 due to the proof mode header + let offset = offset + 2; casm_extend! {ctx, [ap + 0] = [fp - offset], ap++; } @@ -483,6 +515,8 @@ fn create_entry_code( casm_extend! {ctx, [ap + 0] = [ap + offset] + 3, ap++; } + // This code should be re enabled to make the programs work with arguments + // } else if let Some(Arg::Array(_)) = arg_iter.peek() { // let values = extract_matches!(arg_iter.next().unwrap(), Arg::Array); // let offset = -ap_offset + vecs.pop().unwrap(); @@ -511,15 +545,18 @@ fn create_entry_code( // actual: args.len(), // }); // } + let before_final_call = ctx.current_code_offset; let final_call_size = 3; let offset = final_call_size + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; + casm_extend! {ctx, call rel offset; ret; } assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); + Ok((ctx.instructions, builtins)) } @@ -684,7 +721,7 @@ mod tests { #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/sample.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] fn test_run_sample_ok(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("500000500000"))]); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(felt_str!("5050"))]); } #[rstest] @@ -721,4 +758,11 @@ mod tests { let args = args.iter().cloned().map(String::from); assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(100)]); } + + #[rstest] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/dictionaries.cairo", "--trace_file", "/dev/null", "--memory_file", "/dev/null", "--layout", "all_cairo"].as_slice())] + fn test_run_dictionaries(#[case] args: &[&str]) { + let args = args.iter().cloned().map(String::from); + assert_matches!(run(args), Ok(res) if res == vec![MaybeRelocatable::from(1024)]); + } } diff --git a/cairo_programs/cairo-1-programs/dictionaries.cairo b/cairo_programs/cairo-1-programs/dictionaries.cairo new file mode 100644 index 0000000000..56176777b5 --- /dev/null +++ b/cairo_programs/cairo-1-programs/dictionaries.cairo @@ -0,0 +1,15 @@ +use dict::Felt252DictTrait; + +fn main() -> felt252 { + let mut dict_u8 = felt252_dict_new::(); + let mut dict_felt = felt252_dict_new::(); + let mut dict_felt2 = felt252_dict_new::(); + + dict_u8.insert(10, 110); + dict_u8.insert(10, 110); + + let val10 = dict_u8[10]; // 110 + let val11 = dict_felt[11]; // 0 + dict_felt.insert(11, 1024); + dict_felt[11] // 1024 +} diff --git a/cairo_programs/cairo-1-programs/sample.cairo b/cairo_programs/cairo-1-programs/sample.cairo index 0cea9e1051..9c3c9da753 100644 --- a/cairo_programs/cairo-1-programs/sample.cairo +++ b/cairo_programs/cairo-1-programs/sample.cairo @@ -8,5 +8,5 @@ fn inner(i: felt252) -> felt252 { } fn main() -> felt252 { - inner(1000000) + inner(100) } diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index c934809952..cb52546cd8 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -142,18 +142,25 @@ pub struct CairoRunner { run_ended: bool, segments_finalized: bool, execution_public_memory: Option>, - proof_mode: bool, + runner_mode: RunnerMode, pub original_steps: Option, pub relocated_memory: Vec>, pub exec_scopes: ExecutionScopes, pub relocated_trace: Option>, } +#[derive(Clone, Debug, PartialEq)] +pub enum RunnerMode { + ExecutionMode, + ProofModeCanonical, + ProofModeCairo1, +} + impl CairoRunner { - pub fn new( + pub fn new_v2( program: &Program, layout: &str, - proof_mode: bool, + mode: RunnerMode, ) -> Result { let cairo_layout = match layout { "plain" => CairoLayout::plain_instance(), @@ -183,15 +190,31 @@ impl CairoRunner { initial_pc: None, run_ended: false, segments_finalized: false, - proof_mode, + runner_mode: mode.clone(), original_steps: None, relocated_memory: Vec::new(), exec_scopes: ExecutionScopes::new(), - execution_public_memory: if proof_mode { Some(Vec::new()) } else { None }, + execution_public_memory: if mode != RunnerMode::ExecutionMode { + Some(Vec::new()) + } else { + None + }, relocated_trace: None, }) } + pub fn new( + program: &Program, + layout: &str, + proof_mode: bool, + ) -> Result { + if proof_mode { + Self::new_v2(program, layout, RunnerMode::ProofModeCanonical) + } else { + Self::new_v2(program, layout, RunnerMode::ExecutionMode) + } + } + pub fn initialize(&mut self, vm: &mut VirtualMachine) -> Result { self.initialize_builtins(vm)?; self.initialize_segments(vm, None); @@ -219,21 +242,21 @@ impl CairoRunner { if self.layout.builtins.output { let included = program_builtins.remove(&BuiltinName::output); - if included || self.proof_mode { + if included || self.is_proof_mode() { 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.proof_mode { + if included || self.is_proof_mode() { 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.proof_mode { + if included || self.is_proof_mode() { builtin_runners.push( RangeCheckBuiltinRunner::new( instance_def.ratio, @@ -247,35 +270,35 @@ impl CairoRunner { if let Some(instance_def) = self.layout.builtins.ecdsa.as_ref() { let included = program_builtins.remove(&BuiltinName::ecdsa); - if included || self.proof_mode { + if included || self.is_proof_mode() { builtin_runners.push(SignatureBuiltinRunner::new(instance_def, included).into()); } } if let Some(instance_def) = self.layout.builtins.bitwise.as_ref() { let included = program_builtins.remove(&BuiltinName::bitwise); - if included || self.proof_mode { + if included || self.is_proof_mode() { builtin_runners.push(BitwiseBuiltinRunner::new(instance_def, 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.proof_mode { + if included || self.is_proof_mode() { builtin_runners.push(EcOpBuiltinRunner::new(instance_def, included).into()); } } if let Some(instance_def) = self.layout.builtins.keccak.as_ref() { let included = program_builtins.remove(&BuiltinName::keccak); - if included || self.proof_mode { + if included || self.is_proof_mode() { builtin_runners.push(KeccakBuiltinRunner::new(instance_def, included).into()); } } if let Some(instance_def) = self.layout.builtins.poseidon.as_ref() { let included = program_builtins.remove(&BuiltinName::poseidon); - if included || self.proof_mode { + if included || self.is_proof_mode() { builtin_runners .push(PoseidonBuiltinRunner::new(instance_def.ratio, included).into()); } @@ -291,6 +314,11 @@ impl CairoRunner { Ok(()) } + fn is_proof_mode(&self) -> bool { + self.runner_mode == RunnerMode::ProofModeCanonical + || self.runner_mode == RunnerMode::ProofModeCairo1 + } + // Initialize all the builtins. Values used are the original one from the CairoFunctionRunner // Values extracted from here: https://github.com/starkware-libs/cairo-lang/blob/4fb83010ab77aa7ead0c9df4b0c05e030bc70b87/src/starkware/cairo/common/cairo_function_runner.py#L28 fn initialize_all_builtins( @@ -437,44 +465,77 @@ impl CairoRunner { } ///Initializes state for running a program from the main() entrypoint. - ///If self.proof_mode == True, the execution starts from the start label rather then the main() function. + ///If self.is_proof_mode() == True, the execution starts from the start label rather then the main() function. ///Returns the value of the program counter after returning from main. fn initialize_main_entrypoint( &mut self, vm: &mut VirtualMachine, ) -> Result { let mut stack = Vec::new(); + for builtin_runner in vm.builtin_runners.iter() { stack.append(&mut builtin_runner.initial_stack()); } - //Different process if proof_mode is enabled - if self.proof_mode { - // Add the dummy last fp and pc to the public memory, so that the verifier can enforce [fp - 2] = fp. - let mut stack_prefix = vec![ - Into::::into( - self.execution_base - .as_ref() - .ok_or(RunnerError::NoExecBase)? - + 2, - ), - MaybeRelocatable::from(Felt252::zero()), - ]; - stack_prefix.extend(stack); - self.execution_public_memory = Some(Vec::from_iter(0..stack_prefix.len())); - self.initialize_state( - vm, - self.program - .shared_program_data - .start - .ok_or(RunnerError::NoProgramStart)?, - stack_prefix, - )?; + + if self.is_proof_mode() { + // In canonical proof mode, add the dummy last fp and pc to the public memory, so that the verifier can enforce + + // canonical offset should be 2 for Cairo 0 + let mut target_offset = 2; + + // Cairo1 is not adding data to check [fp - 2] = fp, and has a different initialization of the stack. This should be updated. + // Cairo0 remains canonical + + if matches!(self.runner_mode, RunnerMode::ProofModeCairo1) { + target_offset = stack.len() + 2; + + // This values shouldn't be needed with a canonical proof mode + let return_fp = vm.segments.add(); + let end = vm.segments.add(); + stack.append(&mut vec![ + MaybeRelocatable::RelocatableValue(return_fp), + MaybeRelocatable::RelocatableValue(end), + ]); + + self.initialize_state( + vm, + self.program + .shared_program_data + .start + .ok_or(RunnerError::NoProgramStart)?, + stack, + )?; + } else { + let mut stack_prefix = vec![ + Into::::into( + self.execution_base + .as_ref() + .ok_or(RunnerError::NoExecBase)? + + target_offset, + ), + MaybeRelocatable::from(Felt252::zero()), + ]; + stack_prefix.extend(stack.clone()); + + self.execution_public_memory = Some(Vec::from_iter(0..stack_prefix.len())); + + self.initialize_state( + vm, + self.program + .shared_program_data + .start + .ok_or(RunnerError::NoProgramStart)?, + stack_prefix.clone(), + )?; + } + self.initial_fp = Some( self.execution_base .as_ref() .ok_or(RunnerError::NoExecBase)? - + 2, + + target_offset, ); + self.initial_ap = self.initial_fp; return Ok(self.program_base.as_ref().ok_or(RunnerError::NoProgBase)? + self @@ -483,6 +544,7 @@ impl CairoRunner { .end .ok_or(RunnerError::NoProgramEnd)?); } + let return_fp = vm.segments.add(); if let Some(main) = &self.entrypoint { let main_clone = *main; @@ -585,6 +647,7 @@ impl CairoRunner { &mut hint_ranges, &self.program.constants, )?; + hint_processor.consume_step(); } @@ -769,7 +832,7 @@ impl CairoRunner { } vm.segments.compute_effective_sizes(); - if self.proof_mode && !disable_trace_padding { + if self.is_proof_mode() && !disable_trace_padding { self.run_until_next_power_of_2(vm, hint_processor)?; loop { match self.check_used_cells(vm) { @@ -1160,7 +1223,7 @@ impl CairoRunner { if self.segments_finalized { return Err(RunnerError::FailedAddingReturnValues); } - if self.proof_mode { + if self.is_proof_mode() { let exec_base = *self .execution_base .as_ref() @@ -3850,7 +3913,7 @@ mod tests { fn finalize_segments_run_ended_empty_no_exec_base() { let program = program!(); let mut cairo_runner = cairo_runner!(program); - cairo_runner.proof_mode = true; + cairo_runner.runner_mode = RunnerMode::ProofModeCanonical; cairo_runner.program_base = Some(Relocatable::from((0, 0))); cairo_runner.run_ended = true; let mut vm = vm!(); @@ -4399,7 +4462,7 @@ mod tests { fn initialize_main_entrypoint_proof_mode_empty_program() { let program = program!(start = Some(0), end = Some(0), main = Some(8),); let mut runner = cairo_runner!(program); - runner.proof_mode = true; + runner.runner_mode = RunnerMode::ProofModeCanonical; let mut vm = vm!(); runner.initialize_segments(&mut vm, None); assert_eq!(runner.execution_base, Some(Relocatable::from((1, 0)))); @@ -4423,7 +4486,7 @@ mod tests { builtins = vec![BuiltinName::output, BuiltinName::ec_op], ); let mut runner = cairo_runner!(program); - runner.proof_mode = true; + runner.runner_mode = RunnerMode::ProofModeCanonical; let mut vm = vm!(); runner.initialize_builtins(&mut vm).unwrap(); runner.initialize_segments(&mut vm, None);