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