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

[Cairo1] Use a cheatcode to relocate all dicts + Make temporary segment usage configurable #1776

Merged
merged 14 commits into from
May 30, 2024
Merged
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

#### Upcoming Changes

* feat(BREAKING): Use a cheatcode to relocate all dicts + Make temporary segment usage configurable [#1776](https://github.com/lambdaclass/cairo-vm/pull/1776)
* Add the flags `segment_arena_validation` & `use_temporary_segments` to the `Cairo1HintProcessor` & `DictManagerExecScope` respectively. These flags will determine if real segments or temporary segments will be used when creating dictionaries.
* `DictManagerExecScope::finalize_segment` no longer performs relocation and is ignored if `use_temporary_segments` is set to false.
* Add method `DictManagerExecScope::relocate_all_dictionaries` that adds relocation rules for all tracked dictionaries, relocating them one next to the other in a new segment.
* Add cheatcode `RelocateAllDictionaries` to the `Cairo1HintProcessor`, which calls the aforementioned method.
* Add casm instruction to call the aforementioned cheatcode in `create_entry_code` if either `proof_mode` or `append_return_values` are set to true, and segment arena is present.

* feat(BREAKING): Serialize inputs into output segment in cairo1-run crate:
* Checks that only `Array<Felt252>` can be received by the program main function when running with with either `--proof_mode` or `--append_return_values`.
* Copies the input value to the output segment right after the output in the format `[array_len, arr[0], arr[1],.., arr[n]]`.
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cairo1-run/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ assert_matches = "1.5.0"
rstest = "0.17.0"
mimalloc = { version = "0.1.37", default-features = false, optional = true }
num-traits = { version = "0.2", default-features = false }
num-bigint.workspace = true

[features]
default = ["with_mimalloc"]
Expand Down
30 changes: 27 additions & 3 deletions cairo1-run/src/cairo_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use cairo_lang_casm::{
casm, casm_build_extend,
cell_expression::CellExpression,
deref, deref_or_immediate,
hints::Hint,
hints::{Hint, StarknetHint},
inline::CasmContext,
instructions::{Instruction, InstructionBody},
};
Expand All @@ -31,7 +31,9 @@ use cairo_lang_sierra_to_casm::{
metadata::calc_metadata_ap_change_only,
};
use cairo_lang_sierra_type_size::get_type_size_map;
use cairo_lang_utils::{casts::IntoOrPanic, unordered_hash_map::UnorderedHashMap};
use cairo_lang_utils::{
bigint::BigIntAsHex, casts::IntoOrPanic, unordered_hash_map::UnorderedHashMap,
};
use cairo_vm::{
hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor,
math_utils::signed_felt,
Expand All @@ -48,6 +50,7 @@ use cairo_vm::{
Felt252,
};
use itertools::{chain, Itertools};
use num_bigint::{BigInt, Sign};
use num_traits::{cast::ToPrimitive, Zero};
use std::{collections::HashMap, iter::Peekable};

Expand Down Expand Up @@ -178,7 +181,11 @@ pub fn cairo_run_program(

let (processor_hints, program_hints) = build_hints_vec(instructions.clone());

let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default());
let mut hint_processor = Cairo1HintProcessor::new(
&processor_hints,
RunResources::default(),
cairo_run_config.append_return_values || cairo_run_config.proof_mode,
);

let data: Vec<MaybeRelocatable> = instructions
.flat_map(|inst| inst.assemble().encode())
Expand Down Expand Up @@ -728,6 +735,23 @@ fn create_entry_code(
// len(builtins) + len(builtins - output) + segment_arena_ptr + info_segment + 0
let off = 2 * builtins.len() + 2;
let segment_arena_ptr = ctx.add_var(CellExpression::Deref(deref!([fp + off as i16])));
// Call the hint that will relocate all dictionaries
ctx.add_hint(
|[ignored_in], [ignored_out]| StarknetHint::Cheatcode {
selector: BigIntAsHex {
value: BigInt::from_bytes_be(
Sign::Plus,
"RelocateAllDictionaries".as_bytes(),
),
},
input_start: ignored_in.clone(),
input_end: ignored_in,
output_start: ignored_out,
output_end: ignored_out,
},
[segment_arena_ptr],
[segment_arena_ptr],
);
// Validating the segment arena's segments are one after the other.
casm_build_extend! {ctx,
tempvar n_segments = segment_arena_ptr[-2];
Expand Down
93 changes: 55 additions & 38 deletions vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
data: HashMap<Felt252, MaybeRelocatable>,
/// The start of the segment of the dictionary.
start: Relocatable,
/// The start of the next segment in the segment arena, if finalized.
next_start: Option<Relocatable>,
/// The end of this segment, if finalized.
end: Option<Relocatable>,
}

/// Helper object to allocate, track and destruct all dictionaries in the run.
Expand All @@ -25,6 +25,9 @@
segment_to_tracker: HashMap<isize, usize>,
/// The actual trackers of the dictionaries, in the order of allocation.
trackers: Vec<DictTrackerExecScope>,
// If set to true, dictionaries will be created on temporary segments which can then be relocated into a single segment by the end of the run
// If set to false, each dictionary will use a single real segment
use_temporary_segments: bool,
}

impl DictTrackerExecScope {
Expand All @@ -33,29 +36,39 @@
Self {
data: HashMap::default(),
start,
next_start: None,
end: None,
}
}
}

impl DictManagerExecScope {
pub const DICT_DEFAULT_VALUE: usize = 0;

// Creates a new DictManagerExecScope
pub fn new(use_temporary_segments: bool) -> Self {
Self {
use_temporary_segments,
..Default::default()
}
}

/// Allocates a new segment for a new dictionary and return the start of the segment.
pub fn new_default_dict(&mut self, vm: &mut VirtualMachine) -> Result<Relocatable, HintError> {
let dict_segment = match self.trackers.last() {
// This is the first dict - a totally new segment is required.
None => vm.add_memory_segment(),
// New dict segment should be appended to the last segment.
// Appending by a temporary segment, if the last segment is not finalized.
Some(last) => last
.next_start
.unwrap_or_else(|| vm.add_temporary_segment()),
let dict_segment = if self.use_temporary_segments {
vm.add_temporary_segment()
} else {
vm.add_memory_segment()
};
let tracker = DictTrackerExecScope::new(dict_segment);
// Not checking if overriding - since overriding is allowed.
self.segment_to_tracker
.insert(dict_segment.segment_index, self.trackers.len());
if self
.segment_to_tracker
.insert(dict_segment.segment_index, self.trackers.len())
.is_some()
{
return Err(HintError::CantCreateDictionaryOnTakenSegment(
dict_segment.segment_index,
));

Check warning on line 70 in vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs#L68-L70

Added lines #L68 - L70 were not covered by tests
}

self.trackers.push(tracker);
Ok(dict_segment)
Expand Down Expand Up @@ -85,31 +98,35 @@
}

/// Finalizes a segment of a dictionary.
pub fn finalize_segment(
&mut self,
vm: &mut VirtualMachine,
dict_end: Relocatable,
) -> Result<(), HintError> {
let tracker_idx = self.get_dict_infos_index(dict_end).unwrap();
let tracker = &mut self.trackers[tracker_idx];
let next_start = (dict_end + 1u32).unwrap();
if let Some(prev) = tracker.next_start {
return Err(HintError::CustomHint(
format!(
"The segment is already finalized. \
Attempting to override next start {prev}, with: {next_start}.",
)
.into_boxed_str(),
));
/// Does nothing if use_temporary_segments is set to false
pub fn finalize_segment(&mut self, dict_end: Relocatable) -> Result<(), HintError> {
if self.use_temporary_segments {
let tracker_idx = self.get_dict_infos_index(dict_end)?;
let tracker = &mut self.trackers[tracker_idx];
if let Some(prev) = tracker.end {
return Err(HintError::CustomHint(
format!(
"The segment is already finalized. \
Attempting to override next start {prev}, with: {dict_end}.",
)
.into_boxed_str(),
));

Check warning on line 113 in vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs#L107-L113

Added lines #L107 - L113 were not covered by tests
}
tracker.end = Some(dict_end);
}
tracker.next_start = Some(next_start);
if let Some(next) = self.trackers.get(tracker_idx + 1) {
// Merging the next temporary segment with the closed segment.
vm.add_relocation_rule(next.start, next_start).unwrap();
// Updating the segment to point to tracker the next segment points to.
let next_tracker_idx = self.segment_to_tracker[&next.start.segment_index];
self.segment_to_tracker
.insert(dict_end.segment_index, next_tracker_idx);
Ok(())
}

/// Relocates all dictionaries into a single segment
/// Does nothing if use_temporary_segments is set to false
pub fn relocate_all_dictionaries(&mut self, vm: &mut VirtualMachine) -> Result<(), HintError> {
if self.use_temporary_segments {
let mut prev_end = vm.add_memory_segment();
for tracker in &self.trackers {
vm.add_relocation_rule(tracker.start, prev_end)?;
prev_end += (tracker.end.unwrap_or_default() - tracker.start)?;
prev_end += 1;
}
}
Ok(())
}
Expand Down
34 changes: 28 additions & 6 deletions vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use ark_ff::fields::{Fp256, MontBackend, MontConfig};
use ark_ff::{Field, PrimeField};
use ark_std::UniformRand;
use cairo_lang_casm::hints::{CoreHintBase, DeprecatedHint};
use cairo_lang_casm::hints::{CoreHintBase, DeprecatedHint, StarknetHint};
use cairo_lang_casm::{
hints::{CoreHint, Hint},
operand::{CellRef, ResOperand},
Expand Down Expand Up @@ -54,13 +54,21 @@
pub struct Cairo1HintProcessor {
hints: HashMap<usize, Vec<Hint>>,
run_resources: RunResources,
/// If set to true, uses a single segment for dictionaries to aid in segment arena validations
/// WARNING: The program must call the "RelocateAllDictionaries" Cheatcode if the flag is enabled
segment_arena_validations: bool,
}

impl Cairo1HintProcessor {
pub fn new(hints: &[(usize, Vec<Hint>)], run_resources: RunResources) -> Self {
pub fn new(
hints: &[(usize, Vec<Hint>)],
run_resources: RunResources,
segment_arena_validations: bool,
) -> Self {
Self {
hints: hints.iter().cloned().collect(),
run_resources,
segment_arena_validations,
}
}
// Runs a single Hint
Expand Down Expand Up @@ -166,7 +174,7 @@
})) => self.linear_split(vm, value, scalar, max_x, x, y),

Hint::Core(CoreHintBase::Core(CoreHint::AllocFelt252Dict { segment_arena_ptr })) => {
self.alloc_felt_256_dict(vm, segment_arena_ptr, exec_scopes)
self.alloc_felt_252_dict(vm, segment_arena_ptr, exec_scopes)
}

Hint::Core(CoreHintBase::Core(CoreHint::AssertLeFindSmallArcs {
Expand Down Expand Up @@ -266,6 +274,20 @@
t_or_k0,
t_or_k1,
),
Hint::Starknet(StarknetHint::Cheatcode { selector, .. }) => {
let selector = &selector.value.to_bytes_be().1;
let selector = crate::stdlib::str::from_utf8(selector).map_err(|_| {
HintError::CustomHint(Box::from("failed to parse selector".to_string()))

Check warning on line 280 in vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs#L280

Added line #L280 was not covered by tests
})?;
match selector {
"RelocateAllDictionaries" => {
let dict_manager_exec_scope = exec_scopes
.get_mut_ref::<DictManagerExecScope>("dict_manager_exec_scope")?;
dict_manager_exec_scope.relocate_all_dictionaries(vm)
}
_ => Err(HintError::UnknownHint(selector.into())),

Check warning on line 288 in vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs#L288

Added line #L288 was not covered by tests
}
}

hint => Err(HintError::UnknownHint(
format!("{:?}", hint).into_boxed_str(),
Expand Down Expand Up @@ -418,7 +440,7 @@
vm.insert_value(cell_ref_to_relocatable(dict_index, vm)?, dict_infos_index)
.map_err(HintError::from)?;
// The hint is only for dictionary finalization, so can be called.
dict_manager_exec_scope.finalize_segment(vm, dict_address)
dict_manager_exec_scope.finalize_segment(dict_address)
}

#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -548,7 +570,7 @@
Err(HintError::KeyNotFound)
}

fn alloc_felt_256_dict(
fn alloc_felt_252_dict(
&self,
vm: &mut VirtualMachine,
segment_arena_ptr: &ResOperand,
Expand Down Expand Up @@ -577,7 +599,7 @@
Err(_) => {
exec_scopes.assign_or_update_variable(
"dict_manager_exec_scope",
Box::<DictManagerExecScope>::default(),
Box::new(DictManagerExecScope::new(self.segment_arena_validations)),
);
exec_scopes.get_mut_ref::<DictManagerExecScope>("dict_manager_exec_scope")?
}
Expand Down
6 changes: 3 additions & 3 deletions vm/src/tests/cairo_1_run_from_entrypoint_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ fn fibonacci_with_run_resources_ok() {
let contract_class: CasmContractClass = serde_json::from_slice(program_data).unwrap();
// Program takes 621 steps
let mut hint_processor =
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(621));
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(621), false);
assert_matches!(
run_cairo_1_entrypoint_with_run_resources(
serde_json::from_slice(program_data.as_slice()).unwrap(),
Expand All @@ -625,7 +625,7 @@ fn fibonacci_with_run_resources_2_ok() {
let contract_class: CasmContractClass = serde_json::from_slice(program_data).unwrap();
// Program takes 621 steps
let mut hint_processor =
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(1000));
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(1000), false);
assert_matches!(
run_cairo_1_entrypoint_with_run_resources(
contract_class,
Expand All @@ -648,7 +648,7 @@ fn fibonacci_with_run_resources_error() {
let contract_class: CasmContractClass = serde_json::from_slice(program_data).unwrap();
// Program takes 621 steps
let mut hint_processor =
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(100));
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(100), false);
assert!(run_cairo_1_entrypoint_with_run_resources(
contract_class,
0,
Expand Down
2 changes: 1 addition & 1 deletion vm/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ fn run_cairo_1_entrypoint(
) {
let contract_class: CasmContractClass = serde_json::from_slice(program_content).unwrap();
let mut hint_processor =
Cairo1HintProcessor::new(&contract_class.hints, RunResources::default());
Cairo1HintProcessor::new(&contract_class.hints, RunResources::default(), false);

let mut runner = CairoRunner::new(
&(contract_class.clone().try_into().unwrap()),
Expand Down
Loading