Skip to content

Commit c745ece

Browse files
fmolettaorizi
andauthored
[Cairo1] Use a cheatcode to relocate all dicts + Make temporary segment usage configurable (#1776)
* Merged hint temporary segments by cheatcode hint. * Added line removed by mistake. * Remove std import * Rename Cheatcode for clarity + remove unwrap * use real or temporary based on flag * Remove assert * Remove unwrap * Add flag to HintProcessor * Update test util * Add Changelog Entry * Fmt + clarity --------- Co-authored-by: Ori Ziv <[email protected]>
1 parent 55ffaf4 commit c745ece

File tree

8 files changed

+123
-51
lines changed

8 files changed

+123
-51
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
#### Upcoming Changes
44

5+
* feat(BREAKING): Use a cheatcode to relocate all dicts + Make temporary segment usage configurable [#1776](https://github.com/lambdaclass/cairo-vm/pull/1776)
6+
* 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.
7+
* `DictManagerExecScope::finalize_segment` no longer performs relocation and is ignored if `use_temporary_segments` is set to false.
8+
* 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.
9+
* Add cheatcode `RelocateAllDictionaries` to the `Cairo1HintProcessor`, which calls the aforementioned method.
10+
* 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.
11+
512
* Bump `starknet-types-core` version + Use the lib's pedersen hash [#1734](https://github.com/lambdaclass/cairo-vm/pull/1734)
613

714
* refactor: Add boolean method Cairo1RunConfig::copy_to_output + Update Doc [#1778](https://github.com/lambdaclass/cairo-vm/pull/1778)

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cairo1-run/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ assert_matches = "1.5.0"
2929
rstest = "0.17.0"
3030
mimalloc = { version = "0.1.37", default-features = false, optional = true }
3131
num-traits = { version = "0.2", default-features = false }
32+
num-bigint.workspace = true
3233

3334
[features]
3435
default = ["with_mimalloc"]

cairo1-run/src/cairo_run.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use cairo_lang_casm::{
44
casm, casm_build_extend,
55
cell_expression::CellExpression,
66
deref, deref_or_immediate,
7-
hints::Hint,
7+
hints::{Hint, StarknetHint},
88
inline::CasmContext,
99
instructions::{Instruction, InstructionBody},
1010
};
@@ -31,7 +31,9 @@ use cairo_lang_sierra_to_casm::{
3131
metadata::calc_metadata_ap_change_only,
3232
};
3333
use cairo_lang_sierra_type_size::get_type_size_map;
34-
use cairo_lang_utils::{casts::IntoOrPanic, unordered_hash_map::UnorderedHashMap};
34+
use cairo_lang_utils::{
35+
bigint::BigIntAsHex, casts::IntoOrPanic, unordered_hash_map::UnorderedHashMap,
36+
};
3537
use cairo_vm::{
3638
hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor,
3739
math_utils::signed_felt,
@@ -48,6 +50,7 @@ use cairo_vm::{
4850
Felt252,
4951
};
5052
use itertools::{chain, Itertools};
53+
use num_bigint::{BigInt, Sign};
5154
use num_traits::{cast::ToPrimitive, Zero};
5255
use std::{collections::HashMap, iter::Peekable};
5356

@@ -195,7 +198,11 @@ pub fn cairo_run_program(
195198

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

198-
let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default());
201+
let mut hint_processor = Cairo1HintProcessor::new(
202+
&processor_hints,
203+
RunResources::default(),
204+
cairo_run_config.copy_to_output(),
205+
);
199206

200207
let data: Vec<MaybeRelocatable> = instructions
201208
.flat_map(|inst| inst.assemble().encode())
@@ -742,6 +749,23 @@ fn create_entry_code(
742749
// len(builtins) + len(builtins - output) + segment_arena_ptr + info_segment + 0
743750
let off = 2 * builtins.len() + 2;
744751
let segment_arena_ptr = ctx.add_var(CellExpression::Deref(deref!([fp + off as i16])));
752+
// Call the hint that will relocate all dictionaries
753+
ctx.add_hint(
754+
|[ignored_in], [ignored_out]| StarknetHint::Cheatcode {
755+
selector: BigIntAsHex {
756+
value: BigInt::from_bytes_be(
757+
Sign::Plus,
758+
"RelocateAllDictionaries".as_bytes(),
759+
),
760+
},
761+
input_start: ignored_in.clone(),
762+
input_end: ignored_in,
763+
output_start: ignored_out,
764+
output_end: ignored_out,
765+
},
766+
[segment_arena_ptr],
767+
[segment_arena_ptr],
768+
);
745769
// Validating the segment arena's segments are one after the other.
746770
casm_build_extend! {ctx,
747771
tempvar n_segments = segment_arena_ptr[-2];

vm/src/hint_processor/cairo_1_hint_processor/dict_manager.rs

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ pub struct DictTrackerExecScope {
1414
data: HashMap<Felt252, MaybeRelocatable>,
1515
/// The start of the segment of the dictionary.
1616
start: Relocatable,
17-
/// The start of the next segment in the segment arena, if finalized.
18-
next_start: Option<Relocatable>,
17+
/// The end of this segment, if finalized.
18+
end: Option<Relocatable>,
1919
}
2020

2121
/// Helper object to allocate, track and destruct all dictionaries in the run.
@@ -25,6 +25,9 @@ pub struct DictManagerExecScope {
2525
segment_to_tracker: HashMap<isize, usize>,
2626
/// The actual trackers of the dictionaries, in the order of allocation.
2727
trackers: Vec<DictTrackerExecScope>,
28+
// 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
29+
// If set to false, each dictionary will use a single real segment
30+
use_temporary_segments: bool,
2831
}
2932

3033
impl DictTrackerExecScope {
@@ -33,29 +36,39 @@ impl DictTrackerExecScope {
3336
Self {
3437
data: HashMap::default(),
3538
start,
36-
next_start: None,
39+
end: None,
3740
}
3841
}
3942
}
4043

4144
impl DictManagerExecScope {
4245
pub const DICT_DEFAULT_VALUE: usize = 0;
4346

47+
// Creates a new DictManagerExecScope
48+
pub fn new(use_temporary_segments: bool) -> Self {
49+
Self {
50+
use_temporary_segments,
51+
..Default::default()
52+
}
53+
}
54+
4455
/// Allocates a new segment for a new dictionary and return the start of the segment.
4556
pub fn new_default_dict(&mut self, vm: &mut VirtualMachine) -> Result<Relocatable, HintError> {
46-
let dict_segment = match self.trackers.last() {
47-
// This is the first dict - a totally new segment is required.
48-
None => vm.add_memory_segment(),
49-
// New dict segment should be appended to the last segment.
50-
// Appending by a temporary segment, if the last segment is not finalized.
51-
Some(last) => last
52-
.next_start
53-
.unwrap_or_else(|| vm.add_temporary_segment()),
57+
let dict_segment = if self.use_temporary_segments {
58+
vm.add_temporary_segment()
59+
} else {
60+
vm.add_memory_segment()
5461
};
5562
let tracker = DictTrackerExecScope::new(dict_segment);
56-
// Not checking if overriding - since overriding is allowed.
57-
self.segment_to_tracker
58-
.insert(dict_segment.segment_index, self.trackers.len());
63+
if self
64+
.segment_to_tracker
65+
.insert(dict_segment.segment_index, self.trackers.len())
66+
.is_some()
67+
{
68+
return Err(HintError::CantCreateDictionaryOnTakenSegment(
69+
dict_segment.segment_index,
70+
));
71+
}
5972

6073
self.trackers.push(tracker);
6174
Ok(dict_segment)
@@ -85,31 +98,35 @@ impl DictManagerExecScope {
8598
}
8699

87100
/// Finalizes a segment of a dictionary.
88-
pub fn finalize_segment(
89-
&mut self,
90-
vm: &mut VirtualMachine,
91-
dict_end: Relocatable,
92-
) -> Result<(), HintError> {
93-
let tracker_idx = self.get_dict_infos_index(dict_end).unwrap();
94-
let tracker = &mut self.trackers[tracker_idx];
95-
let next_start = (dict_end + 1u32).unwrap();
96-
if let Some(prev) = tracker.next_start {
97-
return Err(HintError::CustomHint(
98-
format!(
99-
"The segment is already finalized. \
100-
Attempting to override next start {prev}, with: {next_start}.",
101-
)
102-
.into_boxed_str(),
103-
));
101+
/// Does nothing if use_temporary_segments is set to false
102+
pub fn finalize_segment(&mut self, dict_end: Relocatable) -> Result<(), HintError> {
103+
if self.use_temporary_segments {
104+
let tracker_idx = self.get_dict_infos_index(dict_end)?;
105+
let tracker = &mut self.trackers[tracker_idx];
106+
if let Some(prev) = tracker.end {
107+
return Err(HintError::CustomHint(
108+
format!(
109+
"The segment is already finalized. \
110+
Attempting to override next start {prev}, with: {dict_end}.",
111+
)
112+
.into_boxed_str(),
113+
));
114+
}
115+
tracker.end = Some(dict_end);
104116
}
105-
tracker.next_start = Some(next_start);
106-
if let Some(next) = self.trackers.get(tracker_idx + 1) {
107-
// Merging the next temporary segment with the closed segment.
108-
vm.add_relocation_rule(next.start, next_start).unwrap();
109-
// Updating the segment to point to tracker the next segment points to.
110-
let next_tracker_idx = self.segment_to_tracker[&next.start.segment_index];
111-
self.segment_to_tracker
112-
.insert(dict_end.segment_index, next_tracker_idx);
117+
Ok(())
118+
}
119+
120+
/// Relocates all dictionaries into a single segment
121+
/// Does nothing if use_temporary_segments is set to false
122+
pub fn relocate_all_dictionaries(&mut self, vm: &mut VirtualMachine) -> Result<(), HintError> {
123+
if self.use_temporary_segments {
124+
let mut prev_end = vm.add_memory_segment();
125+
for tracker in &self.trackers {
126+
vm.add_relocation_rule(tracker.start, prev_end)?;
127+
prev_end += (tracker.end.unwrap_or_default() - tracker.start)?;
128+
prev_end += 1;
129+
}
113130
}
114131
Ok(())
115132
}

vm/src/hint_processor/cairo_1_hint_processor/hint_processor.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
use ark_ff::fields::{Fp256, MontBackend, MontConfig};
1818
use ark_ff::{Field, PrimeField};
1919
use ark_std::UniformRand;
20-
use cairo_lang_casm::hints::{CoreHintBase, DeprecatedHint};
20+
use cairo_lang_casm::hints::{CoreHintBase, DeprecatedHint, StarknetHint};
2121
use cairo_lang_casm::{
2222
hints::{CoreHint, Hint},
2323
operand::{CellRef, ResOperand},
@@ -54,13 +54,21 @@ fn get_beta() -> Felt252 {
5454
pub struct Cairo1HintProcessor {
5555
hints: HashMap<usize, Vec<Hint>>,
5656
run_resources: RunResources,
57+
/// If set to true, uses a single segment for dictionaries to aid in segment arena validations
58+
/// WARNING: The program must call the "RelocateAllDictionaries" Cheatcode if the flag is enabled
59+
segment_arena_validations: bool,
5760
}
5861

5962
impl Cairo1HintProcessor {
60-
pub fn new(hints: &[(usize, Vec<Hint>)], run_resources: RunResources) -> Self {
63+
pub fn new(
64+
hints: &[(usize, Vec<Hint>)],
65+
run_resources: RunResources,
66+
segment_arena_validations: bool,
67+
) -> Self {
6168
Self {
6269
hints: hints.iter().cloned().collect(),
6370
run_resources,
71+
segment_arena_validations,
6472
}
6573
}
6674
// Runs a single Hint
@@ -166,7 +174,7 @@ impl Cairo1HintProcessor {
166174
})) => self.linear_split(vm, value, scalar, max_x, x, y),
167175

168176
Hint::Core(CoreHintBase::Core(CoreHint::AllocFelt252Dict { segment_arena_ptr })) => {
169-
self.alloc_felt_256_dict(vm, segment_arena_ptr, exec_scopes)
177+
self.alloc_felt_252_dict(vm, segment_arena_ptr, exec_scopes)
170178
}
171179

172180
Hint::Core(CoreHintBase::Core(CoreHint::AssertLeFindSmallArcs {
@@ -266,6 +274,20 @@ impl Cairo1HintProcessor {
266274
t_or_k0,
267275
t_or_k1,
268276
),
277+
Hint::Starknet(StarknetHint::Cheatcode { selector, .. }) => {
278+
let selector = &selector.value.to_bytes_be().1;
279+
let selector = crate::stdlib::str::from_utf8(selector).map_err(|_| {
280+
HintError::CustomHint(Box::from("failed to parse selector".to_string()))
281+
})?;
282+
match selector {
283+
"RelocateAllDictionaries" => {
284+
let dict_manager_exec_scope = exec_scopes
285+
.get_mut_ref::<DictManagerExecScope>("dict_manager_exec_scope")?;
286+
dict_manager_exec_scope.relocate_all_dictionaries(vm)
287+
}
288+
_ => Err(HintError::UnknownHint(selector.into())),
289+
}
290+
}
269291

270292
hint => Err(HintError::UnknownHint(
271293
format!("{:?}", hint).into_boxed_str(),
@@ -418,7 +440,7 @@ impl Cairo1HintProcessor {
418440
vm.insert_value(cell_ref_to_relocatable(dict_index, vm)?, dict_infos_index)
419441
.map_err(HintError::from)?;
420442
// The hint is only for dictionary finalization, so can be called.
421-
dict_manager_exec_scope.finalize_segment(vm, dict_address)
443+
dict_manager_exec_scope.finalize_segment(dict_address)
422444
}
423445

424446
#[allow(clippy::too_many_arguments)]
@@ -548,7 +570,7 @@ impl Cairo1HintProcessor {
548570
Err(HintError::KeyNotFound)
549571
}
550572

551-
fn alloc_felt_256_dict(
573+
fn alloc_felt_252_dict(
552574
&self,
553575
vm: &mut VirtualMachine,
554576
segment_arena_ptr: &ResOperand,
@@ -577,7 +599,7 @@ impl Cairo1HintProcessor {
577599
Err(_) => {
578600
exec_scopes.assign_or_update_variable(
579601
"dict_manager_exec_scope",
580-
Box::<DictManagerExecScope>::default(),
602+
Box::new(DictManagerExecScope::new(self.segment_arena_validations)),
581603
);
582604
exec_scopes.get_mut_ref::<DictManagerExecScope>("dict_manager_exec_scope")?
583605
}

vm/src/tests/cairo_1_run_from_entrypoint_tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ fn fibonacci_with_run_resources_ok() {
604604
let contract_class: CasmContractClass = serde_json::from_slice(program_data).unwrap();
605605
// Program takes 621 steps
606606
let mut hint_processor =
607-
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(621));
607+
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(621), false);
608608
assert_matches!(
609609
run_cairo_1_entrypoint_with_run_resources(
610610
serde_json::from_slice(program_data.as_slice()).unwrap(),
@@ -625,7 +625,7 @@ fn fibonacci_with_run_resources_2_ok() {
625625
let contract_class: CasmContractClass = serde_json::from_slice(program_data).unwrap();
626626
// Program takes 621 steps
627627
let mut hint_processor =
628-
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(1000));
628+
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(1000), false);
629629
assert_matches!(
630630
run_cairo_1_entrypoint_with_run_resources(
631631
contract_class,
@@ -648,7 +648,7 @@ fn fibonacci_with_run_resources_error() {
648648
let contract_class: CasmContractClass = serde_json::from_slice(program_data).unwrap();
649649
// Program takes 621 steps
650650
let mut hint_processor =
651-
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(100));
651+
Cairo1HintProcessor::new(&contract_class.hints, RunResources::new(100), false);
652652
assert!(run_cairo_1_entrypoint_with_run_resources(
653653
contract_class,
654654
0,

vm/src/tests/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ fn run_cairo_1_entrypoint(
108108
) {
109109
let contract_class: CasmContractClass = serde_json::from_slice(program_content).unwrap();
110110
let mut hint_processor =
111-
Cairo1HintProcessor::new(&contract_class.hints, RunResources::default());
111+
Cairo1HintProcessor::new(&contract_class.hints, RunResources::default(), false);
112112

113113
let mut runner = CairoRunner::new(
114114
&(contract_class.clone().try_into().unwrap()),

0 commit comments

Comments
 (0)