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 let mut builtin_offset = 3;
315 for builtin_name in helper.builtins.iter().rev() {
316 helper.builtin_vars.insert(
317 *builtin_name,
318 helper.ctx.add_var(CellExpression::Deref(cell_ref!([fp - builtin_offset]))),
319 );
320 builtin_offset += 1;
321 }
322 } else {
323 helper.process_builtins(param_types);
324 }
325 helper.process_params(param_types);
326 casm_build_extend!(helper.ctx, let () = call FUNCTION;);
327 helper.process_output(return_types);
328
329 if helper.has_post_calculation_loop {
330 helper.validate_segment_arena();
331 }
332
333 if !helper.config.testing {
334 helper.process_builtins_output();
335 }
336
337 casm_build_extend! (helper.ctx, ret;);
338 helper.ctx.future_label("FUNCTION", code_offset);
341 Ok((helper.ctx.build([]).instructions, helper.builtins))
342}
343
344struct EntryCodeHelper {
346 ctx: CasmBuilder,
347 config: EntryCodeConfig,
348 builtin_ty_to_vm_name: OrderedHashMap<GenericTypeId, BuiltinName>,
349 builtins: Vec<BuiltinName>,
350 got_segment_arena: bool,
351 has_post_calculation_loop: bool,
352 builtin_vars: UnorderedHashMap<BuiltinName, Var>,
354 local_exprs: OrderedHashMap<BuiltinName, CellExpression>,
357 emulated_builtins: UnorderedHashSet<GenericTypeId>,
358}
359
360impl EntryCodeHelper {
361 fn new(config: EntryCodeConfig) -> Self {
363 Self {
364 ctx: CasmBuilder::default(),
365 config,
366 builtin_ty_to_vm_name: OrderedHashMap::from_iter([
367 (PedersenType::ID, BuiltinName::pedersen),
368 (RangeCheckType::ID, BuiltinName::range_check),
369 (BitwiseType::ID, BuiltinName::bitwise),
370 (EcOpType::ID, BuiltinName::ec_op),
371 (PoseidonType::ID, BuiltinName::poseidon),
372 (RangeCheck96Type::ID, BuiltinName::range_check96),
373 (AddModType::ID, BuiltinName::add_mod),
374 (MulModType::ID, BuiltinName::mul_mod),
375 (SegmentArenaType::ID, BuiltinName::segment_arena),
376 ]),
377 builtins: vec![],
378 got_segment_arena: false,
379 has_post_calculation_loop: false,
380 builtin_vars: UnorderedHashMap::default(),
381 local_exprs: OrderedHashMap::default(),
382 emulated_builtins: UnorderedHashSet::<_>::from_iter([SystemType::ID]),
383 }
384 }
385
386 fn process_builtins(&mut self, param_types: &[(GenericTypeId, i16)]) {
388 let mut builtin_offset = 3;
389
390 for (builtin_ty, builtin_name) in self.builtin_ty_to_vm_name.iter().rev().skip(1) {
392 if param_types.iter().any(|(ty, _)| ty == builtin_ty) {
393 self.builtin_vars.insert(
394 *builtin_name,
395 self.ctx.add_var(CellExpression::Deref(cell_ref!([fp - builtin_offset]))),
396 );
397 builtin_offset += 1;
398 self.builtins.push(*builtin_name);
399 }
400 }
401 if !self.config.testing {
402 let output_builtin_var =
403 self.ctx.add_var(CellExpression::Deref(cell_ref!([fp - builtin_offset])));
404 self.builtin_vars.insert(BuiltinName::output, output_builtin_var);
405 self.builtins.push(BuiltinName::output);
406 }
407 self.builtins.reverse();
408 }
409
410 fn process_params(&mut self, param_types: &[(GenericTypeId, i16)]) {
412 self.got_segment_arena = param_types.iter().any(|(ty, _)| ty == &SegmentArenaType::ID);
413 self.has_post_calculation_loop = self.got_segment_arena && !self.config.testing;
414
415 if self.has_post_calculation_loop {
416 if !self.config.testing {
417 casm_build_extend!(self.ctx, localvar local;);
419 self.local_exprs
420 .insert(BuiltinName::output, self.ctx.get_unadjusted(local).clone());
421 }
422
423 for (generic_ty, _ty_size) in param_types {
424 if let Some(name) = self.builtin_ty_to_vm_name.get(generic_ty)
425 && name != &BuiltinName::segment_arena
426 {
427 casm_build_extend!(self.ctx, localvar local;);
428 self.local_exprs.insert(*name, self.ctx.get_unadjusted(local).clone());
429 }
430 }
431
432 if !self.local_exprs.is_empty() {
433 casm_build_extend!(self.ctx, ap += self.local_exprs.len(););
434 }
435 }
436
437 if self.got_segment_arena {
438 casm_build_extend! {self.ctx,
439 tempvar segment_arena;
440 tempvar infos;
441 hint AllocSegment into {dst: segment_arena};
442 hint AllocSegment into {dst: infos};
443 const czero = 0;
444 tempvar zero = czero;
445 assert infos = *(segment_arena++);
447 assert zero = *(segment_arena++);
448 assert zero = *(segment_arena++);
449 }
450 self.builtin_vars.insert(BuiltinName::segment_arena, segment_arena);
452 }
453 let mut unallocated_count = 0;
454 let mut param_index = 0;
455
456 let non_proof_signature_params =
459 if self.config.testing { param_types } else { ¶m_types[..(param_types.len() - 2)] };
460 for (generic_ty, ty_size) in non_proof_signature_params {
461 if let Some(name) = self.builtin_ty_to_vm_name.get(generic_ty).cloned() {
462 let var = self.builtin_vars[&name];
463 casm_build_extend!(self.ctx, tempvar _builtin = var;);
464 } else if self.emulated_builtins.contains(generic_ty) {
465 assert!(
466 self.config.allow_unsound,
467 "Cannot support emulated builtins if not configured to `allow_unsound`."
468 );
469 casm_build_extend! {self.ctx,
470 tempvar system;
471 hint AllocSegment into {dst: system};
472 };
473 unallocated_count += ty_size;
474 } else if self.config.testing {
475 if *ty_size > 0 {
476 casm_build_extend! {self.ctx,
477 tempvar first;
478 const param_index = param_index;
479 hint ExternalHint::WriteRunParam { index: param_index } into { dst: first };
480 };
481 for _ in 1..*ty_size {
482 casm_build_extend!(self.ctx, tempvar _cell;);
483 }
484 }
485 param_index += 1;
486 unallocated_count += ty_size;
487 } else if generic_ty == &GasBuiltinType::ID {
488 casm_build_extend! {self.ctx,
489 const max_gas = i64::MAX;
490 tempvar gas = max_gas;
491 };
492 } else {
493 unreachable!("Unexpected argument type: {:?}", generic_ty);
494 }
495 }
496 if !self.config.testing {
497 let output_ptr = self.builtin_vars[&BuiltinName::output];
498 casm_build_extend! { self.ctx,
499 tempvar input_start;
500 tempvar _input_end;
501 const param_index = 0;
502 hint ExternalHint::WriteRunParam { index: param_index } into { dst: input_start };
503 tempvar output_start = output_ptr;
504 tempvar output_end = output_ptr;
505 };
506 unallocated_count += 2;
507 }
508 if unallocated_count > 0 {
509 casm_build_extend!(self.ctx, ap += unallocated_count.into_or_panic::<usize>(););
510 }
511 }
512
513 fn process_output(&mut self, return_types: &[(GenericTypeId, i16)]) {
515 let mut unprocessed_return_size = return_types.iter().map(|(_, size)| size).sum::<i16>();
516 let mut next_unprocessed_deref = || {
517 let deref_cell = CellExpression::Deref(cell_ref!([ap - unprocessed_return_size]));
518 assert!(unprocessed_return_size > 0);
519 unprocessed_return_size -= 1;
520 deref_cell
521 };
522 let non_proof_return_types = if self.config.testing {
525 return_types
526 } else {
527 &return_types[..(return_types.len() - 1)]
528 };
529 let mut new_builtin_vars = UnorderedHashMap::<BuiltinName, Var>::default();
530 for (ret_ty, size) in non_proof_return_types {
531 if let Some(name) = self.builtin_ty_to_vm_name.get(ret_ty) {
532 new_builtin_vars.insert(*name, self.ctx.add_var(next_unprocessed_deref()));
533 } else if self.config.testing {
534 for _ in 0..*size {
535 next_unprocessed_deref();
536 }
537 } else if self.emulated_builtins.contains(ret_ty) {
538 assert!(
539 self.config.allow_unsound,
540 "Cannot support emulated builtins if not configured to `allow_unsound`."
541 );
542 let _ = next_unprocessed_deref();
543 } else {
544 assert_eq!(ret_ty, &GasBuiltinType::ID);
545 let _ = next_unprocessed_deref();
546 }
547 }
548 if !self.config.testing {
549 let (ret_ty, size) = return_types.last().unwrap();
550 let opt_panic_indicator = match *size {
551 2 => None,
552 3 => Some(self.ctx.add_var(next_unprocessed_deref())),
553 _ => panic!("Unexpected output type: {:?}", ret_ty.0),
554 };
555 let ptr_start = self.ctx.add_var(next_unprocessed_deref());
558 let ptr_end = self.ctx.add_var(next_unprocessed_deref());
561 if let Some(panic_indicator) = opt_panic_indicator {
562 casm_build_extend! {self.ctx,
563 const czero = 0;
564 tempvar zero = czero;
565 hint ExternalHint::AddMarker { start: ptr_start, end: ptr_end };
566 assert zero = panic_indicator;
567 };
568 }
569 new_builtin_vars.insert(BuiltinName::output, ptr_end);
570 }
571 assert_eq!(unprocessed_return_size, 0);
572 if self.has_post_calculation_loop {
573 for (name, local_expr) in self.local_exprs.iter() {
578 let builtin_var = new_builtin_vars[name];
579 let local_expr = local_expr.clone();
580 let local_var = self.ctx.add_var(local_expr.clone());
581 casm_build_extend!(self.ctx, assert local_var = builtin_var;);
582 }
583 }
584
585 for name in &self.builtins {
588 new_builtin_vars.entry(*name).or_insert_with(|| {
589 assert!(
590 self.config.builtin_list.is_some(),
591 "if builtin_list is not some, output builtins should cover all input builtins"
592 );
593
594 let var = self.builtin_vars[name];
595 self.local_exprs.insert(*name, self.ctx.get_unadjusted(var).clone());
596 var
597 });
598 }
599 self.builtin_vars = new_builtin_vars;
600 }
601
602 fn validate_segment_arena(&mut self) {
604 let segment_arena = self.builtin_vars.remove(&BuiltinName::segment_arena).unwrap();
605 casm_build_extend! {self.ctx,
606 tempvar n_segments = segment_arena[-2];
607 tempvar n_finalized = segment_arena[-1];
608 assert n_segments = n_finalized;
609 jump STILL_LEFT_PRE if n_segments != 0;
610 rescope{};
611 jump DONE_VALIDATION;
612 STILL_LEFT_PRE:
613 const one = 1;
614 tempvar infos = segment_arena[-3];
615 tempvar remaining_segments = n_segments - one;
616 rescope{infos = infos, remaining_segments = remaining_segments};
617 LOOP_START:
618 jump STILL_LEFT_LOOP if remaining_segments != 0;
619 rescope{};
620 jump DONE_VALIDATION;
621 STILL_LEFT_LOOP:
622 tempvar prev_end = infos[1];
623 tempvar curr_start = infos[3];
624 const one = 1;
625 let expected_curr_start = prev_end + one;
626 hint ExternalHint::AddRelocationRule { src: curr_start, dst: expected_curr_start };
627 assert curr_start = prev_end + one;
628 const three = 3;
629 tempvar next_infos = infos + three;
630 tempvar next_remaining_segments = remaining_segments - one;
631 rescope{infos = next_infos, remaining_segments = next_remaining_segments};
632 #{ steps = 0; }
633 jump LOOP_START;
634 DONE_VALIDATION:
635 };
636 }
637
638 fn process_builtins_output(&mut self) {
640 for name in &self.builtins {
641 if let Some(mut var) = self.builtin_vars.remove(name) {
642 if let Some(local_expr) = self.local_exprs.get(name) {
643 var = self.ctx.add_var(local_expr.clone());
646 }
647 casm_build_extend!(self.ctx, tempvar cell = var;);
648 }
649 }
650 assert!(self.builtin_vars.is_empty());
651 }
652}
653
654pub fn create_code_footer() -> Vec<Instruction> {
656 casm! {
657 ret;
660 }
661 .instructions
662}
663
664fn create_metadata(
666 sierra_program: &SierraProgram,
667 program_info: &ProgramRegistryInfo,
668 metadata_config: Option<MetadataComputationConfig>,
669) -> Result<Metadata, BuildError> {
670 if let Some(metadata_config) = metadata_config {
671 calc_metadata(sierra_program, program_info, metadata_config)
672 } else {
673 calc_metadata_ap_change_only(sierra_program, program_info)
674 }
675 .map_err(|err| match err {
676 MetadataError::ApChangeError(err) => BuildError::ApChangeError(err),
677 MetadataError::CostError(err) => BuildError::FailedGasCalculation(err),
678 })
679}