Skip to content

Commit a7963ad

Browse files
authored
Merge branch 'main' into patch-16
2 parents b393a16 + 57881b1 commit a7963ad

File tree

11 files changed

+1146
-57
lines changed

11 files changed

+1146
-57
lines changed

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use cairo_lang_sierra::extensions::const_type::ConstConcreteLibfunc;
1515
use cairo_lang_sierra::extensions::core::CoreConcreteLibfunc::{self, *};
1616
use cairo_lang_sierra::extensions::coupon::CouponConcreteLibfunc;
1717
use cairo_lang_sierra::extensions::ec::EcConcreteLibfunc;
18-
use cairo_lang_sierra::extensions::enm::EnumConcreteLibfunc;
18+
use cairo_lang_sierra::extensions::enm::{EnumBoxedMatchConcreteLibfunc, EnumConcreteLibfunc};
1919
use cairo_lang_sierra::extensions::felt252::{
2020
Felt252BinaryOperationConcrete, Felt252BinaryOperator, Felt252Concrete,
2121
};
@@ -32,6 +32,7 @@ use cairo_lang_sierra::extensions::int::unsigned256::Uint256Concrete;
3232
use cairo_lang_sierra::extensions::int::unsigned512::Uint512Concrete;
3333
use cairo_lang_sierra::extensions::int::{IntMulTraits, IntOperator};
3434
use cairo_lang_sierra::extensions::is_zero::IsZeroTraits;
35+
use cairo_lang_sierra::extensions::lib_func::SignatureOnlyConcreteLibfunc;
3536
use cairo_lang_sierra::extensions::mem::MemConcreteLibfunc;
3637
use cairo_lang_sierra::extensions::nullable::NullableConcreteLibfunc;
3738
use cairo_lang_sierra::extensions::pedersen::PedersenConcreteLibfunc;
@@ -293,8 +294,19 @@ pub fn core_libfunc_ap_change<InfoProvider: InvocationApChangeInfoProvider>(
293294
1 | 2 => vec![ApChange::Known(0)],
294295
_ => vec![ApChange::Known(1)],
295296
},
296-
EnumConcreteLibfunc::Match(libfunc) | EnumConcreteLibfunc::SnapshotMatch(libfunc) => {
297-
vec![ApChange::Known(0); libfunc.signature.branch_signatures.len()]
297+
EnumConcreteLibfunc::Match(SignatureOnlyConcreteLibfunc { signature, .. })
298+
| EnumConcreteLibfunc::SnapshotMatch(SignatureOnlyConcreteLibfunc {
299+
signature, ..
300+
}) => {
301+
vec![ApChange::Known(0); signature.branch_signatures.len()]
302+
}
303+
EnumConcreteLibfunc::BoxedMatch(EnumBoxedMatchConcreteLibfunc {
304+
signature, ..
305+
}) => {
306+
// For zero or one variants, no instructions are generated (no branching needed)
307+
// For other cases, we need 1 AP change for loading the variant selector
308+
let n = signature.branch_signatures.len();
309+
if n <= 1 { vec![ApChange::Known(0); n] } else { vec![ApChange::Known(1); n] }
298310
}
299311
},
300312
Struct(libfunc) => match libfunc {

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use cairo_lang_sierra::extensions::const_type::ConstConcreteLibfunc;
1616
use cairo_lang_sierra::extensions::core::CoreConcreteLibfunc::{self, *};
1717
use cairo_lang_sierra::extensions::coupon::CouponConcreteLibfunc;
1818
use cairo_lang_sierra::extensions::ec::EcConcreteLibfunc;
19-
use cairo_lang_sierra::extensions::enm::EnumConcreteLibfunc;
19+
use cairo_lang_sierra::extensions::enm::{EnumBoxedMatchConcreteLibfunc, EnumConcreteLibfunc};
2020
use cairo_lang_sierra::extensions::felt252::{
2121
Felt252BinaryOperationConcrete, Felt252BinaryOperator, Felt252Concrete,
2222
};
@@ -411,6 +411,24 @@ pub fn core_libfunc_cost(
411411
.collect_vec(),
412412
}
413413
}
414+
EnumConcreteLibfunc::BoxedMatch(EnumBoxedMatchConcreteLibfunc {
415+
signature, ..
416+
}) => {
417+
// BoxedMatch needs to load the variant selector with a double-deref (1 step)
418+
// plus the regular match cost - but only when branching is actually needed
419+
let n = signature.branch_signatures.len();
420+
match n {
421+
0 => vec![],
422+
1 => vec![ConstCost::default().into()], // No branching needed for single
423+
// variant
424+
2 => vec![ConstCost::steps(2).into(); 2],
425+
_ => chain!(
426+
iter::once(ConstCost::steps(2).into()),
427+
itertools::repeat_n(ConstCost::steps(3).into(), n - 1)
428+
)
429+
.collect_vec(),
430+
}
431+
}
414432
},
415433
Struct(
416434
StructConcreteLibfunc::Construct(_)

crates/cairo-lang-sierra-generator/src/utils.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,16 +253,24 @@ fn const_type_id(
253253
))
254254
}
255255

