1use core::panic;
2
3use cairo_lang_casm::assembler::AssembledCairoProgram;
4use cairo_lang_casm::builder::{CasmBuilder, Var};
5use cairo_lang_casm::cell_expression::CellExpression;
6use cairo_lang_casm::hints::ExternalHint;
7use cairo_lang_casm::instructions::Instruction;
8use cairo_lang_casm::{ap_change, casm, casm_build_extend, deref};
9use cairo_lang_sierra::extensions::bitwise::BitwiseType;
10use cairo_lang_sierra::extensions::circuit::{AddModType, MulModType};
11use cairo_lang_sierra::extensions::core::{CoreLibfunc, CoreType};
12use cairo_lang_sierra::extensions::ec::EcOpType;
13use cairo_lang_sierra::extensions::gas::GasBuiltinType;
14use cairo_lang_sierra::extensions::pedersen::PedersenType;
15use cairo_lang_sierra::extensions::poseidon::PoseidonType;
16use cairo_lang_sierra::extensions::range_check::{RangeCheck96Type, RangeCheckType};
17use cairo_lang_sierra::extensions::segment_arena::SegmentArenaType;
18use cairo_lang_sierra::extensions::starknet::syscalls::SystemType;
19use cairo_lang_sierra::extensions::{ConcreteType, NamedType};
20use cairo_lang_sierra::ids::{ConcreteTypeId, GenericTypeId};
21use cairo_lang_sierra::program::{
22 ConcreteTypeLongId, Function, Program as SierraProgram, StatementIdx,
23};
24use cairo_lang_sierra::program_registry::{ProgramRegistry, ProgramRegistryError};
25use cairo_lang_sierra_ap_change::ApChangeError;
26use cairo_lang_sierra_gas::CostError;
27use cairo_lang_sierra_to_casm::annotations::AnnotationError;
28use cairo_lang_sierra_to_casm::compiler::{CairoProgram, CompilationError, SierraToCasmConfig};
29use cairo_lang_sierra_to_casm::metadata::{
30 Metadata, MetadataComputationConfig, MetadataError, calc_metadata, calc_metadata_ap_change_only,
31};
32use cairo_lang_sierra_type_size::{TypeSizeMap, get_type_size_map};
33use cairo_lang_utils::casts::IntoOrPanic;
34use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
35use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
36use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
37use cairo_vm::types::builtin_name::BuiltinName;
38use itertools::zip_eq;
39use thiserror::Error;
40
41#[derive(Debug, Error)]
42pub enum BuildError {
43 #[error(
44 "Failed calculating gas usage, it is likely a call for `gas::withdraw_gas` is missing. \
45 Inner error: {0}"
46 )]
47 FailedGasCalculation(#[from] CostError),
48 #[error("Function with suffix `{suffix}` to run not found.")]
49 MissingFunction { suffix: String },
50 #[error(transparent)]
51 ProgramRegistryError(#[from] Box<ProgramRegistryError>),
52 #[error(transparent)]
53 SierraCompilationError(#[from] Box<CompilationError>),
54 #[error(transparent)]
55 ApChangeError(#[from] ApChangeError),
56}
57
58impl BuildError {
59 pub fn stmt_indices(&self) -> Vec<StatementIdx> {
60 match self {
61 BuildError::SierraCompilationError(err) => err.stmt_indices(),
62 _ => vec![],
63 }
64 }
65
66 pub fn is_ap_overflow_error(&self) -> bool {
67 let BuildError::SierraCompilationError(err) = self else {
68 return false;
69 };
70
71 let CompilationError::AnnotationError(AnnotationError::ApChangeError { ref error, .. }) =
72 **err
73 else {
74 return false;
75 };
76
77 *error == ap_change::ApChangeError::OffsetOverflow
78 }
79}
80
81pub struct RunnableBuilder {
83 sierra_program: SierraProgram,
85 metadata: Metadata,
87 sierra_program_registry: ProgramRegistry<CoreType, CoreLibfunc>,
89 type_sizes: TypeSizeMap,
91 casm_program: CairoProgram,
93 non_args_types: UnorderedHashSet<GenericTypeId>,
95}
96
97impl RunnableBuilder {
98 pub fn new(
100 sierra_program: SierraProgram,
101 metadata_config: Option<MetadataComputationConfig>,
102 ) -> Result<Self, BuildError> {
103 let gas_usage_check = metadata_config.is_some();
104 let metadata = create_metadata(&sierra_program, metadata_config)?;
105 let sierra_program_registry =
106 ProgramRegistry::<CoreType, CoreLibfunc>::new(&sierra_program)?;
107 let type_sizes = get_type_size_map(&sierra_program, &sierra_program_registry).unwrap();
108 let casm_program = cairo_lang_sierra_to_casm::compiler::compile(
109 &sierra_program,
110 &metadata,
111 SierraToCasmConfig { gas_usage_check, max_bytecode_size: usize::MAX },
112 )?;
113
114 Ok(Self {
115 sierra_program,
116 metadata,
117 sierra_program_registry,
118 type_sizes,
119 casm_program,
120 non_args_types: UnorderedHashSet::from_iter([
121 AddModType::ID,
122 BitwiseType::ID,
123 GasBuiltinType::ID,
124 EcOpType::ID,
125 MulModType::ID,
126 PedersenType::ID,
127 PoseidonType::ID,
128 RangeCheck96Type::ID,
129 RangeCheckType::ID,
130 SegmentArenaType::ID,
131 SystemType::ID,
132 ]),
133 })
134 }
135
136 pub fn sierra_program(&self) -> &SierraProgram {
138 &self.sierra_program
139 }
140
141 pub fn casm_program(&self) -> &CairoProgram {
143 &self.casm_program
144 }
145
146 pub fn metadata(&self) -> &Metadata {
148 &self.metadata
149 }
150
151 pub fn registry(&self) -> &ProgramRegistry<CoreType, CoreLibfunc> {
153 &self.sierra_program_registry
154 }
155
156 pub fn find_function(&self, name_suffix: &str) -> Result<&Function, BuildError> {
158 self.sierra_program
159 .funcs
160 .iter()
161 .find(|f| {
162 if let Some(name) = &f.id.debug_name { name.ends_with(name_suffix) } else { false }
163 })
164 .ok_or_else(|| BuildError::MissingFunction { suffix: name_suffix.to_owned() })
165 }
166
167 fn type_info(&self, ty: &ConcreteTypeId) -> &cairo_lang_sierra::extensions::types::TypeInfo {
169 self.sierra_program_registry.get_type(ty).unwrap().info()
170 }
171
172 pub fn type_long_id(&self, ty: &ConcreteTypeId) -> &ConcreteTypeLongId {
174 &self.type_info(ty).long_id
175 }
176
177 pub fn type_size(&self, ty: &ConcreteTypeId) -> i16 {
179 self.type_sizes[ty]
180 }
181
182 pub fn is_user_arg_type(&self, ty: &GenericTypeId) -> bool {
184 !self.non_args_types.contains(ty)
185 }
186
187 pub fn create_wrapper_info(
189 &self,
190 func: &Function,
191 config: EntryCodeConfig,
192 ) -> Result<CasmProgramWrapperInfo, BuildError> {
193 let (header, builtins) = self.create_entry_code(func, config)?;
194 Ok(CasmProgramWrapperInfo { header, builtins, footer: create_code_footer() })
195 }
196
197 pub fn assemble_function_program(
199 &self,
200 func: &Function,
201 config: EntryCodeConfig,
202 ) -> Result<(AssembledCairoProgram, Vec<BuiltinName>), BuildError> {
203 let info = self.create_wrapper_info(func, config)?;
204 let assembled_cairo_program = self.casm_program.assemble_ex(&info.header, &info.footer);
205 Ok((assembled_cairo_program, info.builtins))
206 }
207
208 fn create_entry_code(
221 &self,
222 func: &Function,
223 config: EntryCodeConfig,
224 ) -> Result<(Vec<Instruction>, Vec<BuiltinName>), BuildError> {
225 let param_types = self.generic_id_and_size_from_concrete(&func.signature.param_types);
226 let return_types = self.generic_id_and_size_from_concrete(&func.signature.ret_types);
227
228 let entry_point = func.entry_point.0;
229 let code_offset =
230 self.casm_program.debug_info.sierra_statement_info[entry_point].start_offset;
231
232 create_entry_code_from_params(¶m_types, &return_types, code_offset, config)
233 }
234
235 pub fn generic_id_and_size_from_concrete(
237 &self,
238 types: &[ConcreteTypeId],
239 ) -> Vec<(GenericTypeId, i16)> {
240 types
241 .iter()
242 .map(|pt| {
243 let info = self.type_info(pt);
244 let generic_id = &info.long_id.generic_id;
245 let size = self.type_sizes[pt];
246 (generic_id.clone(), size)
247 })
248 .collect()
249 }
250}
251
252#[derive(Clone, Debug)]
254pub struct EntryCodeConfig {
255 pub testing: bool,
265 pub allow_unsound: bool,
268}
269impl EntryCodeConfig {
270 pub fn testing() -> Self {
275 Self { testing: true, allow_unsound: true }
276 }
277
278 pub fn executable(allow_unsound: bool) -> Self {
280 Self { testing: false, allow_unsound }
281 }
282}
283
284pub struct CasmProgramWrapperInfo {
286 pub builtins: Vec<BuiltinName>,
288 pub header: Vec<Instruction>,
290 pub footer: Vec<Instruction>,
292}
293
294pub fn create_entry_code_from_params(
309 param_types: &[(GenericTypeId, i16)],
310 return_types: &[(GenericTypeId, i16)],
311 code_offset: usize,
312 config: EntryCodeConfig,
313) -> Result<(Vec<Instruction>, Vec<BuiltinName>), BuildError> {
314 let mut helper = EntryCodeHelper::new(config);
315
316 helper.process_builtins(param_types);
317 helper.process_params(param_types);
318 casm_build_extend!(helper.ctx, let () = call FUNCTION;);
319 helper.process_output(return_types);
320
321 if helper.has_post_calculation_loop {
322 helper.validate_segment_arena();
323 helper.update_builtins_as_locals();
324 }
325
326 if !helper.config.testing {
327 helper.process_builtins_output();
328 }
329
330 casm_build_extend! (helper.ctx, ret;);
331 helper.ctx.future_label("FUNCTION".into(), code_offset);
334 Ok((helper.ctx.build([]).instructions, helper.builtins))
335}
336
337struct EntryCodeHelper {
339 ctx: CasmBuilder,
340 config: EntryCodeConfig,
341 input_builtin_vars: OrderedHashMap<BuiltinName, Var>,
342 builtin_ty_to_vm_name: UnorderedHashMap<GenericTypeId, BuiltinName>,
343 builtins: Vec<BuiltinName>,
344 got_segment_arena: bool,
345 has_post_calculation_loop: bool,
346 local_exprs: Vec<CellExpression>,
347 output_builtin_vars: OrderedHashMap<BuiltinName, Var>,
348 emulated_builtins: UnorderedHashSet<GenericTypeId>,
349}
350
351impl EntryCodeHelper {
352 fn new(config: EntryCodeConfig) -> Self {
354 Self {
355 ctx: CasmBuilder::default(),
356 config,
357 input_builtin_vars: OrderedHashMap::default(),
358 builtin_ty_to_vm_name: UnorderedHashMap::default(),
359 builtins: vec![],
360 got_segment_arena: false,
361 has_post_calculation_loop: false,
362 local_exprs: vec![],
363 output_builtin_vars: OrderedHashMap::default(),
364 emulated_builtins: UnorderedHashSet::<_>::from_iter([SystemType::ID]),
365 }
366 }
367
368 fn process_builtins(&mut self, param_types: &[(GenericTypeId, i16)]) {
370 let mut builtin_offset = 3;
371 for (builtin_name, builtin_ty) in [
372 (BuiltinName::mul_mod, MulModType::ID),
373 (BuiltinName::add_mod, AddModType::ID),
374 (BuiltinName::range_check96, RangeCheck96Type::ID),
375 (BuiltinName::poseidon, PoseidonType::ID),
376 (BuiltinName::ec_op, EcOpType::ID),
377 (BuiltinName::bitwise, BitwiseType::ID),
378 (BuiltinName::range_check, RangeCheckType::ID),
379 (BuiltinName::pedersen, PedersenType::ID),
380 ] {
381 if param_types.iter().any(|(ty, _)| ty == &builtin_ty) {
382 self.input_builtin_vars.insert(
383 builtin_name,
384 self.ctx.add_var(CellExpression::Deref(deref!([fp - builtin_offset]))),
385 );
386 self.builtin_ty_to_vm_name.insert(builtin_ty, builtin_name);
387 builtin_offset += 1;
388 self.builtins.push(builtin_name);
389 }
390 }
391 if !self.config.testing {
392 let output_builtin_var =
393 self.ctx.add_var(CellExpression::Deref(deref!([fp - builtin_offset])));
394 self.input_builtin_vars.insert(BuiltinName::output, output_builtin_var);
395 self.builtins.push(BuiltinName::output);
396 }
397 self.builtins.reverse();
398 }
399
400 fn process_params(&mut self, param_types: &[(GenericTypeId, i16)]) {
402 self.got_segment_arena = param_types.iter().any(|(ty, _)| ty == &SegmentArenaType::ID);
403 self.has_post_calculation_loop = self.got_segment_arena && !self.config.testing;
404
405 if self.has_post_calculation_loop {
406 for name in self.input_builtin_vars.keys() {
407 if name != &BuiltinName::segment_arena {
408 casm_build_extend!(self.ctx, localvar local;);
409 self.local_exprs.push(self.ctx.get_value(local, false));
410 }
411 }
412 if !self.local_exprs.is_empty() {
413 casm_build_extend!(self.ctx, ap += self.local_exprs.len(););
414 }
415 }
416 if self.got_segment_arena {
417 casm_build_extend! {self.ctx,
418 tempvar segment_arena;
419 tempvar infos;
420 hint AllocSegment into {dst: segment_arena};
421 hint AllocSegment into {dst: infos};
422 const czero = 0;
423 tempvar zero = czero;
424 assert infos = *(segment_arena++);
426 assert zero = *(segment_arena++);
427 assert zero = *(segment_arena++);
428 }
429 self.input_builtin_vars.insert(BuiltinName::segment_arena, segment_arena);
431 self.builtin_ty_to_vm_name.insert(SegmentArenaType::ID, BuiltinName::segment_arena);
432 }
433 let mut unallocated_count = 0;
434 let mut param_index = 0;
435
436 let non_proof_signature_params =
439 if self.config.testing { param_types } else { ¶m_types[..(param_types.len() - 2)] };
440 for (generic_ty, ty_size) in non_proof_signature_params {
441 if let Some(name) = self.builtin_ty_to_vm_name.get(generic_ty).cloned() {
442 let var = self.input_builtin_vars[&name];
443 casm_build_extend!(self.ctx, tempvar _builtin = var;);
444 } else if self.emulated_builtins.contains(generic_ty) {
445 assert!(
446 self.config.allow_unsound,
447 "Cannot support emulated builtins if not configured to `allow_unsound`."
448 );
449 casm_build_extend! {self.ctx,
450 tempvar system;
451 hint AllocSegment into {dst: system};
452 };
453 unallocated_count += ty_size;
454 } else if self.config.testing {
455 if *ty_size > 0 {
456 casm_build_extend! {self.ctx,
457 tempvar first;
458 const param_index = param_index;
459 hint ExternalHint::WriteRunParam { index: param_index } into { dst: first };
460 };
461 for _ in 1..*ty_size {
462 casm_build_extend!(self.ctx, tempvar _cell;);
463 }
464 }
465 param_index += 1;
466 unallocated_count += ty_size;
467 } else if generic_ty == &GasBuiltinType::ID {
468 casm_build_extend! {self.ctx,
469 const max_gas = i64::MAX;
470 tempvar gas = max_gas;
471 };
472 } else {
473 unreachable!("Unexpected argument type: {:?}", generic_ty);
474 }
475 }
476 if !self.config.testing {
477 let output_ptr = self.input_builtin_vars[&BuiltinName::output];
478 casm_build_extend! { self.ctx,
479 tempvar input_start;
480 tempvar _input_end;
481 const param_index = 0;
482 hint ExternalHint::WriteRunParam { index: param_index } into { dst: input_start };
483 tempvar output_start = output_ptr;
484 tempvar output_end = output_ptr;
485 };
486 unallocated_count += 2;
487 }
488 if unallocated_count > 0 {
489 casm_build_extend!(self.ctx, ap += unallocated_count.into_or_panic::<usize>(););
490 }
491 }
492
493 fn process_output(&mut self, return_types: &[(GenericTypeId, i16)]) {
495 let mut unprocessed_return_size = return_types.iter().map(|(_, size)| size).sum::<i16>();
496 let mut next_unprocessed_deref = || {
497 let deref_cell = CellExpression::Deref(deref!([ap - unprocessed_return_size]));
498 assert!(unprocessed_return_size > 0);
499 unprocessed_return_size -= 1;
500 deref_cell
501 };
502 let non_proof_return_types = if self.config.testing {
505 return_types
506 } else {
507 &return_types[..(return_types.len() - 1)]
508 };
509 for (ret_ty, size) in non_proof_return_types {
510 if let Some(name) = self.builtin_ty_to_vm_name.get(ret_ty) {
511 self.output_builtin_vars.insert(*name, self.ctx.add_var(next_unprocessed_deref()));
512 } else if self.config.testing {
513 for _ in 0..*size {
514 next_unprocessed_deref();
515 }
516 } else if self.emulated_builtins.contains(ret_ty) {
517 assert!(
518 self.config.allow_unsound,
519 "Cannot support emulated builtins if not configured to `allow_unsound`."
520 );
521 let _ = next_unprocessed_deref();
522 } else {
523 assert_eq!(ret_ty, &GasBuiltinType::ID);
524 let _ = next_unprocessed_deref();
525 }
526 }
527 if !self.config.testing {
528 let (ret_ty, size) = return_types.last().unwrap();
529 let opt_panic_indicator = match *size {
530 2 => None,
531 3 => Some(self.ctx.add_var(next_unprocessed_deref())),
532 _ => panic!("Unexpected output type: {:?}", ret_ty.0),
533 };
534 let ptr_start = self.ctx.add_var(next_unprocessed_deref());
537 let ptr_end = self.ctx.add_var(next_unprocessed_deref());
540 if let Some(panic_indicator) = opt_panic_indicator {
541 casm_build_extend! {self.ctx,
542 const czero = 0;
543 tempvar zero = czero;
544 hint ExternalHint::AddMarker { start: ptr_start, end: ptr_end };
545 assert zero = panic_indicator;
546 };
547 }
548 self.output_builtin_vars.insert(BuiltinName::output, ptr_end);
549 }
550 assert_eq!(unprocessed_return_size, 0);
551 assert_eq!(self.input_builtin_vars.len(), self.output_builtin_vars.len());
552 if self.has_post_calculation_loop {
553 for (cell, local_expr) in zip_eq(
555 self.output_builtin_vars.iter().filter_map(non_segment_arena_var).copied(),
556 &self.local_exprs,
557 ) {
558 let local_cell = self.ctx.add_var(local_expr.clone());
559 casm_build_extend!(self.ctx, assert local_cell = cell;);
560 }
561 }
562 }
563
564 fn validate_segment_arena(&mut self) {
566 let segment_arena =
567 self.output_builtin_vars.swap_remove(&BuiltinName::segment_arena).unwrap();
568 casm_build_extend! {self.ctx,
569 tempvar n_segments = segment_arena[-2];
570 tempvar n_finalized = segment_arena[-1];
571 assert n_segments = n_finalized;
572 jump STILL_LEFT_PRE if n_segments != 0;
573 rescope{};
574 jump DONE_VALIDATION;
575 STILL_LEFT_PRE:
576 const one = 1;
577 tempvar infos = segment_arena[-3];
578 tempvar remaining_segments = n_segments - one;
579 rescope{infos = infos, remaining_segments = remaining_segments};
580 LOOP_START:
581 jump STILL_LEFT_LOOP if remaining_segments != 0;
582 rescope{};
583 jump DONE_VALIDATION;
584 STILL_LEFT_LOOP:
585 tempvar prev_end = infos[1];
586 tempvar curr_start = infos[3];
587 const one = 1;
588 let expected_curr_start = prev_end + one;
589 hint ExternalHint::AddRelocationRule { src: curr_start, dst: expected_curr_start };
590 assert curr_start = prev_end + one;
591 const three = 3;
592 tempvar next_infos = infos + three;
593 tempvar next_remaining_segments = remaining_segments - one;
594 rescope{infos = next_infos, remaining_segments = next_remaining_segments};
595 #{ steps = 0; }
596 jump LOOP_START;
597 DONE_VALIDATION:
598 };
599 }
600
601 fn update_builtins_as_locals(&mut self) {
603 for (var, local_expr) in zip_eq(
604 self.output_builtin_vars.iter_mut().filter_map(non_segment_arena_var),
605 &self.local_exprs,
606 ) {
607 *var = self.ctx.add_var(local_expr.clone());
608 }
609 }
610
611 fn process_builtins_output(&mut self) {
613 for name in &self.builtins {
614 if let Some(var) = self.output_builtin_vars.swap_remove(name) {
615 casm_build_extend!(self.ctx, tempvar cell = var;);
616 }
617 }
618 assert!(self.output_builtin_vars.is_empty());
619 }
620}
621
622fn non_segment_arena_var<T>((name, var): (&BuiltinName, T)) -> Option<T> {
624 if *name == BuiltinName::segment_arena { None } else { Some(var) }
625}
626
627pub fn create_code_footer() -> Vec<Instruction> {
629 casm! {
630 ret;
633 }
634 .instructions
635}
636
637fn create_metadata(
639 sierra_program: &SierraProgram,
640 metadata_config: Option<MetadataComputationConfig>,
641) -> Result<Metadata, BuildError> {
642 if let Some(metadata_config) = metadata_config {
643 calc_metadata(sierra_program, metadata_config)
644 } else {
645 calc_metadata_ap_change_only(sierra_program)
646 }
647 .map_err(|err| match err {
648 MetadataError::ApChangeError(err) => BuildError::ApChangeError(err),
649 MetadataError::CostError(err) => BuildError::FailedGasCalculation(err),
650 })
651}