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::ProgramRegistryInfo;
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 program_info: ProgramRegistryInfo,
89 casm_program: CairoProgram,
91 non_args_types: UnorderedHashSet<GenericTypeId>,
93}
94
95impl RunnableBuilder {
96 pub fn new(
98 sierra_program: SierraProgram,
99 metadata_config: Option<MetadataComputationConfig>,
100 ) -> Result<Self, BuildError> {
101 let program_info = ProgramRegistryInfo::new(&sierra_program)?;
102 let gas_usage_check = metadata_config.is_some();
103 let metadata = create_metadata(&sierra_program, &program_info, metadata_config)?;
104 let casm_program = cairo_lang_sierra_to_casm::compiler::compile(
105 &sierra_program,
106 &program_info,
107 &metadata,
108 SierraToCasmConfig { gas_usage_check, max_bytecode_size: usize::MAX },
109 )?;
110
111 Ok(Self {
112 sierra_program,
113 metadata,
114 program_info,
115 casm_program,
116 non_args_types: UnorderedHashSet::from_iter([
117 AddModType::ID,
118 BitwiseType::ID,
119 GasBuiltinType::ID,
120 EcOpType::ID,
121 MulModType::ID,
122 PedersenType::ID,
123 PoseidonType::ID,
124 RangeCheck96Type::ID,
125 RangeCheckType::ID,
126 SegmentArenaType::ID,
127 SystemType::ID,
128 ]),
129 })
130 }
131
132 pub fn sierra_program(&self) -> &SierraProgram {
134 &self.sierra_program
135 }
136
137 pub fn casm_program(&self) -> &CairoProgram {
139 &self.casm_program
140 }
141
142 pub fn metadata(&self) -> &Metadata {
144 &self.metadata
145 }
146
147 pub fn registry(&self) -> &ProgramRegistry<CoreType, CoreLibfunc> {
149 &self.program_info.registry
150 }
151
152 pub fn find_function(&self, name_suffix: &str) -> Result<&Function, BuildError> {
154 self.sierra_program
155 .funcs
156 .iter()
157 .find(|f| {
158 if let Some(name) = &f.id.debug_name { name.ends_with(name_suffix) } else { false }
159 })
160 .ok_or_else(|| BuildError::MissingFunction { suffix: name_suffix.to_owned() })
161 }
162
163 fn type_info(&self, ty: &ConcreteTypeId) -> &cairo_lang_sierra::extensions::types::TypeInfo {
165 self.program_info.registry.get_type(ty).unwrap().info()
166 }
167
168 pub fn type_long_id(&self, ty: &ConcreteTypeId) -> &ConcreteTypeLongId {
170 &self.type_info(ty).long_id
171 }
172
173 pub fn type_size(&self, ty: &ConcreteTypeId) -> i16 {
175 self.program_info.type_sizes[ty]
176 }
177
178 pub fn is_user_arg_type(&self, ty: &GenericTypeId) -> bool {
180 !self.non_args_types.contains(ty)
181 }
182
183 pub fn create_wrapper_info(
185 &self,
186 func: &Function,
187 config: EntryCodeConfig,
188 ) -> Result<CasmProgramWrapperInfo, BuildError> {
189 let (header, builtins) = self.create_entry_code(func, config)?;
190 Ok(CasmProgramWrapperInfo { header, builtins, footer: create_code_footer() })
191 }
192
193 pub fn assemble_function_program(
195 &self,
196 func: &Function,
197 config: EntryCodeConfig,
198 ) -> Result<(AssembledCairoProgram, Vec<BuiltinName>), BuildError> {
199 let info = self.create_wrapper_info(func, config)?;
200 let assembled_cairo_program = self.casm_program.assemble_ex(&info.header, &info.footer);
201 Ok((assembled_cairo_program, info.builtins))
202 }
203
204 fn create_entry_code(
217 &self,
218 func: &Function,
219 config: EntryCodeConfig,
220 ) -> Result<(Vec<Instruction>, Vec<BuiltinName>), BuildError> {
221 let param_types = self.generic_id_and_size_from_concrete(&func.signature.param_types);
222 let return_types = self.generic_id_and_size_from_concrete(&func.signature.ret_types);
223
224 let entry_point = func.entry_point.0;
225 let code_offset =
226 self.casm_program.debug_info.sierra_statement_info[entry_point].start_offset;
227
228 create_entry_code_from_params(¶m_types, &return_types, code_offset, config)
229 }
230
231 pub fn generic_id_and_size_from_concrete(
233 &self,
234 types: &[ConcreteTypeId],
235 ) -> Vec<(GenericTypeId, i16)> {
236 types
237 .iter()
238 .map(|pt| {
239 let info = self.type_info(pt);
240 let generic_id = &info.long_id.generic_id;
241 let size = self.type_size(pt);
242 (generic_id.clone(), size)
243 })
244 .collect()
245 }
246}
247
248#[derive(Clone, Debug)]
250pub struct EntryCodeConfig {
251 pub testing: bool,
261 pub allow_unsound: bool,
264}
265impl EntryCodeConfig {
266 pub fn testing() -> Self {
271 Self { testing: true, allow_unsound: true }
272 }
273
274 pub fn executable(allow_unsound: bool) -> Self {
276 Self { testing: false, allow_unsound }
277 }
278}
279
280pub struct CasmProgramWrapperInfo {
282 pub builtins: Vec<BuiltinName>,
284 pub header: Vec<Instruction>,
286 pub footer: Vec<Instruction>,
288}
289
290pub fn create_entry_code_from_params(
305 param_types: &[(GenericTypeId, i16)],
306 return_types: &[(GenericTypeId, i16)],
307 code_offset: usize,
308 config: EntryCodeConfig,
309) -> Result<(Vec<Instruction>, Vec<BuiltinName>), BuildError> {
310 let mut helper = EntryCodeHelper::new(config);
311
312 helper.process_builtins(param_types);
313 helper.process_params(param_types);
314 casm_build_extend!(helper.ctx, let () = call FUNCTION;);
315 helper.process_output(return_types);
316
317 if helper.has_post_calculation_loop {
318 helper.validate_segment_arena();
319 helper.update_builtins_as_locals();
320 }
321
322 if !helper.config.testing {
323 helper.process_builtins_output();
324 }
325
326 casm_build_extend! (helper.ctx, ret;);
327 helper.ctx.future_label("FUNCTION", code_offset);
330 Ok((helper.ctx.build([]).instructions, helper.builtins))
331}
332
333struct EntryCodeHelper {
335 ctx: CasmBuilder,
336 config: EntryCodeConfig,
337 input_builtin_vars: OrderedHashMap<BuiltinName, Var>,
338 builtin_ty_to_vm_name: UnorderedHashMap<GenericTypeId, BuiltinName>,
339 builtins: Vec<BuiltinName>,
340 got_segment_arena: bool,
341 has_post_calculation_loop: bool,
342 local_exprs: Vec<CellExpression>,
343 output_builtin_vars: OrderedHashMap<BuiltinName, Var>,
344 emulated_builtins: UnorderedHashSet<GenericTypeId>,
345}
346
347impl EntryCodeHelper {
348 fn new(config: EntryCodeConfig) -> Self {
350 Self {
351 ctx: CasmBuilder::default(),
352 config,
353 input_builtin_vars: OrderedHashMap::default(),
354 builtin_ty_to_vm_name: UnorderedHashMap::default(),
355 builtins: vec![],
356 got_segment_arena: false,
357 has_post_calculation_loop: false,
358 local_exprs: vec![],
359 output_builtin_vars: OrderedHashMap::default(),
360 emulated_builtins: UnorderedHashSet::<_>::from_iter([SystemType::ID]),
361 }
362 }
363
364 fn process_builtins(&mut self, param_types: &[(GenericTypeId, i16)]) {
366 let mut builtin_offset = 3;
367 for (builtin_name, builtin_ty) in [
368 (BuiltinName::mul_mod, MulModType::ID),
369 (BuiltinName::add_mod, AddModType::ID),
370 (BuiltinName::range_check96, RangeCheck96Type::ID),
371 (BuiltinName::poseidon, PoseidonType::ID),
372 (BuiltinName::ec_op, EcOpType::ID),
373 (BuiltinName::bitwise, BitwiseType::ID),
374 (BuiltinName::range_check, RangeCheckType::ID),
375 (BuiltinName::pedersen, PedersenType::ID),
376 ] {
377 if param_types.iter().any(|(ty, _)| ty == &builtin_ty) {
378 self.input_builtin_vars.insert(
379 builtin_name,
380 self.ctx.add_var(CellExpression::Deref(deref!([fp - builtin_offset]))),
381 );
382 self.builtin_ty_to_vm_name.insert(builtin_ty, builtin_name);
383 builtin_offset += 1;
384 self.builtins.push(builtin_name);
385 }
386 }
387 if !self.config.testing {
388 let output_builtin_var =
389 self.ctx.add_var(CellExpression::Deref(deref!([fp - builtin_offset])));
390 self.input_builtin_vars.insert(BuiltinName::output, output_builtin_var);
391 self.builtins.push(BuiltinName::output);
392 }
393 self.builtins.reverse();
394 }
395
396 fn process_params(&mut self, param_types: &[(GenericTypeId, i16)]) {
398 self.got_segment_arena = param_types.iter().any(|(ty, _)| ty == &SegmentArenaType::ID);
399 self.has_post_calculation_loop = self.got_segment_arena && !self.config.testing;
400
401 if self.has_post_calculation_loop {
402 for name in self.input_builtin_vars.keys() {
403 if name != &BuiltinName::segment_arena {
404 casm_build_extend!(self.ctx, localvar local;);
405 self.local_exprs.push(self.ctx.get_value(local, false));
406 }
407 }
408 if !self.local_exprs.is_empty() {
409 casm_build_extend!(self.ctx, ap += self.local_exprs.len(););
410 }
411 }
412 if self.got_segment_arena {
413 casm_build_extend! {self.ctx,
414 tempvar segment_arena;
415 tempvar infos;
416 hint AllocSegment into {dst: segment_arena};
417 hint AllocSegment into {dst: infos};
418 const czero = 0;
419 tempvar zero = czero;
420 assert infos = *(segment_arena++);
422 assert zero = *(segment_arena++);
423 assert zero = *(segment_arena++);
424 }
425 self.input_builtin_vars.insert(BuiltinName::segment_arena, segment_arena);
427 self.builtin_ty_to_vm_name.insert(SegmentArenaType::ID, BuiltinName::segment_arena);
428 }
429 let mut unallocated_count = 0;
430 let mut param_index = 0;
431
432 let non_proof_signature_params =
435 if self.config.testing { param_types } else { ¶m_types[..(param_types.len() - 2)] };
436 for (generic_ty, ty_size) in non_proof_signature_params {
437 if let Some(name) = self.builtin_ty_to_vm_name.get(generic_ty).cloned() {
438 let var = self.input_builtin_vars[&name];
439 casm_build_extend!(self.ctx, tempvar _builtin = var;);
440 } else if self.emulated_builtins.contains(generic_ty) {
441 assert!(
442 self.config.allow_unsound,
443 "Cannot support emulated builtins if not configured to `allow_unsound`."
444 );
445 casm_build_extend! {self.ctx,
446 tempvar system;
447 hint AllocSegment into {dst: system};
448 };
449 unallocated_count += ty_size;
450 } else if self.config.testing {
451 if *ty_size > 0 {
452 casm_build_extend! {self.ctx,
453 tempvar first;
454 const param_index = param_index;
455 hint ExternalHint::WriteRunParam { index: param_index } into { dst: first };
456 };
457 for _ in 1..*ty_size {
458 casm_build_extend!(self.ctx, tempvar _cell;);
459 }
460 }
461 param_index += 1;
462 unallocated_count += ty_size;
463 } else if generic_ty == &GasBuiltinType::ID {
464 casm_build_extend! {self.ctx,
465 const max_gas = i64::MAX;
466 tempvar gas = max_gas;
467 };
468 } else {
469 unreachable!("Unexpected argument type: {:?}", generic_ty);
470 }
471 }
472 if !self.config.testing {
473 let output_ptr = self.input_builtin_vars[&BuiltinName::output];
474 casm_build_extend! { self.ctx,
475 tempvar input_start;
476 tempvar _input_end;
477 const param_index = 0;
478 hint ExternalHint::WriteRunParam { index: param_index } into { dst: input_start };
479 tempvar output_start = output_ptr;
480 tempvar output_end = output_ptr;
481 };
482 unallocated_count += 2;
483 }
484 if unallocated_count > 0 {
485 casm_build_extend!(self.ctx, ap += unallocated_count.into_or_panic::<usize>(););
486 }
487 }
488
489 fn process_output(&mut self, return_types: &[(GenericTypeId, i16)]) {
491 let mut unprocessed_return_size = return_types.iter().map(|(_, size)| size).sum::<i16>();
492 let mut next_unprocessed_deref = || {
493 let deref_cell = CellExpression::Deref(deref!([ap - unprocessed_return_size]));
494 assert!(unprocessed_return_size > 0);
495 unprocessed_return_size -= 1;
496 deref_cell
497 };
498 let non_proof_return_types = if self.config.testing {
501 return_types
502 } else {
503 &return_types[..(return_types.len() - 1)]
504 };
505 for (ret_ty, size) in non_proof_return_types {
506 if let Some(name) = self.builtin_ty_to_vm_name.get(ret_ty) {
507 self.output_builtin_vars.insert(*name, self.ctx.add_var(next_unprocessed_deref()));
508 } else if self.config.testing {
509 for _ in 0..*size {
510 next_unprocessed_deref();
511 }
512 } else if self.emulated_builtins.contains(ret_ty) {
513 assert!(
514 self.config.allow_unsound,
515 "Cannot support emulated builtins if not configured to `allow_unsound`."
516 );
517 let _ = next_unprocessed_deref();
518 } else {
519 assert_eq!(ret_ty, &GasBuiltinType::ID);
520 let _ = next_unprocessed_deref();
521 }
522 }
523 if !self.config.testing {
524 let (ret_ty, size) = return_types.last().unwrap();
525 let opt_panic_indicator = match *size {
526 2 => None,
527 3 => Some(self.ctx.add_var(next_unprocessed_deref())),
528 _ => panic!("Unexpected output type: {:?}", ret_ty.0),
529 };
530 let ptr_start = self.ctx.add_var(next_unprocessed_deref());
533 let ptr_end = self.ctx.add_var(next_unprocessed_deref());
536 if let Some(panic_indicator) = opt_panic_indicator {
537 casm_build_extend! {self.ctx,
538 const czero = 0;
539 tempvar zero = czero;
540 hint ExternalHint::AddMarker { start: ptr_start, end: ptr_end };
541 assert zero = panic_indicator;
542 };
543 }
544 self.output_builtin_vars.insert(BuiltinName::output, ptr_end);
545 }
546 assert_eq!(unprocessed_return_size, 0);
547 assert_eq!(self.input_builtin_vars.len(), self.output_builtin_vars.len());
548 if self.has_post_calculation_loop {
549 for (cell, local_expr) in zip_eq(
551 self.output_builtin_vars.iter().filter_map(non_segment_arena_var).copied(),
552 &self.local_exprs,
553 ) {
554 let local_cell = self.ctx.add_var(local_expr.clone());
555 casm_build_extend!(self.ctx, assert local_cell = cell;);
556 }
557 }
558 }
559
560 fn validate_segment_arena(&mut self) {
562 let segment_arena =
563 self.output_builtin_vars.swap_remove(&BuiltinName::segment_arena).unwrap();
564 casm_build_extend! {self.ctx,
565 tempvar n_segments = segment_arena[-2];
566 tempvar n_finalized = segment_arena[-1];
567 assert n_segments = n_finalized;
568 jump STILL_LEFT_PRE if n_segments != 0;
569 rescope{};
570 jump DONE_VALIDATION;
571 STILL_LEFT_PRE:
572 const one = 1;
573 tempvar infos = segment_arena[-3];
574 tempvar remaining_segments = n_segments - one;
575 rescope{infos = infos, remaining_segments = remaining_segments};
576 LOOP_START:
577 jump STILL_LEFT_LOOP if remaining_segments != 0;
578 rescope{};
579 jump DONE_VALIDATION;
580 STILL_LEFT_LOOP:
581 tempvar prev_end = infos[1];
582 tempvar curr_start = infos[3];
583 const one = 1;
584 let expected_curr_start = prev_end + one;
585 hint ExternalHint::AddRelocationRule { src: curr_start, dst: expected_curr_start };
586 assert curr_start = prev_end + one;
587 const three = 3;
588 tempvar next_infos = infos + three;
589 tempvar next_remaining_segments = remaining_segments - one;
590 rescope{infos = next_infos, remaining_segments = next_remaining_segments};
591 #{ steps = 0; }
592 jump LOOP_START;
593 DONE_VALIDATION:
594 };
595 }
596
597 fn update_builtins_as_locals(&mut self) {
599 for (var, local_expr) in zip_eq(
600 self.output_builtin_vars.iter_mut().filter_map(non_segment_arena_var),
601 &self.local_exprs,
602 ) {
603 *var = self.ctx.add_var(local_expr.clone());
604 }
605 }
606
607 fn process_builtins_output(&mut self) {
609 for name in &self.builtins {
610 if let Some(var) = self.output_builtin_vars.swap_remove(name) {
611 casm_build_extend!(self.ctx, tempvar cell = var;);
612 }
613 }
614 assert!(self.output_builtin_vars.is_empty());
615 }
616}
617
618fn non_segment_arena_var<T>((name, var): (&BuiltinName, T)) -> Option<T> {
620 if *name == BuiltinName::segment_arena { None } else { Some(var) }
621}
622
623pub fn create_code_footer() -> Vec<Instruction> {
625 casm! {
626 ret;
629 }
630 .instructions
631}
632
633fn create_metadata(
635 sierra_program: &SierraProgram,
636 program_info: &ProgramRegistryInfo,
637 metadata_config: Option<MetadataComputationConfig>,
638) -> Result<Metadata, BuildError> {
639 if let Some(metadata_config) = metadata_config {
640 calc_metadata(sierra_program, program_info, metadata_config)
641 } else {
642 calc_metadata_ap_change_only(sierra_program, program_info)
643 }
644 .map_err(|err| match err {
645 MetadataError::ApChangeError(err) => BuildError::ApChangeError(err),
646 MetadataError::CostError(err) => BuildError::FailedGasCalculation(err),
647 })
648}