256+
/// Returns the appropriate libfunc ID for matching on an enum type.
257+
///
258+
/// Selects between `enum_match`, `enum_snapshot_match`, or `enum_boxed_match` based on
259+
/// whether the type is a snapshot or a boxed type.
256260
pub fn match_enum_libfunc_id(
257261
db: &dyn Database,
258262
ty: cairo_lang_sierra::ids::ConcreteTypeId,
259263
) -> Maybe<cairo_lang_sierra::ids::ConcreteLibfuncId> {
260264
let long_id = &db.get_type_info(ty.clone())?.long_id;
261265
let is_snapshot = long_id.generic_id == SnapshotType::id();
266+
let is_box = long_id.generic_id == BoxType::id();
262267
Ok(if is_snapshot {
263268
let concrete_enum_type =
264269
extract_matches!(&long_id.generic_args[0], GenericArg::Type).clone();
265270
get_libfunc_id_with_generic_arg(db, "enum_snapshot_match", concrete_enum_type)
271+
} else if is_box {
272+
let inner_ty = extract_matches!(&long_id.generic_args[0], GenericArg::Type).clone();
273+
get_libfunc_id_with_generic_arg(db, "enum_boxed_match", inner_ty)
266274
} else {
267275
get_libfunc_id_with_generic_arg(db, "enum_match", ty)
268276
})

crates/cairo-lang-sierra-to-casm/src/invocations/enm.rs

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
use std::iter;
2+
3+
use cairo_lang_casm::ap_change::ApplyApChange;
14
use cairo_lang_casm::builder::CasmBuilder;
2-
use cairo_lang_casm::cell_expression::CellExpression;
3-
use cairo_lang_casm::operand::CellRef;
5+
use cairo_lang_casm::cell_expression::{CellExpression, CellOperator};
6+
use cairo_lang_casm::instructions::Instruction;
7+
use cairo_lang_casm::operand::{CellRef, DerefOrImmediate, Register};
48
use cairo_lang_casm::{casm, casm_build_extend, casm_extend};
59
use cairo_lang_sierra::extensions::ConcreteLibfunc;
6-
use cairo_lang_sierra::extensions::enm::{EnumConcreteLibfunc, EnumInitConcreteLibfunc};
10+
use cairo_lang_sierra::extensions::enm::{
11+
EnumBoxedMatchConcreteLibfunc, EnumConcreteLibfunc, EnumInitConcreteLibfunc,
12+
};
713
use cairo_lang_sierra::ids::ConcreteTypeId;
814
use cairo_lang_sierra::program::{BranchInfo, BranchTarget};
915
use cairo_lang_utils::try_extract_matches;
@@ -33,6 +39,7 @@ pub fn build(
3339
EnumConcreteLibfunc::Match(_) | EnumConcreteLibfunc::SnapshotMatch(_) => {
3440
build_enum_match(builder)
3541
}
42+
EnumConcreteLibfunc::BoxedMatch(libfunc) => build_enum_boxed_match(libfunc, builder),
3643
}
3744
}
3845

