Skip to content

Commit 7eedc53

Browse files
Merge Cairo Pie extra segments into one segment (#1960)
* implement merge_extra_segments * revert changes in CairoPieMemory * format * changelog * add reference * serialize memory according to the segment_offsets values * format * fature std for test * change for * clippy * clippy * add merge_extra_segments as argument * docs * typo in changelog * revert making self mut in write_zip_file method * forgot to update metadata in write_zip_file * make merge_extra_segments method private * clippy * reviews * format * reviews * format * reviews * clippy
1 parent c408e01 commit 7eedc53

File tree

4 files changed

+257
-17
lines changed

4 files changed

+257
-17
lines changed

CHANGELOG.md

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

33
#### Upcoming Changes
44

5+
* fix: Fix Cairo Pie limiting the number of segments to 2^16 [#1960](https://github.com/lambdaclass/cairo-vm/pull/1960)
6+
* Implement `merge_extra_segments`
7+
58
* feat: implement an opcode that computes QM31 arithmetics (add, sub, mul, div) in the VM [#1938](https://github.com/lambdaclass/cairo-vm/pull/1938)
69

710
* feat: add functions that compute packed reduced qm31 arithmetics to `math_utils` [#1944](https://github.com/lambdaclass/cairo-vm/pull/1944)

cairo-vm-cli/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ struct Args {
6969
conflicts_with_all = ["proof_mode", "air_private_input", "air_public_input"]
7070
)]
7171
cairo_pie_output: Option<String>,
72+
#[structopt(long = "merge_extra_segments")]
73+
merge_extra_segments: bool,
7274
#[structopt(long = "allow_missing_builtins")]
7375
allow_missing_builtins: Option<bool>,
7476
#[structopt(long = "tracer")]
@@ -273,7 +275,7 @@ fn run(args: impl Iterator<Item = String>) -> Result<(), Error> {
273275
cairo_runner
274276
.get_cairo_pie()
275277
.map_err(CairoRunError::Runner)?
276-
.write_zip_file(file_path)?
278+
.write_zip_file(file_path, args.merge_extra_segments)?
277279
}
278280

279281
Ok(())

cairo1-run/src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ struct Args {
4848
conflicts_with_all = ["proof_mode", "air_private_input", "air_public_input"]
4949
)]
5050
cairo_pie_output: Option<PathBuf>,
51+
#[clap(long = "merge_extra_segments", value_parser)]
52+
merge_extra_segments: bool,
5153
// Arguments should be spaced, with array elements placed between brackets
5254
// For example " --args '1 2 [1 2 3]'" will yield 3 arguments, with the last one being an array of 3 elements
5355
#[clap(long = "args", default_value = "", value_parser=process_args, conflicts_with = "args_file")]
@@ -234,7 +236,9 @@ fn run(args: impl Iterator<Item = String>) -> Result<Option<String>, Error> {
234236
}
235237

236238
if let Some(ref file_path) = args.cairo_pie_output {
237-
runner.get_cairo_pie()?.write_zip_file(file_path)?
239+
runner
240+
.get_cairo_pie()?
241+
.write_zip_file(file_path, args.merge_extra_segments)?
238242
}
239243

240244
if let Some(trace_path) = args.trace_file {

vm/src/vm/runners/cairo_pie.rs

Lines changed: 246 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -301,17 +301,35 @@ impl CairoPie {
301301
}
302302

303303
#[cfg(feature = "std")]
304-
pub fn write_zip_file(&self, file_path: &Path) -> Result<(), std::io::Error> {
304+
pub fn write_zip_file(
305+
&self,
306+
file_path: &Path,
307+
merge_extra_segments: bool,
308+
) -> Result<(), std::io::Error> {
309+
let mut metadata = self.metadata.clone();
310+
311+
let segment_offsets = if merge_extra_segments {
312+
if let Some((segment, segment_offsets)) = self.merge_extra_segments() {
313+
metadata.extra_segments = vec![segment];
314+
Some(segment_offsets)
315+
} else {
316+
None
317+
}
318+
} else {
319+
None
320+
};
321+
305322
let file = File::create(file_path)?;
306323
let mut zip_writer = ZipWriter::new(file);
307324
let options =
308325
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
326+
309327
zip_writer.start_file("version.json", options)?;
310328
serde_json::to_writer(&mut zip_writer, &self.version)?;
311329
zip_writer.start_file("metadata.json", options)?;
312-
serde_json::to_writer(&mut zip_writer, &self.metadata)?;
330+
serde_json::to_writer(&mut zip_writer, &metadata)?;
313331
zip_writer.start_file("memory.bin", options)?;
314-
zip_writer.write_all(&self.memory.to_bytes())?;
332+
zip_writer.write_all(&self.memory.to_bytes(segment_offsets))?;
315333
zip_writer.start_file("additional_data.json", options)?;
316334
serde_json::to_writer(&mut zip_writer, &self.additional_data)?;
317335
zip_writer.start_file("execution_resources.json", options)?;
@@ -372,6 +390,48 @@ impl CairoPie {
372390

373391
Self::from_zip_archive(zip)
374392
}
393+
394+
// Heavily inspired in:
395+
// https://github.com/starkware-libs/cairo-lang/blob/8276ac35830148a397e1143389f23253c8b80e93/src/starkware/cairo/lang/vm/cairo_pie.py#L286-L306
396+
/// Merges `extra_segments` to a single segment.
397+
///
398+
/// Returns a tuple with the new `extra_segments` (containing just the merged segment)
399+
/// and a HashMap with the old segment indices mapped to their new offset in the new segment
400+
#[cfg(feature = "std")]
401+
fn merge_extra_segments(&self) -> Option<(SegmentInfo, HashMap<usize, Relocatable>)> {
402+
if self.metadata.extra_segments.is_empty() {
403+
return None;
404+
}
405+
406+
let new_index = self.metadata.extra_segments[0].index;
407+
let mut accumulated_size = 0;
408+
let offsets: HashMap<usize, Relocatable> = self
409+
.metadata
410+
.extra_segments
411+
.iter()
412+
.map(|seg| {
413+
let value = (
414+
seg.index as usize,
415+
Relocatable {
416+
segment_index: new_index,
417+
offset: accumulated_size,
418+
},
419+
);
420+
421+
accumulated_size += seg.size;
422+
423+
value
424+
})
425+
.collect();
426+
427+
Some((
428+
SegmentInfo {
429+
index: new_index,
430+
size: accumulated_size,
431+
},
432+
offsets,
433+
))
434+
}
375435
}
376436

377437
pub(super) mod serde_impl {
@@ -595,26 +655,50 @@ pub(super) mod serde_impl {
595655
}
596656

597657
impl CairoPieMemory {
598-
pub fn to_bytes(&self) -> Vec<u8> {
658+
/// Relocates a `Relocatable` value, which represented by its
659+
/// index and offset, according to a given segment offsets
660+
fn relocate_value(
661+
index: usize,
662+
offset: usize,
663+
segment_offsets: &Option<HashMap<usize, Relocatable>>,
664+
) -> (usize, usize) {
665+
segment_offsets
666+
.as_ref()
667+
.and_then(|offsets| offsets.get(&index))
668+
.map(|relocatable| {
669+
(
670+
relocatable.segment_index as usize,
671+
relocatable.offset + offset,
672+
)
673+
})
674+
.unwrap_or((index, offset))
675+
}
676+
677+
pub fn to_bytes(&self, seg_offsets: Option<HashMap<usize, Relocatable>>) -> Vec<u8> {
599678
// Missing segment and memory holes can be ignored
600679
// as they can be inferred by the address on the prover side
601680
let values = &self.0;
602681
let mem_cap = values.len() * ADDR_BYTE_LEN + values.len() * FIELD_BYTE_LEN;
603682
let mut res = Vec::with_capacity(mem_cap);
604683

605684
for ((segment, offset), value) in values.iter() {
606-
let mem_addr = ADDR_BASE + *segment as u64 * OFFSET_BASE + *offset as u64;
685+
let (segment, offset) = Self::relocate_value(*segment, *offset, &seg_offsets);
686+
let mem_addr = ADDR_BASE + segment as u64 * OFFSET_BASE + offset as u64;
607687
res.extend_from_slice(mem_addr.to_le_bytes().as_ref());
608688
match value {
609689
// Serializes RelocatableValue(little endian):
610690
// 1bit | SEGMENT_BITS | OFFSET_BITS
611691
// 1 | segment | offset
612692
MaybeRelocatable::RelocatableValue(rel_val) => {
693+
let (segment, offset) = Self::relocate_value(
694+
rel_val.segment_index as usize,
695+
rel_val.offset,
696+
&seg_offsets,
697+
);
613698
let reloc_base = BigUint::from_str_radix(RELOCATE_BASE, 16).unwrap();
614699
let reloc_value = reloc_base
615-
+ BigUint::from(rel_val.segment_index as usize)
616-
* BigUint::from(OFFSET_BASE)
617-
+ BigUint::from(rel_val.offset);
700+
+ BigUint::from(segment) * BigUint::from(OFFSET_BASE)
701+
+ BigUint::from(offset);
618702
res.extend_from_slice(reloc_value.to_bytes_le().as_ref());
619703
}
620704
// Serializes Int(little endian):
@@ -780,7 +864,14 @@ pub(super) mod serde_impl {
780864
#[cfg(test)]
781865
mod test {
782866
#[cfg(feature = "std")]
783-
use rstest::rstest;
867+
use {
868+
crate::{
869+
cairo_run::CairoRunConfig,
870+
hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
871+
types::layout_name::LayoutName,
872+
},
873+
rstest::rstest,
874+
};
784875

785876
use super::*;
786877

@@ -858,11 +949,6 @@ mod test {
858949
#[case(include_bytes!("../../../../cairo_programs/bitwise_output.json"), "bitwise")]
859950
#[case(include_bytes!("../../../../cairo_programs/value_beyond_segment.json"), "relocate_beyond")]
860951
fn read_write_pie_zip(#[case] program_content: &[u8], #[case] identifier: &str) {
861-
use crate::{
862-
cairo_run::CairoRunConfig,
863-
hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
864-
types::layout_name::LayoutName,
865-
};
866952
// Run a program to obtain the CairoPie
867953
let cairo_pie = {
868954
let cairo_run_config = CairoRunConfig {
@@ -880,12 +966,157 @@ mod test {
880966
// Serialize the CairoPie into a zip file
881967
let filename = format!("temp_file_{}", identifier); // Identifier used to avoid name clashes
882968
let file_path = Path::new(&filename);
883-
cairo_pie.write_zip_file(file_path).unwrap();
969+
cairo_pie.write_zip_file(file_path, false).unwrap();
884970
// Deserialize the zip file
885971
let deserialized_pie = CairoPie::read_zip_file(file_path).unwrap();
886972
// Check that both pies are equal
887973
assert_eq!(cairo_pie, deserialized_pie);
888974
// Remove zip file created by the test
889975
std::fs::remove_file(file_path).unwrap();
890976
}
977+
978+
#[test]
979+
#[cfg(feature = "std")]
980+
fn cairo_pie_with_extra_segments() {
981+
let program_content = include_bytes!("../../../../cairo_programs/fibonacci.json");
982+
let mut cairo_pie = {
983+
let cairo_run_config = CairoRunConfig {
984+
layout: LayoutName::starknet_with_keccak,
985+
..Default::default()
986+
};
987+
let runner = crate::cairo_run::cairo_run(
988+
program_content,
989+
&cairo_run_config,
990+
&mut BuiltinHintProcessor::new_empty(),
991+
)
992+
.unwrap();
993+
runner.get_cairo_pie().unwrap()
994+
};
995+
996+
cairo_pie.metadata.extra_segments = vec![
997+
SegmentInfo { index: 8, size: 10 },
998+
SegmentInfo { index: 9, size: 20 },
999+
];
1000+
let memory = CairoPieMemory(vec![
1001+
(
1002+
(3, 4),
1003+
MaybeRelocatable::RelocatableValue(Relocatable {
1004+
segment_index: 6,
1005+
offset: 7,
1006+
}),
1007+
),
1008+
(
1009+
(8, 0),
1010+
MaybeRelocatable::RelocatableValue(Relocatable {
1011+
segment_index: 8,
1012+
offset: 4,
1013+
}),
1014+
),
1015+
(
1016+
(9, 3),
1017+
MaybeRelocatable::RelocatableValue(Relocatable {
1018+
segment_index: 9,
1019+
offset: 7,
1020+
}),
1021+
),
1022+
]);
1023+
1024+
cairo_pie.memory = memory;
1025+
1026+
let file_path = Path::new("merge_extra_segments_test");
1027+
1028+
cairo_pie.write_zip_file(file_path, true).unwrap();
1029+
1030+
let result_cairo_pie = CairoPie::read_zip_file(file_path).unwrap();
1031+
1032+
std::fs::remove_file(file_path).unwrap();
1033+
1034+
assert_eq!(
1035+
result_cairo_pie.metadata.extra_segments,
1036+
vec![SegmentInfo { index: 8, size: 30 }]
1037+
);
1038+
assert_eq!(
1039+
result_cairo_pie.memory,
1040+
CairoPieMemory(vec![
1041+
(
1042+
(3, 4),
1043+
MaybeRelocatable::RelocatableValue(Relocatable {
1044+
segment_index: 6,
1045+
offset: 7
1046+
})
1047+
),
1048+
(
1049+
(8, 0),
1050+
MaybeRelocatable::RelocatableValue(Relocatable {
1051+
segment_index: 8,
1052+
offset: 4
1053+
})
1054+
),
1055+
(
1056+
(8, 13),
1057+
MaybeRelocatable::RelocatableValue(Relocatable {
1058+
segment_index: 8,
1059+
offset: 17
1060+
})
1061+
),
1062+
])
1063+
)
1064+
}
1065+
1066+
#[test]
1067+
#[cfg(feature = "std")]
1068+
fn cairo_pie_without_extra_segments() {
1069+
let program_content = include_bytes!("../../../../cairo_programs/fibonacci.json");
1070+
let mut cairo_pie = {
1071+
let cairo_run_config = CairoRunConfig {
1072+
layout: LayoutName::starknet_with_keccak,
1073+
..Default::default()
1074+
};
1075+
let runner = crate::cairo_run::cairo_run(
1076+
program_content,
1077+
&cairo_run_config,
1078+
&mut BuiltinHintProcessor::new_empty(),
1079+
)
1080+
.unwrap();
1081+
runner.get_cairo_pie().unwrap()
1082+
};
1083+
1084+
cairo_pie.metadata.extra_segments = vec![];
1085+
let memory = CairoPieMemory(vec![
1086+
(
1087+
(3, 4),
1088+
MaybeRelocatable::RelocatableValue(Relocatable {
1089+
segment_index: 6,
1090+
offset: 7,
1091+
}),
1092+
),
1093+
(
1094+
(8, 0),
1095+
MaybeRelocatable::RelocatableValue(Relocatable {
1096+
segment_index: 8,
1097+
offset: 4,
1098+
}),
1099+
),
1100+
(
1101+
(9, 3),
1102+
MaybeRelocatable::RelocatableValue(Relocatable {
1103+
segment_index: 9,
1104+
offset: 7,
1105+
}),
1106+
),
1107+
]);
1108+
1109+
cairo_pie.memory = memory.clone();
1110+
1111+
let file_path = Path::new("merge_without_extra_segments_test");
1112+
1113+
cairo_pie.write_zip_file(file_path, true).unwrap();
1114+
1115+
let result_cairo_pie = CairoPie::read_zip_file(file_path).unwrap();
1116+
1117+
std::fs::remove_file(file_path).unwrap();
1118+
1119+
assert_eq!(result_cairo_pie.metadata.extra_segments, vec![]);
1120+
assert_eq!(result_cairo_pie.memory, memory)
1121+
}
8911122
}

0 commit comments

Comments
 (0)