Skip to content

Commit c4e803d

Browse files
(optimization) A new libfunc for calculating the pointer to temporary storage using a double call
SIERRA_UPDATE_MINOR_CHANGE_TAG=adding a libfunc for optimizing creating boxes from temp storage
1 parent 06db3a9 commit c4e803d

File tree

13 files changed

+293
-33
lines changed

13 files changed

+293
-33
lines changed

crates/cairo-lang-runner/src/casm_run/test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::{CairoHintProcessor, StarknetState, build_hints_dict};
2020
fn assembled(casm: CasmContext) -> AssembledCairoProgram {
2121
CairoProgram {
2222
instructions: casm.instructions,
23-
consts_info: Default::default(),
23+
segments_info: Default::default(),
2424
debug_info: CairoProgramDebugInfo { sierra_statement_info: vec![] },
2525
}
2626
.assemble()

crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ pub fn core_libfunc_ap_change<InfoProvider: InvocationApChangeInfoProvider>(
113113
BoxConcreteLibfunc::Into(_) => vec![ApChange::Known(1)],
114114
BoxConcreteLibfunc::Unbox(_) => vec![ApChange::Known(0)],
115115
BoxConcreteLibfunc::ForwardSnapshot(_) => vec![ApChange::Known(0)],
116+
BoxConcreteLibfunc::FromTempStore(_) => {
117+
// Total: 2 calls * 2
118+
vec![ApChange::Known(4)]
119+
}
116120
},
117121
Cast(libfunc) => match libfunc {
118122
CastConcreteLibfunc::Downcast(libfunc) => {

crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ pub fn core_libfunc_cost(
371371
BoxConcreteLibfunc::Unbox(_) | BoxConcreteLibfunc::ForwardSnapshot(_) => {
372372
vec![ConstCost::default().into()]
373373
}
374+
BoxConcreteLibfunc::FromTempStore(_) => vec![ConstCost::steps(4).into()],
374375
},
375376
Mem(libfunc) => match libfunc {
376377
StoreTemp(libfunc) => {

crates/cairo-lang-sierra-to-casm/src/compiler.rs

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::fmt::Display;
22

33
use cairo_lang_casm::assembler::AssembledCairoProgram;
4+
use cairo_lang_casm::casm;
45
use cairo_lang_casm::instructions::{Instruction, InstructionBody, RetInstruction};
56
use cairo_lang_sierra::extensions::ConcreteLibfunc;
7+
use cairo_lang_sierra::extensions::boxing::BoxConcreteLibfunc;
68
use cairo_lang_sierra::extensions::circuit::{CircuitConcreteLibfunc, CircuitInfo, VALUE_SIZE};
79
use cairo_lang_sierra::extensions::const_type::ConstConcreteLibfunc;
810
use cairo_lang_sierra::extensions::core::{
@@ -106,7 +108,7 @@ pub struct SierraToCasmConfig {
106108
pub struct CairoProgram {
107109
pub instructions: Vec<Instruction>,
108110
pub debug_info: CairoProgramDebugInfo,
109-
pub consts_info: ConstsInfo,
111+
pub segments_info: SegmentsInfo,
110112
}
111113
impl Display for CairoProgram {
112114
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -116,24 +118,35 @@ impl Display for CairoProgram {
116118
writeln!(f, "{instruction}; // {bytecode_offset}")?;
117119
bytecode_offset += instruction.body.op_size();
118120
}
119-
for segment in self.consts_info.segments.values() {
121+
for segment in self.segments_info.const_segments.values() {
120122
writeln!(f, "ret; // {bytecode_offset}")?;
121123
bytecode_offset += 1;
122124
for value in &segment.values {
123125
writeln!(f, "dw {value}; // {bytecode_offset}")?;
124126
bytecode_offset += 1;
125127
}
126128
}
129+
for segment in self.segments_info.utility_segments.values() {
130+
for instruction in &segment.instructions {
131+
writeln!(f, "{instruction}; // {bytecode_offset}")?;
132+
bytecode_offset += instruction.body.op_size();
133+
}
134+
}
127135
} else {
128136
for instruction in &self.instructions {
129137
writeln!(f, "{instruction};")?;
130138
}
131-
for segment in self.consts_info.segments.values() {
139+
for segment in self.segments_info.const_segments.values() {
132140
writeln!(f, "ret;")?;
133141
for value in &segment.values {
134142
writeln!(f, "dw {value};")?;
135143
}
136144
}
145+
for segment in self.segments_info.utility_segments.values() {
146+
for instruction in &segment.instructions {
147+
writeln!(f, "{instruction};")?;
148+
}
149+
}
137150
}
138151
Ok(())
139152
}
@@ -166,10 +179,13 @@ impl CairoProgram {
166179
else {
167180
panic!("`ret` instruction should be a single word.")
168181
};
169-
for segment in self.consts_info.segments.values() {
182+
for segment in self.segments_info.const_segments.values() {
170183
bytecode.push(ret_bytecode.clone());
171184
bytecode.extend(segment.values.clone());
172185
}
186+
for segment in self.segments_info.utility_segments.values() {
187+
bytecode.extend(segment.instructions.iter().flat_map(|inst| inst.assemble().encode()));
188+
}
173189
for instruction in footer {
174190
assert!(
175191
instruction.hints.is_empty(),
@@ -238,14 +254,18 @@ pub struct CairoProgramDebugInfo {
238254

239255
/// The information about the constants used in the program.
240256
#[derive(Debug, Eq, PartialEq, Default, Clone)]
241-
pub struct ConstsInfo {
242-
pub segments: OrderedHashMap<u32, ConstSegment>,
257+
pub struct SegmentsInfo {
258+
pub const_segments: OrderedHashMap<u32, ConstSegment>,
243259
pub total_segments_size: usize,
244260

245261
/// Maps a circuit to its segment id.
246262
pub circuit_segments: OrderedHashMap<ConcreteTypeId, u32>,
263+
264+
/// Utility segments for code reuse.
265+
pub utility_segments: OrderedHashMap<UtilityId, UtilitySegment>,
247266
}
248-
impl ConstsInfo {
267+
268+
impl SegmentsInfo {
249269
/// Creates a new `ConstSegmentsInfo` from the given libfuncs.
250270
pub fn new<'a>(
251271
program_info: &ProgramRegistryInfo,
@@ -272,14 +292,14 @@ impl ConstsInfo {
272292
Ok(())
273293
};
274294

275-
let mut segments = OrderedHashMap::default();
295+
let mut const_segments = OrderedHashMap::default();
276296

277297
for id in libfunc_ids.clone() {
278298
if let CoreConcreteLibfunc::Const(ConstConcreteLibfunc::AsBox(as_box)) =
279299
program_info.registry.get_libfunc(id).unwrap()
280300
{
281301
add_const(
282-
&mut segments,
302+
&mut const_segments,
283303
as_box.segment_id,
284304
as_box.const_type.clone(),
285305
extract_const_value(program_info, &as_box.const_type).unwrap(),
@@ -288,18 +308,18 @@ impl ConstsInfo {
288308
}
289309

290310
// Check that the segments were declared in order and without holes.
291-
if segments
311+
if const_segments
292312
.keys()
293313
.enumerate()
294314
.any(|(i, segment_id)| i != segment_id.into_or_panic::<usize>())
295315
{
296316
return Err(CompilationError::ConstSegmentsOutOfOrder);
297317
}
298318

299-
let mut next_segment = segments.len() as u32;
319+
let mut next_segment = const_segments.len() as u32;
300320
let mut circuit_segments = OrderedHashMap::default();
301321

302-
for id in libfunc_ids {
322+
for id in libfunc_ids.clone() {
303323
if let CoreConcreteLibfunc::Circuit(CircuitConcreteLibfunc::GetDescriptor(libfunc)) =
304324
program_info.registry.get_libfunc(id).unwrap()
305325
{
@@ -314,19 +334,50 @@ impl ConstsInfo {
314334
push_offset(gate_offsets.output);
315335
}
316336

317-
add_const(&mut segments, next_segment, circ_ty.clone(), const_value)?;
337+
add_const(&mut const_segments, next_segment, circ_ty.clone(), const_value)?;
318338
circuit_segments.insert(circ_ty.clone(), next_segment);
319339
next_segment += 1;
320340
}
321341
}
322342

343+
let mut utility_instructions = Vec::default();
344+
345+
// Check for BoxFromTempStore usage
346+
let has_box_from_temp_store = libfunc_ids.into_iter().any(|id| {
347+
matches!(
348+
program_info.registry.get_libfunc(id),
349+
Ok(CoreConcreteLibfunc::Box(BoxConcreteLibfunc::FromTempStore(_)))
350+
)
351+
});
352+
353+
if has_box_from_temp_store {
354+
utility_instructions.push((
355+
UtilityId::BoxFromTempStore,
356+
casm! {
357+
call rel 2;
358+
ret;
359+
}
360+
.instructions,
361+
));
362+
}
363+
323364
let mut total_segments_size = 0;
324-
for (_, segment) in segments.iter_mut() {
365+
for (_, segment) in const_segments.iter_mut() {
325366
segment.segment_offset = total_segments_size;
326367
// Add 1 for the `ret` instruction.
327368
total_segments_size += 1 + segment.values.len();
328369
}
329-
Ok(Self { segments, total_segments_size, circuit_segments })
370+
371+
let utility_segments = utility_instructions
372+
.into_iter()
373+
.map(|(id, instructions)| {
374+
let segment = UtilitySegment { instructions, offset: total_segments_size };
375+
total_segments_size +=
376+
segment.instructions.iter().map(|i| i.body.op_size()).sum::<usize>();
377+
(id, segment)
378+
})
379+
.collect();
380+
Ok(Self { const_segments, total_segments_size, circuit_segments, utility_segments })
330381
}
331382
}
332383

@@ -341,6 +392,20 @@ pub struct ConstSegment {
341392
pub segment_offset: usize,
342393
}
343394

395+
/// The data for a single utility segment (code).
396+
#[derive(Debug, Eq, PartialEq, Default, Clone)]
397+
pub struct UtilitySegment {
398+
/// The instructions in the segment.
399+
pub instructions: Vec<Instruction>,
400+
/// Offset from start of segments
401+
pub offset: usize,
402+
}
403+
404+
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
405+
pub enum UtilityId {
406+
BoxFromTempStore,
407+
}
408+
344409
/// Gets a concrete type, if it is a const type returns a vector of the values to be stored in the
345410
/// const segment.
346411
fn extract_const_value(
@@ -639,17 +704,17 @@ pub fn compile(
639704
.max_bytecode_size
640705
.checked_sub(program_offset)
641706
.ok_or_else(|| Box::new(CompilationError::CodeSizeLimitExceeded))?;
642-
let consts_info = ConstsInfo::new(
707+
let segments_info = SegmentsInfo::new(
643708
program_info,
644709
program.libfunc_declarations.iter().map(|ld| &ld.id),
645710
&circuits_info.circuits,
646711
const_segments_max_size,
647712
)?;
648-
relocate_instructions(&relocations, &statement_offsets, &consts_info, &mut instructions);
713+
relocate_instructions(&relocations, &statement_offsets, &segments_info, &mut instructions);
649714

650715
Ok(CairoProgram {
651716
instructions,
652-
consts_info,
717+
segments_info,
653718
debug_info: CairoProgramDebugInfo { sierra_statement_info },
654719
})
655720
}

crates/cairo-lang-sierra-to-casm/src/compiler_test.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,93 @@ indoc! {"
760760
dw 5;
761761
"};
762762
"Get builtin costs with a const segment.")]
763+
#[test_case(indoc! {"
764+
type felt252 = felt252;
765+
type Box<felt252> = Box<felt252>;
766+
767+
libfunc felt252_const<42> = felt252_const<42>;
768+
libfunc store_temp<felt252> = store_temp<felt252>;
769+
libfunc drop<felt252> = drop<felt252>;
770+
libfunc box_from_temp_store<felt252> = box_from_temp_store<felt252>;
771+
libfunc store_temp<Box<felt252>> = store_temp<Box<felt252>>;
772+
773+
felt252_const<42>() -> ([0]);
774+
store_temp<felt252>([0]) -> ([0]); // Allocate and store the constant felt252
775+
drop<felt252>([0]) -> (); // Drop the stored value
776+
box_from_temp_store<felt252>() -> ([1]); // Compute address for a felt252
777+
store_temp<Box<felt252>>([1]) -> ([1]);
778+
return([1]);
779+
780+
test_program@0() -> (Box<felt252>);
781+
"},
782+
false,
783+
indoc! {"
784+
[ap + 0] = 42, ap++;
785+
call rel 5;
786+
[ap + 0] = [ap + -2] + -1, ap++;
787+
ret;
788+
call rel 2;
789+
ret;
790+
"};
791+
"box_from_temp_store with felt252 (size 1), after storing constant at [0].")]
792+
#[test_case(indoc! {"
793+
type u128 = u128;
794+
type felt252 = felt252;
795+
type core::integer::u256 = Struct<ut@core::integer::u256, u128, u128>;
796+
type Box<core::integer::u256> = Box<core::integer::u256>;
797+
798+
libfunc u128_const<0> = u128_const<0>;
799+
libfunc struct_construct<core::integer::u256> = struct_construct<core::integer::u256>;
800+
libfunc store_temp<core::integer::u256> = store_temp<core::integer::u256>;
801+
libfunc drop<core::integer::u256> = drop<core::integer::u256>;
802+
libfunc box_from_temp_store<core::integer::u256> = box_from_temp_store<core::integer::u256>;
803+
libfunc store_temp<Box<core::integer::u256>> = store_temp<Box<core::integer::u256>>;
804+
805+
u128_const<0>() -> ([0]);
806+
u128_const<0>() -> ([1]);
807+
struct_construct<core::integer::u256>([0], [1]) -> ([2]);
808+
store_temp<core::integer::u256>([2]) -> ([2]); // Store u256
809+
drop<core::integer::u256>([2]) -> (); // Drop the stored value
810+
box_from_temp_store<core::integer::u256>() -> ([3]); // Compute address for a u256
811+
store_temp<Box<core::integer::u256>>([3]) -> ([3]);
812+
return([3]);
813+
814+
test_program@0() -> (Box<core::integer::u256>);
815+
"},
816+
false,
817+
indoc! {"
818+
[ap + 0] = 0, ap++;
819+
[ap + 0] = 0, ap++;
820+
call rel 5;
821+
[ap + 0] = [ap + -2] + -2, ap++;
822+
ret;
823+
call rel 2;
824+
ret;
825+
"};
826+
"box_from_temp_store with u256 (size 2), after storing u256 at [0].")]
827+
#[test_case(indoc! {"
828+
type Unit = Struct<ut@Tuple>;
829+
type felt252 = felt252;
830+
type Box<Unit> = Box<Unit>;
831+
832+
libfunc box_from_temp_store<Unit> = box_from_temp_store<Unit>;
833+
libfunc store_temp<Box<Unit>> = store_temp<Box<Unit>>;
834+
835+
box_from_temp_store<Unit>() -> ([0]); // No prior allocation for size 0 unit
836+
store_temp<Box<Unit>>([0]) -> ([0]);
837+
return([0]);
838+
839+
test_program@0() -> (Box<Unit>);
840+
"},
841+
false,
842+
indoc! {"
843+
call rel 5;
844+
[ap + 0] = [ap + -2] + 0, ap++;
845+
ret;
846+
call rel 2;
847+
ret;
848+
"};
849+
"box_from_temp_store with unit type (size 0), no prior allocation.")]
763850
fn sierra_to_casm(sierra_code: &str, gas_usage_check: bool, expected_casm: &str) {
764851
let program = ProgramParser::new().parse(sierra_code).unwrap();
765852
let program_info = ProgramRegistryInfo::new(&program).unwrap();

0 commit comments

Comments
 (0)