diff --git a/crates/bin/cairo-execute/src/main.rs b/crates/bin/cairo-execute/src/main.rs index f0db53f4782..38e4922d9dc 100644 --- a/crates/bin/cairo-execute/src/main.rs +++ b/crates/bin/cairo-execute/src/main.rs @@ -7,6 +7,7 @@ use cairo_lang_compiler::diagnostics::DiagnosticsReporter; use cairo_lang_compiler::project::check_compiler_path; use cairo_lang_executable::compile::compile_executable; use cairo_lang_executable::executable::{EntryPointKind, Executable}; +use cairo_lang_runner::casm_run::format_for_panic; use cairo_lang_runner::{Arg, CairoHintProcessor, build_hints_dict}; use cairo_lang_utils::bigint::BigUintAsHex; use cairo_vm::cairo_run::{CairoRunConfig, cairo_run_program}; @@ -225,6 +226,7 @@ fn main() -> anyhow::Result<()> { run_resources: Default::default(), syscalls_used_resources: Default::default(), no_temporary_segments: false, + markers: Default::default(), }; let cairo_run_config = CairoRunConfig { @@ -243,6 +245,16 @@ fn main() -> anyhow::Result<()> { let mut output_buffer = "Program Output:\n".to_string(); runner.vm.write_output(&mut output_buffer)?; print!("{output_buffer}"); + if let [.., start_marker, end_marker] = &hint_processor.markers[..] { + let size = (*end_marker - *start_marker).with_context(|| { + format!("Panic data markers mismatch: start={start_marker}, end={end_marker}") + })?; + let panic_data = runner + .vm + .get_integer_range(*start_marker, size) + .with_context(|| "Failed reading panic data.")?; + println!("{}", format_for_panic(panic_data.into_iter().map(|value| *value))); + } } if let Some(trace_path) = &args.run.standalone_outputs.trace_file { diff --git a/crates/cairo-lang-casm/src/hints/mod.rs b/crates/cairo-lang-casm/src/hints/mod.rs index 1304242d163..9812dc62ab3 100644 --- a/crates/cairo-lang-casm/src/hints/mod.rs +++ b/crates/cairo-lang-casm/src/hints/mod.rs @@ -354,6 +354,9 @@ pub enum ExternalHint { /// Writes a run argument of number `index` to `dst` and on. #[cfg_attr(feature = "parity-scale-codec", codec(index = 1))] WriteRunParam { index: ResOperand, dst: CellRef }, + /// Stores a marker in the HintProcessor. Useful for debugging. + #[cfg_attr(feature = "parity-scale-codec", codec(index = 2))] + SetMarker { marker: ResOperand }, } struct DerefOrImmediateFormatter<'a>(&'a DerefOrImmediate); @@ -850,6 +853,10 @@ impl PythonicHint for ExternalHint { let index = ResOperandAsIntegerFormatter(index); format!(r#"raise NotImplementedError("memory{dst}.. = params[{index}])")"#) } + Self::SetMarker { marker } => { + let marker = ResOperandAsAddressFormatter(marker); + format!(r#"raise NotImplementedError("marker = {}")"#, marker) + } } } } diff --git a/crates/cairo-lang-executable/src/compile_test_data/basic b/crates/cairo-lang-executable/src/compile_test_data/basic index b58fc6f7858..7176b773d79 100644 --- a/crates/cairo-lang-executable/src/compile_test_data/basic +++ b/crates/cairo-lang-executable/src/compile_test_data/basic @@ -18,6 +18,8 @@ call rel 12; jmp rel 5 if [ap + -3] != 0, ap++; [ap + -1] = [ap + -2]; jmp rel 4; +%{ raise NotImplementedError("marker = memory[ap + -3]") %} +%{ raise NotImplementedError("marker = memory[ap + -2]") %} [ap + -1] = [fp + -3] + 1; [ap + -4] = [[fp + -3] + 0]; [ap + 0] = [ap + -1], ap++; @@ -68,6 +70,8 @@ call rel 12; jmp rel 5 if [ap + -3] != 0, ap++; [ap + -1] = [ap + -2]; jmp rel 4; +%{ raise NotImplementedError("marker = memory[ap + -3]") %} +%{ raise NotImplementedError("marker = memory[ap + -2]") %} [ap + -1] = [fp + -3] + 1; [ap + -4] = [[fp + -3] + 0]; [ap + 0] = [ap + -1], ap++; @@ -155,6 +159,8 @@ call rel 13; jmp rel 5 if [ap + -3] != 0, ap++; [ap + -1] = [ap + -2]; jmp rel 4; +%{ raise NotImplementedError("marker = memory[ap + -3]") %} +%{ raise NotImplementedError("marker = memory[ap + -2]") %} [ap + -1] = [fp + -4] + 1; [ap + -4] = [[fp + -4] + 0]; [ap + 0] = [ap + -1], ap++; @@ -428,6 +434,8 @@ call rel 12; jmp rel 5 if [ap + -3] != 0, ap++; [ap + -1] = [ap + -2]; jmp rel 4; +%{ raise NotImplementedError("marker = memory[ap + -3]") %} +%{ raise NotImplementedError("marker = memory[ap + -2]") %} [ap + -1] = [fp + -3] + 1; [ap + -4] = [[fp + -3] + 0]; [ap + 0] = [ap + -1], ap++; @@ -490,6 +498,8 @@ call rel 39; jmp rel 5 if [ap + -3] != 0, ap++; [ap + -1] = [ap + -2]; jmp rel 4; +%{ raise NotImplementedError("marker = memory[ap + -3]") %} +%{ raise NotImplementedError("marker = memory[ap + -2]") %} [ap + -1] = [fp + -4] + 1; [ap + -4] = [[fp + -4] + 0]; [fp + 0] = [ap + -7]; diff --git a/crates/cairo-lang-runnable-utils/src/builder.rs b/crates/cairo-lang-runnable-utils/src/builder.rs index d984aa4f5c1..8b5feeee88f 100644 --- a/crates/cairo-lang-runnable-utils/src/builder.rs +++ b/crates/cairo-lang-runnable-utils/src/builder.rs @@ -413,34 +413,40 @@ pub fn create_entry_code_from_params( } casm_build_extend! (ctx, let () = call FUNCTION;); let mut unprocessed_return_size = return_types.iter().map(|(_, size)| size).sum::(); + let mut next_unprocessed_deref = || { + let deref_cell = CellExpression::Deref(deref!([ap - unprocessed_return_size])); + unprocessed_return_size -= 1; + deref_cell + }; let mut return_data = vec![]; for (ret_ty, size) in return_types { if let Some(name) = builtin_ty_to_vm_name.get(ret_ty) { - *builtin_vars.get_mut(name).unwrap() = - ctx.add_var(CellExpression::Deref(deref!([ap - unprocessed_return_size]))); - unprocessed_return_size -= 1; + *builtin_vars.get_mut(name).unwrap() = ctx.add_var(next_unprocessed_deref()); } else if config.outputting_function { if ret_ty == &GasBuiltinType::ID { - unprocessed_return_size -= 1; + next_unprocessed_deref(); continue; } let output_ptr_var = builtin_vars[&BuiltinName::output]; // The output builtin values. let new_output_ptr = if *size == 3 { - let panic_indicator = - ctx.add_var(CellExpression::Deref(deref!([ap - unprocessed_return_size]))); - unprocessed_return_size -= 2; - // The output ptr in the case of successful run. - let output_ptr_end = - ctx.add_var(CellExpression::Deref(deref!([ap - unprocessed_return_size]))); - unprocessed_return_size -= 1; + let panic_indicator = ctx.add_var(next_unprocessed_deref()); + // The start ptr of the output in the case of successful run, + // or the panic data in case of a failure. + let ptr_start = ctx.add_var(next_unprocessed_deref()); + // The end ptr of the output in the case of successful run, + // or the panic data in case of a failure. + let ptr_end = ctx.add_var(next_unprocessed_deref()); casm_build_extend! {ctx, tempvar new_output_ptr; jump PANIC if panic_indicator != 0; // SUCCESS: - assert new_output_ptr = output_ptr_end; + assert new_output_ptr = ptr_end; jump AFTER_PANIC_HANDLING; PANIC: + // Marking the panic message in case of a panic. + hint ExternalHint::SetMarker { marker: ptr_start }; + hint ExternalHint::SetMarker { marker: ptr_end }; const one = 1; // In the case of an error, we assume no values are written to the output_ptr. assert new_output_ptr = output_ptr_var + one; @@ -450,10 +456,8 @@ pub fn create_entry_code_from_params( new_output_ptr } else if *size == 2 { // No panic possible. - unprocessed_return_size -= 1; - let output_ptr_end = - ctx.add_var(CellExpression::Deref(deref!([ap - unprocessed_return_size]))); - unprocessed_return_size -= 1; + next_unprocessed_deref(); + let output_ptr_end = ctx.add_var(next_unprocessed_deref()); casm_build_extend! {ctx, const czero = 0; tempvar zero = czero; @@ -466,10 +470,7 @@ pub fn create_entry_code_from_params( *builtin_vars.get_mut(&BuiltinName::output).unwrap() = new_output_ptr; } else { for _ in 0..*size { - return_data.push( - ctx.add_var(CellExpression::Deref(deref!([ap - unprocessed_return_size]))), - ); - unprocessed_return_size -= 1; + return_data.push(ctx.add_var(next_unprocessed_deref())); } } } diff --git a/crates/cairo-lang-runner/src/casm_run/mod.rs b/crates/cairo-lang-runner/src/casm_run/mod.rs index 111c2dfa58e..20940179632 100644 --- a/crates/cairo-lang-runner/src/casm_run/mod.rs +++ b/crates/cairo-lang-runner/src/casm_run/mod.rs @@ -102,6 +102,8 @@ pub struct CairoHintProcessor<'a> { pub syscalls_used_resources: StarknetExecutionResources, /// Avoid allocating memory segments so finalization of segment arena may not occur. pub no_temporary_segments: bool, + /// A set of markers created by the run. + pub markers: Vec, } pub fn cell_ref_to_relocatable(cell_ref: &CellRef, vm: &VirtualMachine) -> Relocatable { @@ -1306,15 +1308,15 @@ impl CairoHintProcessor<'_> { /// Executes an external hint. fn execute_external_hint( - &self, + &mut self, vm: &mut VirtualMachine, core_hint: &ExternalHint, ) -> Result<(), HintError> { match core_hint { - ExternalHint::AddRelocationRule { src, dst } => Ok(vm.add_relocation_rule( + ExternalHint::AddRelocationRule { src, dst } => vm.add_relocation_rule( extract_relocatable(vm, src)?, extract_relocatable(vm, dst)?, - )?), + )?, ExternalHint::WriteRunParam { index, dst } => { let index = get_val(vm, index)?.to_usize().expect("Got a bad index."); let mut stack = vec![(cell_ref_to_relocatable(dst, vm), &self.user_args[index])]; @@ -1336,9 +1338,12 @@ impl CairoHintProcessor<'_> { } } } - Ok(()) + } + ExternalHint::SetMarker { marker } => { + self.markers.push(extract_relocatable(vm, marker)?); } } + Ok(()) } } @@ -2378,6 +2383,20 @@ where Some(FormattedItem { item: format_short_string(&first_felt), is_string: false }) } +/// Formats the given felts as a panic string. +pub fn format_for_panic(mut felts: T) -> String +where + T: Iterator + Clone, +{ + let mut items = Vec::new(); + while let Some(item) = format_next_item(&mut felts) { + items.push(item.quote_if_string()); + } + let panic_values_string = + if let [item] = &items[..] { item.clone() } else { format!("({})", items.join(", ")) }; + format!("Panicked with {panic_values_string}.") +} + /// Formats a `Felt252`, as a short string if possible. fn format_short_string(value: &Felt252) -> String { let hex_value = value.to_biguint(); diff --git a/crates/cairo-lang-runner/src/casm_run/test.rs b/crates/cairo-lang-runner/src/casm_run/test.rs index bf32b65e00b..6eaf881600d 100644 --- a/crates/cairo-lang-runner/src/casm_run/test.rs +++ b/crates/cairo-lang-runner/src/casm_run/test.rs @@ -130,6 +130,7 @@ fn test_runner(function: CasmContext, n_returns: usize, expected: &[i128]) { run_resources: RunResources::default(), syscalls_used_resources: Default::default(), no_temporary_segments: true, + markers: Default::default(), }; let RunFunctionResult { ap, memory, .. } = @@ -159,6 +160,7 @@ fn test_allocate_segment() { run_resources: RunResources::default(), syscalls_used_resources: Default::default(), no_temporary_segments: true, + markers: Default::default(), }; let RunFunctionResult { ap, memory, .. } = diff --git a/crates/cairo-lang-runner/src/lib.rs b/crates/cairo-lang-runner/src/lib.rs index 37b10ec9e95..38423dc5665 100644 --- a/crates/cairo-lang-runner/src/lib.rs +++ b/crates/cairo-lang-runner/src/lib.rs @@ -207,6 +207,7 @@ impl SierraCasmRunner { run_resources: RunResources::default(), syscalls_used_resources: Default::default(), no_temporary_segments: true, + markers: Default::default(), }; let RunResult { gas_counter, memory, value, used_resources, profiling_info } = self .run_function( diff --git a/crates/cairo-lang-test-runner/src/lib.rs b/crates/cairo-lang-test-runner/src/lib.rs index cfffdc61f6c..3952ec99bd7 100644 --- a/crates/cairo-lang-test-runner/src/lib.rs +++ b/crates/cairo-lang-test-runner/src/lib.rs @@ -1,6 +1,5 @@ use std::path::Path; use std::sync::Mutex; -use std::vec::IntoIter; use anyhow::{Context, Result, bail}; use cairo_lang_compiler::db::RootDatabase; @@ -8,7 +7,7 @@ use cairo_lang_compiler::diagnostics::DiagnosticsReporter; use cairo_lang_compiler::project::setup_project; use cairo_lang_filesystem::cfg::{Cfg, CfgSet}; use cairo_lang_filesystem::ids::CrateId; -use cairo_lang_runner::casm_run::format_next_item; +use cairo_lang_runner::casm_run::format_for_panic; use cairo_lang_runner::profiling::{ ProfilingInfo, ProfilingInfoProcessor, ProfilingInfoProcessorParams, }; @@ -165,17 +164,6 @@ impl CompiledTestRunner { } } -/// Formats the given felts as a panic string. -fn format_for_panic(mut felts: IntoIter) -> String { - let mut items = Vec::new(); - while let Some(item) = format_next_item(&mut felts) { - items.push(item.quote_if_string()); - } - let panic_values_string = - if let [item] = &items[..] { item.clone() } else { format!("({})", items.join(", ")) }; - format!("Panicked with {panic_values_string}.") -} - /// Whether to run the profiler, and what results to produce. /// /// With `None`, don't run the profiler. diff --git a/crates/cairo-lang-test-runner/src/test.rs b/crates/cairo-lang-test-runner/src/test.rs index df4f2db9edb..8504c817362 100644 --- a/crates/cairo-lang-test-runner/src/test.rs +++ b/crates/cairo-lang-test-runner/src/test.rs @@ -1,3 +1,4 @@ +use cairo_lang_runner::casm_run::format_for_panic; use cairo_lang_sierra::program::{Program, ProgramArtifact}; use cairo_lang_test_plugin::test_config::TestExpectation; use cairo_lang_test_plugin::{TestCompilationMetadata, TestConfig, TestsCompilationConfig}; @@ -5,7 +6,7 @@ use cairo_lang_utils::byte_array::BYTE_ARRAY_MAGIC; use itertools::Itertools; use starknet_types_core::felt::Felt as Felt252; -use crate::{TestCompilation, TestCompiler, filter_test_cases, format_for_panic}; +use crate::{TestCompilation, TestCompiler, filter_test_cases}; #[test] fn test_compiled_serialization() {