Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add printing hints #1476

Merged
merged 15 commits into from
Nov 23, 2023
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())

Check warning on line 53 in vm/src/hint_processor/builtin_hint_processor/print.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/hint_processor/builtin_hint_processor/print.rs#L53

Added line #L53 was not covered by tests
})?;
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(),
)

Check warning on line 90 in vm/src/hint_processor/builtin_hint_processor/print.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/hint_processor/builtin_hint_processor/print.rs#L88-L90

Added lines #L88 - L90 were not covered by tests
})?;

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,

Check warning on line 98 in vm/src/hint_processor/builtin_hint_processor/print.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/hint_processor/builtin_hint_processor/print.rs#L98

Added line #L98 was not covered by tests
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())

Check warning on line 105 in vm/src/hint_processor/builtin_hint_processor/print.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/hint_processor/builtin_hint_processor/print.rs#L105

Added line #L105 was not covered by tests
})?;
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() {
Oppen marked this conversation as resolved.
Show resolved Hide resolved
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);
}
Loading