Skip to content

Commit

Permalink
feat: add printing hints (#1476)
Browse files Browse the repository at this point in the history
* add print hints

* test

* remove u256 add felt

* change log

* fix based on comments

* add std feature for print

* Move Cairo Programs

* Update Makefile

* Use print tag  in test

* cargo fmt

* Update Makefile

* Update Makefile

---------

Co-authored-by: Pedro Fontana <[email protected]>
  • Loading branch information
greged93 and pefontana authored Nov 23, 2023
1 parent 397e699 commit 4089b46
Show file tree
Hide file tree
Showing 12 changed files with 278 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
# ======================
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions cairo_programs/print_feature/print_array.cairo
Original file line number Diff line number Diff line change
@@ -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();
}
34 changes: 34 additions & 0 deletions cairo_programs/print_feature/print_dict_array.cairo
Original file line number Diff line number Diff line change
@@ -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();
}
24 changes: 24 additions & 0 deletions cairo_programs/print_feature/print_dict_felt.cairo
Original file line number Diff line number Diff line change
@@ -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();
}
9 changes: 9 additions & 0 deletions cairo_programs/print_feature/print_felt.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
%builtins range_check

func main{range_check_ptr: felt}() {
let x = 123;
%{
print(ids.x)
%}
return();
}
2 changes: 2 additions & 0 deletions vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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())),
}
}
Expand Down
12 changes: 12 additions & 0 deletions vm/src/hint_processor/builtin_hint_processor/hint_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
)"#;
2 changes: 2 additions & 0 deletions vm/src/hint_processor/builtin_hint_processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
124 changes: 124 additions & 0 deletions vm/src/hint_processor/builtin_hint_processor/print.rs
Original file line number Diff line number Diff line change
@@ -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<String, HintReference>,
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<String, HintReference>,
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<String, HintReference>,
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<Felt252>),
}

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<String, HintReference>,
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(())
}
29 changes: 29 additions & 0 deletions vm/src/tests/cairo_run_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit 4089b46

Please sign in to comment.