diff --git a/CHANGELOG.md b/CHANGELOG.md index dd8e4f920e..df3c48bd60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ #### Upcoming Changes +* feat: add debugging capabilities behind `print` feature flag. [#1476](https://github.com/lambdaclass/cairo-vm/pull/1476) + #### [0.9.1] - 2023-11-16 + * chore: bump `cairo-lang-` dependencies to 2.3.1 [#1482](https://github.com/lambdaclass/cairo-vm/pull/1482), [#1483](https://github.com/lambdaclass/cairo-vm/pull/1483) * feat: Make PublicInput fields public [#1474](https://github.com/lambdaclass/cairo-vm/pull/1474) diff --git a/Makefile b/Makefile index 27fb46cb83..8925012f37 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,9 @@ BAD_TEST_DIR=cairo_programs/bad_programs BAD_TEST_FILES:=$(wildcard $(BAD_TEST_DIR)/*.cairo) COMPILED_BAD_TESTS:=$(patsubst $(BAD_TEST_DIR)/%.cairo, $(BAD_TEST_DIR)/%.json, $(BAD_TEST_FILES)) +PRINT_TEST_DIR=cairo_programs/print_feature +PRINT_TEST_FILES:=$(wildcard $(PRINT_TEST_DIR)/*.cairo) +COMPILED_PRINT_TESTS:=$(patsubst $(PRINT_TEST_DIR)/%.cairo, $(PRINT_TEST_DIR)/%.json, $(PRINT_TEST_FILES)) NORETROCOMPAT_DIR:=cairo_programs/noretrocompat NORETROCOMPAT_FILES:=$(wildcard $(NORETROCOMPAT_DIR)/*.cairo) @@ -96,6 +99,9 @@ $(NORETROCOMPAT_DIR)/%.json: $(NORETROCOMPAT_DIR)/%.cairo $(BAD_TEST_DIR)/%.json: $(BAD_TEST_DIR)/%.cairo cairo-compile $< --output $@ +$(PRINT_TEST_DIR)/%.json: $(PRINT_TEST_DIR)/%.cairo + cairo-compile $< --output $@ + # ====================== # Test Cairo 1 Contracts # ====================== @@ -212,7 +218,7 @@ run: check: cargo check -cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) +cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) $(COMPILED_PRINT_TESTS) cairo_proof_programs: $(COMPILED_PROOF_TESTS) cairo_bench_programs: $(COMPILED_BENCHES) cairo_1_test_contracts: $(CAIRO_1_COMPILED_CASM_CONTRACTS) @@ -296,6 +302,7 @@ clean: rm -f $(TEST_DIR)/*.trace rm -f $(BENCH_DIR)/*.json rm -f $(BAD_TEST_DIR)/*.json + rm -f $(PRINT_TEST_DIR)/*.json rm -f $(CAIRO_1_CONTRACTS_TEST_DIR)/*.sierra rm -f $(CAIRO_1_CONTRACTS_TEST_DIR)/*.casm rm -f $(TEST_PROOF_DIR)/*.json diff --git a/cairo_programs/print_feature/print_array.cairo b/cairo_programs/print_feature/print_array.cairo new file mode 100644 index 0000000000..18183092e3 --- /dev/null +++ b/cairo_programs/print_feature/print_array.cairo @@ -0,0 +1,20 @@ +%builtins range_check + +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr: felt}() { + let name = 0x4b4b5254; + let (arr: felt*) = alloc(); + assert arr[0] = 1; + assert arr[1] = 2; + assert arr[2] = 3; + assert arr[3] = 4; + assert arr[4] = 5; + let arr_len = 5; + %{ + print(bytes.fromhex(f"{ids.name:062x}").decode().replace('\x00','')) + arr = [memory[ids.arr + i] for i in range(ids.arr_len)] + print(arr) + %} + return(); +} diff --git a/cairo_programs/print_feature/print_dict_array.cairo b/cairo_programs/print_feature/print_dict_array.cairo new file mode 100644 index 0000000000..4bdb56b76e --- /dev/null +++ b/cairo_programs/print_feature/print_dict_array.cairo @@ -0,0 +1,34 @@ +%builtins range_check + +from starkware.cairo.common.dict_access import DictAccess +from starkware.cairo.common.default_dict import default_dict_new, default_dict_finalize +from starkware.cairo.common.dict import dict_write + +struct MyStruct { + a: felt, + b: felt, + c: felt, +} + +func main{range_check_ptr: felt}() { + let name = 0x4b4b5254; + let (dict_ptr) = default_dict_new(0); + let pointer_size = 3; + + tempvar one = new MyStruct(1,2,3); + dict_write{dict_ptr=dict_ptr}(0, cast(one, felt)); + tempvar two = new MyStruct(2,3,4); + dict_write{dict_ptr=dict_ptr}(1, cast(two, felt)); + tempvar three = new MyStruct(3,4,5); + dict_write{dict_ptr=dict_ptr}(2, cast(three, felt)); + tempvar four = new MyStruct(4,5,6); + dict_write{dict_ptr=dict_ptr}(3, cast(four, felt)); + %{ + print(bytes.fromhex(f"{ids.name:062x}").decode().replace('\x00','')) + data = __dict_manager.get_dict(ids.dict_ptr) + print( + {k: v if isinstance(v, int) else [memory[v + i] for i in range(ids.pointer_size)] for k, v in data.items()} + ) + %} + return(); +} diff --git a/cairo_programs/print_feature/print_dict_felt.cairo b/cairo_programs/print_feature/print_dict_felt.cairo new file mode 100644 index 0000000000..10c3344e0d --- /dev/null +++ b/cairo_programs/print_feature/print_dict_felt.cairo @@ -0,0 +1,24 @@ +%builtins range_check + +from starkware.cairo.common.dict_access import DictAccess +from starkware.cairo.common.default_dict import default_dict_new, default_dict_finalize +from starkware.cairo.common.dict import dict_write + +func main{range_check_ptr: felt}() { + let name = 0x4b4b5254; + let (dict_ptr) = default_dict_new(0); + let pointer_size = 1; + dict_write{dict_ptr=dict_ptr}(0, 1); + dict_write{dict_ptr=dict_ptr}(1, 2); + dict_write{dict_ptr=dict_ptr}(2, 3); + dict_write{dict_ptr=dict_ptr}(3, 4); + dict_write{dict_ptr=dict_ptr}(4, 5); + %{ + print(bytes.fromhex(f"{ids.name:062x}").decode().replace('\x00','')) + data = __dict_manager.get_dict(ids.dict_ptr) + print( + {k: v if isinstance(v, int) else [memory[v + i] for i in range(ids.pointer_size)] for k, v in data.items()} + ) + %} + return(); +} diff --git a/cairo_programs/print_feature/print_felt.cairo b/cairo_programs/print_feature/print_felt.cairo new file mode 100644 index 0000000000..383c6d6db2 --- /dev/null +++ b/cairo_programs/print_feature/print_felt.cairo @@ -0,0 +1,9 @@ +%builtins range_check + +func main{range_check_ptr: felt}() { + let x = 123; + %{ + print(ids.x) + %} + return(); +} diff --git a/vm/Cargo.toml b/vm/Cargo.toml index d1d5a77ab3..ed3e45a2df 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -33,9 +33,11 @@ lambdaworks-felt = ["felt/lambdaworks-felt"] test_utils = [ "skip_next_instruction_hint", "hooks", + "print", ] # This feature will reference every test-oriented feature skip_next_instruction_hint = [] hooks = [] +print = ["std"] [dependencies] mimalloc = { workspace = true, optional = true } diff --git a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index 1c3c5f4b40..0b367e3edd 100644 --- a/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/vm/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -116,6 +116,9 @@ use felt::Felt252; #[cfg(feature = "skip_next_instruction_hint")] use crate::hint_processor::builtin_hint_processor::skip_next_instruction::skip_next_instruction; +#[cfg(feature = "print")] +use crate::hint_processor::builtin_hint_processor::print::{print_array, print_dict, print_felt}; + use super::blake2s_utils::example_blake2s_compress; pub struct HintProcessorData { @@ -815,6 +818,14 @@ impl HintProcessorLogic for BuiltinHintProcessor { hint_code::SPLIT_XX => split_xx(vm, &hint_data.ids_data, &hint_data.ap_tracking), #[cfg(feature = "skip_next_instruction_hint")] hint_code::SKIP_NEXT_INSTRUCTION => skip_next_instruction(vm), + #[cfg(feature = "print")] + hint_code::PRINT_FELT => print_felt(vm, &hint_data.ids_data, &hint_data.ap_tracking), + #[cfg(feature = "print")] + hint_code::PRINT_ARR => print_array(vm, &hint_data.ids_data, &hint_data.ap_tracking), + #[cfg(feature = "print")] + hint_code::PRINT_DICT => { + print_dict(vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking) + } code => Err(HintError::UnknownHint(code.to_string().into_boxed_str())), } } diff --git a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs index 2b55c91f30..77365943f8 100644 --- a/vm/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/vm/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -1409,3 +1409,15 @@ ids.x.low = x & ((1<<128)-1) ids.x.high = x >> 128"; #[cfg(feature = "skip_next_instruction_hint")] pub const SKIP_NEXT_INSTRUCTION: &str = "skip_next_instruction()"; + +pub const PRINT_FELT: &str = "print(ids.x)"; + +pub const PRINT_ARR: &str = r#"print(bytes.fromhex(f"{ids.name:062x}").decode().replace('\x00','')) +arr = [memory[ids.arr + i] for i in range(ids.arr_len)] +print(arr)"#; + +pub const PRINT_DICT: &str = r#"print(bytes.fromhex(f"{ids.name:062x}").decode().replace('\x00','')) +data = __dict_manager.get_dict(ids.dict_ptr) +print( + {k: v if isinstance(v, int) else [memory[v + i] for i in range(ids.pointer_size)] for k, v in data.items()} +)"#; diff --git a/vm/src/hint_processor/builtin_hint_processor/mod.rs b/vm/src/hint_processor/builtin_hint_processor/mod.rs index 0a3f067b5b..8236f8a866 100644 --- a/vm/src/hint_processor/builtin_hint_processor/mod.rs +++ b/vm/src/hint_processor/builtin_hint_processor/mod.rs @@ -18,6 +18,8 @@ pub mod memcpy_hint_utils; pub mod memset_utils; pub mod poseidon_utils; pub mod pow_utils; +#[cfg(feature = "print")] +pub mod print; pub mod secp; pub mod segments; pub mod set; diff --git a/vm/src/hint_processor/builtin_hint_processor/print.rs b/vm/src/hint_processor/builtin_hint_processor/print.rs new file mode 100644 index 0000000000..10ce39c504 --- /dev/null +++ b/vm/src/hint_processor/builtin_hint_processor/print.rs @@ -0,0 +1,124 @@ +use core::fmt::{Debug, Formatter}; + +use felt::Felt252; +use num_traits::ToPrimitive; + +use crate::hint_processor::builtin_hint_processor::dict_manager::Dictionary; +use crate::hint_processor::builtin_hint_processor::hint_utils::{ + get_integer_from_var_name, get_ptr_from_var_name, +}; +use crate::serde::deserialize_program::ApTracking; +use crate::stdlib::collections::HashMap; + +use crate::types::exec_scope::ExecutionScopes; +use crate::types::relocatable::MaybeRelocatable; +use crate::vm::errors::hint_errors::HintError; +use crate::{ + hint_processor::hint_processor_definition::HintReference, vm::vm_core::VirtualMachine, +}; + +pub fn print_felt( + vm: &VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let val = get_integer_from_var_name("x", vm, ids_data, ap_tracking)?; + println!("{val}"); + Ok(()) +} + +fn print_name( + vm: &VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let name = get_integer_from_var_name("name", vm, ids_data, ap_tracking)?; + let name = String::from_utf8(name.to_bigint().to_signed_bytes_be()) + .map_err(|err| HintError::CustomHint(err.to_string().into_boxed_str()))?; + println!("{name}"); + Ok(()) +} + +pub fn print_array( + vm: &VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + print_name(vm, ids_data, ap_tracking)?; + + let mut acc = Vec::new(); + let arr = get_ptr_from_var_name("arr", vm, ids_data, ap_tracking)?; + let arr_len = get_integer_from_var_name("arr_len", vm, ids_data, ap_tracking)?; + let arr_len = arr_len.to_usize().ok_or_else(|| { + HintError::CustomHint(String::from("arr_len must be a positive integer").into_boxed_str()) + })?; + for i in 0..arr_len { + let val = vm.get_integer((arr + i)?)?; + acc.push(val); + } + println!("{:?}", acc); + Ok(()) +} + +enum DictValue { + Int(Felt252), + Relocatable(Vec), +} + +impl Debug for DictValue { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Self::Int(int) => write!(f, "{:?}", int), + Self::Relocatable(relocatable) => write!(f, "{:?}", relocatable), + } + } +} + +pub fn print_dict( + vm: &VirtualMachine, + exec_scopes: &ExecutionScopes, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + print_name(vm, ids_data, ap_tracking)?; + + let dict_ptr = get_ptr_from_var_name("dict_ptr", vm, ids_data, ap_tracking)?; + let pointer_size = get_integer_from_var_name("pointer_size", vm, ids_data, ap_tracking)?; + let pointer_size = pointer_size.to_usize().ok_or_else(|| { + HintError::CustomHint( + String::from("pointer_size must be a positive integer").into_boxed_str(), + ) + })?; + + let dict_manager = exec_scopes.get_dict_manager()?; + let dict_manager = dict_manager.borrow(); + let tracker = dict_manager.get_tracker(dict_ptr)?; + + let map = match &tracker.data { + Dictionary::SimpleDictionary(dict) => dict, + Dictionary::DefaultDictionary { dict, .. } => dict, + }; + + let mut acc = HashMap::new(); + for (k, v) in map.iter() { + let key = k.get_int_ref().ok_or_else(|| { + HintError::CustomHint(String::from("Expected felt key for dict").into_boxed_str()) + })?; + match v { + MaybeRelocatable::Int(value) => { + acc.insert(key, DictValue::Int(value.clone())); + } + MaybeRelocatable::RelocatableValue(val) => { + let mut structure = Vec::new(); + for i in 0..pointer_size { + let val = vm.get_integer((*val + i)?)?.as_ref().clone(); + structure.push(val); + } + acc.insert(key, DictValue::Relocatable(structure)); + } + } + } + + println!("{:?}", acc); + Ok(()) +} diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index 715a087f45..fdcbe16f60 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1032,3 +1032,32 @@ fn divmod_igcdex_not_one() { let error_msg = "Operation failed: divmod(1, 340282366920938463463374607431768211457, 340282366920938463463374607431768211457), igcdex(340282366920938463463374607431768211457, 340282366920938463463374607431768211457) != 1"; run_program_with_error(program_data.as_slice(), error_msg); } + +#[test] +#[cfg(feature = "print")] +fn cairo_run_print_felt() { + let program_data = include_bytes!("../../../cairo_programs/print_feature/print_felt.json"); + run_program_simple(program_data); +} + +#[test] +#[cfg(feature = "print")] +fn cairo_run_print_array() { + let program_data = include_bytes!("../../../cairo_programs/print_feature/print_array.json"); + run_program_simple(program_data); +} + +#[test] +#[cfg(feature = "print")] +fn cairo_run_print_dict_felt() { + let program_data = include_bytes!("../../../cairo_programs/print_feature/print_dict_felt.json"); + run_program_simple_with_memory_holes(program_data, 5); +} + +#[test] +#[cfg(feature = "print")] +fn cairo_run_print_dict_array() { + let program_data = + include_bytes!("../../../cairo_programs/print_feature/print_dict_array.json"); + run_program_simple_with_memory_holes(program_data, 4); +}