@@ -230,8 +237,20 @@ fn build_enum_match_short(
230237
Item = impl ExactSizeIterator<Item = ReferenceExpression>,
231238
>,
232239
) -> Result<CompiledInvocation, InvocationError> {
233-
let mut instructions = Vec::new();
240+
build_enum_match_short_ex(builder, variant_selector, output_expressions, Vec::with_capacity(1))
241+
}
242+
243+
/// Extended version of `build_enum_match_short` that allows prepending instructions.
244+
fn build_enum_match_short_ex(
245+
builder: CompiledInvocationBuilder<'_>,
246+
variant_selector: CellRef,
247+
output_expressions: impl ExactSizeIterator<
248+
Item = impl ExactSizeIterator<Item = ReferenceExpression>,
249+
>,
250+
mut instructions: Vec<Instruction>,
251+
) -> Result<CompiledInvocation, InvocationError> {
234252
let mut relocations = Vec::new();
253+
let base_instruction_count = instructions.len();
235254

236255
// First branch is fallthrough. If there is only one branch, this `match` statement is
237256
// translated to nothing in Casm.
@@ -245,7 +264,7 @@ fn build_enum_match_short(
245264

246265
instructions.extend(casm! { jmp rel 0 if variant_selector != 0; }.instructions);
247266
relocations.push(RelocationEntry {
248-
instruction_idx: 0,
267+
instruction_idx: base_instruction_count,
249268
relocation: Relocation::RelativeStatementId(statement_id),
250269
});
251270
}
@@ -284,12 +303,32 @@ fn build_enum_match_long(
284303
output_expressions: impl ExactSizeIterator<
285304
Item = impl ExactSizeIterator<Item = ReferenceExpression>,
286305
>,
306+
) -> Result<CompiledInvocation, InvocationError> {
307+
let expected_instruction_count = builder.invocation.branches.len() + 1;
308+
build_enum_match_long_ex(
309+
builder,
310+
variant_selector,
311+
output_expressions,
312+
Vec::with_capacity(expected_instruction_count),
313+
)
314+
}
315+
316+
/// Extended version of `build_enum_match_long` that allows prepending instructions.
317+
fn build_enum_match_long_ex(
318+
builder: CompiledInvocationBuilder<'_>,
319+
variant_selector: CellRef,
320+
output_expressions: impl ExactSizeIterator<
321+
Item = impl ExactSizeIterator<Item = ReferenceExpression>,
322+
>,
323+
mut instructions: Vec<Instruction>,
287324
) -> Result<CompiledInvocation, InvocationError> {
288325
let target_statement_ids = builder.invocation.branches[1..].iter().map(|b| match b {
289326
BranchInfo { target: BranchTarget::Statement(stmnt_id), .. } => *stmnt_id,
290327
_ => panic!("malformed invocation"),
291328
});
292329

330+
let base_instruction_count = instructions.len();
331+
293332
// The first instruction is the jmp to the relevant index in the jmp table.
294333
let mut ctx = casm! { jmp rel variant_selector; };
295334
let mut relocations = Vec::new();
@@ -300,12 +339,13 @@ fn build_enum_match_long(
300339
// Add the jump instruction to the relevant target.
301340
casm_extend!(ctx, jmp rel 0;);
302341
relocations.push(RelocationEntry {
303-
instruction_idx: i + 1,
342+
instruction_idx: base_instruction_count + i + 1,
304343
relocation: Relocation::RelativeStatementId(stmnt_id),
305344
});
306345
}
307346

308-
Ok(builder.build(ctx.instructions, relocations, output_expressions))
347+
instructions.extend(ctx.instructions);
348+
Ok(builder.build(instructions, relocations, output_expressions))
309349
}
310350

311351
/// A struct representing an actual enum value in the Sierra program.
@@ -363,3 +403,89 @@ fn get_enum_size(
363403
) -> Option<i16> {
364404
Some(program_info.type_sizes.get(concrete_enum_type)?.to_owned())
365405
}
406+
407+
/// Generates CASM instructions for matching a boxed enum into individual boxed variants.
408+
///
409+
/// This function takes a boxed enum (stored in a single memory cell containing the address)
410+
/// and branches based on the variant selector, returning a box pointing to the appropriate variant.
411+
fn build_enum_boxed_match(
412+
libfunc: &EnumBoxedMatchConcreteLibfunc,
413+
builder: CompiledInvocationBuilder<'_>,
414+
) -> Result<CompiledInvocation, InvocationError> {
415+
let num_branches = builder.invocation.branches.len();
416+
417+
// Handle zero variants case - no instructions needed for uninhabited type
418+
if num_branches == 0 {
419+
return Ok(builder.build(
420+
vec![],
421+
vec![],
422+
iter::empty::<iter::Empty<ReferenceExpression>>(),
423+
));
424+
}
425+
426+
let [cell] = builder.try_get_single_cells()?;
427+
let cell_ref = cell.to_deref().ok_or(InvocationError::InvalidReferenceExpressionForArgument)?;
428+
429+
// Calculate the size of each variant
430+
let mut variant_sizes: Vec<i16> = Vec::new();
431+
for variant_ty in &libfunc.variants {
432+
let variant_size = *builder
433+
.program_info
434+
.type_sizes
435+
.get(variant_ty)
436+
.ok_or(InvocationError::InvalidReferenceExpressionForArgument)?;
437+
variant_sizes.push(variant_size);
438+
}
439+
440+
// The enum size is 1 (selector) + max(variant_sizes)
441+
let max_variant_size = variant_sizes.iter().max().copied().unwrap_or(0);
442+
443+
let output_cellref = if num_branches > 1 {
444+
let mut adjusted = cell_ref;
445+
assert!(adjusted.apply_known_ap_change(1));
446+
adjusted
447+
} else {
448+
cell_ref
449+
};
450+
451+
// For each branch, we need to create a reference to Box<Variant>
452+
// The variant data starts at offset 1 + padding to skip to the variant
453+
let output_expressions_for_branches = variant_sizes.iter().map(|&variant_size| {
454+
// Calculate padding: the variant is right-aligned in the enum's value space
455+
let padding = max_variant_size - variant_size;
456+
// The box should point to: base_addr + 1 (selector) + padding
457+
let variant_offset = 1 + padding;
458+
let variant_box = CellExpression::BinOp {
459+
op: CellOperator::Add,
460+
a: output_cellref,
461+
b: DerefOrImmediate::Immediate(variant_offset.into()),
462+
};
463+
464+
vec![ReferenceExpression::from_cell(variant_box)].into_iter()
465+
});
466+
467+
// For single variant enums, no branching is needed - just return the variant box
468+
if num_branches == 1 {
469+
return Ok(builder.build(vec![], vec![], output_expressions_for_branches));
470+
}
471+
472+
// Load the variant selector into a temporary - this is a double deref
473+
let instructions = casm! { [ap] = [[&cell_ref]], ap++; }.instructions;
474+
let variant_selector_cell = CellRef { register: Register::AP, offset: -1 };
475+
476+
if num_branches == 2 {
477+
build_enum_match_short_ex(
478+
builder,
479+
variant_selector_cell,
480+
output_expressions_for_branches.into_iter().map(|v| v.into_iter()),
481+
instructions,
482+
)
483+
} else {
484+
build_enum_match_long_ex(
485+
builder,
486+
variant_selector_cell,
487+
output_expressions_for_branches.into_iter().map(|v| v.into_iter()),
488+
instructions,
489+
)
490+
}
491+
}

0 commit comments

Comments
 (0)