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::{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::compiler::{CairoProgram, CompilationError, SierraToCasmConfig};
28use cairo_lang_sierra_to_casm::metadata::{
29 Metadata, MetadataComputationConfig, MetadataError, calc_metadata, calc_metadata_ap_change_only,
30};
31use cairo_lang_sierra_type_size::{TypeSizeMap, get_type_size_map};
32use cairo_lang_utils::casts::IntoOrPanic;
33use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
34use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
35use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
36use cairo_vm::types::builtin_name::BuiltinName;
37use itertools::zip_eq;
38use thiserror::Error;
39
40#[derive(Debug, Error)]
41pub enum BuildError {
42 #[error(
43 "Failed calculating gas usage, it is likely a call for `gas::withdraw_gas` is missing. \
44 Inner error: {0}"
45 )]
46 FailedGasCalculation(#[from] CostError),
47 #[error("Function with suffix `{suffix}` to run not found.")]
48 MissingFunction { suffix: String },
49 #[error(transparent)]
50 ProgramRegistryError(#[from] Box<ProgramRegistryError>),
51 #[error(transparent)]
52 SierraCompilationError(#[from] Box<CompilationError>),
53 #[error(transparent)]
54 ApChangeError(#[from] ApChangeError),
55}
56
57impl BuildError {
58 pub fn stmt_indices(&self) -> Vec<StatementIdx> {
59 match self {
60 BuildError::SierraCompilationError(err) => err.stmt_indices(),
61 _ => vec![],
62 }
63 }
64}
65
66pub struct RunnableBuilder {
68 sierra_program: SierraProgram,
70 metadata: Metadata,
72 sierra_program_registry: ProgramRegistry<CoreType, CoreLibfunc>,
74 type_sizes: TypeSizeMap,
76 casm_program: CairoProgram,
78 non_args_types: UnorderedHashSet<GenericTypeId>,
80}
81
82impl RunnableBuilder {
83 pub fn new(
85 sierra_program: SierraProgram,
86 metadata_config: Option<MetadataComputationConfig>,
87 ) -> Result<Self, BuildError> {
88 let gas_usage_check = metadata_config.is_some();
89 let metadata = create_metadata(&sierra_program, metadata_config)?;
90 let sierra_program_registry =
91 ProgramRegistry::<CoreType, CoreLibfunc>::new(&sierra_program)?;
92 let type_sizes = get_type_size_map(&sierra_program, &sierra_program_registry).unwrap();
93 let casm_program = cairo_lang_sierra_to_casm::compiler::compile(
94 &sierra_program,
95 &metadata,
96 SierraToCasmConfig { gas_usage_check, max_bytecode_size: usize::MAX },
97 )?;
98
99 Ok(Self {
100 sierra_program,
101 metadata,
102 sierra_program_registry,
103 type_sizes,
104 casm_program,
105 non_args_types: UnorderedHashSet::from_iter([
106 AddModType::ID,
107 BitwiseType::ID,
108 GasBuiltinType::ID,
109 EcOpType::ID,
110 MulModType::ID,
111 PedersenType::ID,
112 PoseidonType::ID,
113 RangeCheck96Type::ID,
114 RangeCheckType::ID,
115 SegmentArenaType::ID,
116 SystemType::ID,
117 ]),
118 })
119 }
120
121 pub fn sierra_program(&self) -> &SierraProgram {
123 &self.sierra_program
124 }
125
126 pub fn casm_program(&self) -> &CairoProgram {
128 &self.casm_program
129 }
130
131 pub fn metadata(&self) -> &Metadata {
133 &self.metadata
134 }
135
136 pub fn registry(&self) -> &ProgramRegistry<CoreType, CoreLibfunc> {
138 &self.sierra_program_registry
139 }
140
141 pub fn find_function(&self, name_suffix: &str) -> Result<&Function, BuildError> {
143 self.sierra_program
144 .funcs
145 .iter()
146 .find(|f| {
147 if let Some(name) = &f.id.debug_name { name.ends_with(name_suffix) } else { false }
148 })
149 .ok_or_else(|| BuildError::MissingFunction { suffix: name_suffix.to_owned() })
150 }
151
152 fn type_info(&self, ty: &ConcreteTypeId) -> &cairo_lang_sierra::extensions::types::TypeInfo {
154 self.sierra_program_registry.get_type(ty).unwrap().info()
155 }
156
157 pub fn type_long_id(&self, ty: &ConcreteTypeId) -> &ConcreteTypeLongId {
159 &self.type_info(ty).long_id
160 }
161
162 pub fn type_size(&self, ty: &ConcreteTypeId) -> i16 {
164 self.type_sizes[ty]
165 }
166
167 pub fn is_user_arg_type(&self, ty: &GenericTypeId) -> bool {
169 !self.non_args_types.contains(ty)
170 }
171
172 pub fn create_wrapper_info(
174 &self,
175 func: &Function,
176 config: EntryCodeConfig,
177 ) -> Result<CasmProgramWrapperInfo, BuildError> {
178 let (header, builtins) = self.create_entry_code(func, config)?;
179 Ok(CasmProgramWrapperInfo { header, builtins, footer: create_code_footer() })
180 }
181
182 pub fn assemble_function_program(
184 &self,
185 func: &Function,
186 config: EntryCodeConfig,
187 ) -> Result<(AssembledCairoProgram, Vec<BuiltinName>), BuildError> {
188 let info = self.create_wrapper_info(func, config)?;
189 let assembled_cairo_program = self.casm_program.assemble_ex(&info.header, &info.footer);
190 Ok((assembled_cairo_program, info.builtins))
191 }
192
193 fn create_entry_code(
206 &self,
207 func: &Function,
208 config: EntryCodeConfig,
209 ) -> Result<(Vec<Instruction>, Vec<BuiltinName>), BuildError> {
210 let param_types = self.generic_id_and_size_from_concrete(&func.signature.param_types);
211 let return_types = self.generic_id_and_size_from_concrete(&func.signature.ret_types);
212
213 let entry_point = func.entry_point.0;
214 let code_offset =
215 self.casm_program.debug_info.sierra_statement_info[entry_point].start_offset;
216
217 create_entry_code_from_params(¶m_types, &return_types, code_offset, config)
218 }
219
220 pub fn generic_id_and_size_from_concrete(
222 &self,
223 types: &[ConcreteTypeId],
224 ) -> Vec<(GenericTypeId, i16)> {
225 types
226 .iter()
227 .map(|pt| {
228 let info = self.type_info(pt);
229 let generic_id = &info.long_id.generic_id;
230 let size = self.type_sizes[pt];
231 (generic_id.clone(), size)
232 })
233 .collect()
234 }
235}
236
237#[derive(Clone, Debug)]
239pub struct EntryCodeConfig {
240 pub testing: bool,
250 pub allow_unsound: bool,
253}
254impl EntryCodeConfig {
255 pub fn testing() -> Self {
260 Self { testing: true, allow_unsound: true }
261 }
262
263 pub fn executable(allow_unsound: bool) -> Self {
265 Self { testing: false, allow_unsound }
266 }
267}
268
269pub struct CasmProgramWrapperInfo {
271 pub builtins: Vec<BuiltinName>,
273 pub header: Vec<Instruction>,
275 pub footer: Vec<Instruction>,
277}
278
279pub fn create_entry_code_from_params(
294 param_types: &[(GenericTypeId, i16)],
295 return_types: &[(GenericTypeId, i16)],
296 code_offset: usize,
297 config: EntryCodeConfig,
298) -> Result<(Vec<Instruction>, Vec<BuiltinName>), BuildError> {
299 let mut helper = EntryCodeHelper::new(config);
300
301 helper.process_builtins(param_types);
302 helper.process_params(param_types);
303 casm_build_extend!(helper.ctx, let () = call FUNCTION;);
304 helper.process_output(return_types);
305
306 if helper.has_post_calculation_loop {
307 helper.validate_segment_arena();
308 helper.update_builtins_as_locals();
309 }
310
311 if !helper.config.testing {
312 helper.process_builtins_output();
313 }
314
315 casm_build_extend! (helper.ctx, ret;);
316 helper.ctx.future_label("FUNCTION".into(), code_offset);
319 Ok((helper.ctx.build([]).instructions, helper.builtins))
320}
321
322struct EntryCodeHelper {
324 ctx: CasmBuilder,
325 config: EntryCodeConfig,
326 input_builtin_vars: OrderedHashMap<BuiltinName, Var>,
327 builtin_ty_to_vm_name: UnorderedHashMap<GenericTypeId, BuiltinName>,
328 builtins: Vec<BuiltinName>,
329 got_segment_arena: bool,
330 has_post_calculation_loop: bool,
331 local_exprs: Vec<CellExpression>,
332 output_builtin_vars: OrderedHashMap<BuiltinName, Var>,
333}
334
335impl EntryCodeHelper {
336 fn new(config: EntryCodeConfig) -> Self {
338 Self {
339 ctx: CasmBuilder::default(),
340 config,
341 input_builtin_vars: OrderedHashMap::default(),
342 builtin_ty_to_vm_name: UnorderedHashMap::default(),
343 builtins: vec![],
344 got_segment_arena: false,
345 has_post_calculation_loop: false,
346 local_exprs: vec![],
347 output_builtin_vars: OrderedHashMap::default(),
348 }
349 }
350
351 fn process_builtins(&mut self, param_types: &[(GenericTypeId, i16)]) {
353 let mut builtin_offset = 3;
354 for (builtin_name, builtin_ty) in [
355 (BuiltinName::mul_mod, MulModType::ID),
356 (BuiltinName::add_mod, AddModType::ID),
357 (BuiltinName::range_check96, RangeCheck96Type::ID),
358 (BuiltinName::poseidon, PoseidonType::ID),
359 (BuiltinName::ec_op, EcOpType::ID),
360 (BuiltinName::bitwise, BitwiseType::ID),
361 (BuiltinName::range_check, RangeCheckType::ID),
362 (BuiltinName::pedersen, PedersenType::ID),
363 ] {
364 if param_types.iter().any(|(ty, _)| ty == &builtin_ty) {
365 self.input_builtin_vars.insert(
366 builtin_name,
367 self.ctx.add_var(CellExpression::Deref(deref!([fp - builtin_offset]))),
368 );
369 self.builtin_ty_to_vm_name.insert(builtin_ty, builtin_name);
370 builtin_offset += 1;
371 self.builtins.push(builtin_name);
372 }
373 }
374 if !self.config.testing {
375 let output_builtin_var =
376 self.ctx.add_var(CellExpression::Deref(deref!([fp - builtin_offset])));
377 self.input_builtin_vars.insert(BuiltinName::output, output_builtin_var);
378 self.builtins.push(BuiltinName::output);
379 }
380 self.builtins.reverse();
381 }
382
383 fn process_params(&mut self, param_types: &[(GenericTypeId, i16)]) {
385 let emulated_builtins = UnorderedHashSet::<_>::from_iter([SystemType::ID]);
386
387 self.got_segment_arena = param_types.iter().any(|(ty, _)| ty == &SegmentArenaType::ID);
388 self.has_post_calculation_loop = self.got_segment_arena && !self.config.testing;
389
390 if self.has_post_calculation_loop {
391 for name in self.input_builtin_vars.keys() {
392 if name != &BuiltinName::segment_arena {
393 casm_build_extend!(self.ctx, localvar local;);
394 self.local_exprs.push(self.ctx.get_value(local, false));
395 }
396 }
397 if !self.local_exprs.is_empty() {
398 casm_build_extend!(self.ctx, ap += self.local_exprs.len(););
399 }
400 }
401 if self.got_segment_arena {
402 casm_build_extend! {self.ctx,
403 tempvar segment_arena;
404 tempvar infos;
405 hint AllocSegment into {dst: segment_arena};
406 hint AllocSegment into {dst: infos};
407 const czero = 0;
408 tempvar zero = czero;
409 assert infos = *(segment_arena++);
411 assert zero = *(segment_arena++);
412 assert zero = *(segment_arena++);
413 }
414 self.input_builtin_vars.insert(BuiltinName::segment_arena, segment_arena);
416 self.builtin_ty_to_vm_name.insert(SegmentArenaType::ID, BuiltinName::segment_arena);
417 }
418 let mut unallocated_count = 0;
419 let mut param_index = 0;
420
421 let non_proof_signature_params =
424 if self.config.testing { param_types } else { ¶m_types[..(param_types.len() - 2)] };
425 for (generic_ty, ty_size) in non_proof_signature_params {
426 if let Some(name) = self.builtin_ty_to_vm_name.get(generic_ty).cloned() {
427 let var = self.input_builtin_vars[&name];
428 casm_build_extend!(self.ctx, tempvar _builtin = var;);
429 } else if emulated_builtins.contains(generic_ty) {
430 assert!(
431 self.config.allow_unsound,
432 "Cannot support emulated builtins if not configured to `allow_unsound`."
433 );
434 casm_build_extend! {self.ctx,
435 tempvar system;
436 hint AllocSegment into {dst: system};
437 };
438 unallocated_count += ty_size;
439 } else if self.config.testing {
440 if *ty_size > 0 {
441 casm_build_extend! {self.ctx,
442 tempvar first;
443 const param_index = param_index;
444 hint ExternalHint::WriteRunParam { index: param_index } into { dst: first };
445 };
446 for _ in 1..*ty_size {
447 casm_build_extend!(self.ctx, tempvar _cell;);
448 }
449 }
450 param_index += 1;
451 unallocated_count += ty_size;
452 } else if generic_ty == &GasBuiltinType::ID {
453 casm_build_extend! {self.ctx,
454 const max_gas = i64::MAX;
455 tempvar gas = max_gas;
456 };
457 } else {
458 unreachable!("Unexpected argument type: {:?}", generic_ty);
459 }
460 }
461 if !self.config.testing {
462 let output_ptr = self.input_builtin_vars[&BuiltinName::output];
463 casm_build_extend! { self.ctx,
464 tempvar input_start;
465 tempvar _input_end;
466 const param_index = 0;
467 hint ExternalHint::WriteRunParam { index: param_index } into { dst: input_start };
468 tempvar output_start = output_ptr;
469 tempvar output_end = output_ptr;
470 };
471 unallocated_count += 2;
472 }
473 if unallocated_count > 0 {
474 casm_build_extend!(self.ctx, ap += unallocated_count.into_or_panic::<usize>(););
475 }
476 }
477
478 fn process_output(&mut self, return_types: &[(GenericTypeId, i16)]) {
480 let mut unprocessed_return_size = return_types.iter().map(|(_, size)| size).sum::<i16>();
481 let mut next_unprocessed_deref = || {
482 let deref_cell = CellExpression::Deref(deref!([ap - unprocessed_return_size]));
483 assert!(unprocessed_return_size > 0);
484 unprocessed_return_size -= 1;
485 deref_cell
486 };
487 let non_proof_return_types = if self.config.testing {
490 return_types
491 } else {
492 &return_types[..(return_types.len() - 1)]
493 };
494 for (ret_ty, size) in non_proof_return_types {
495 if let Some(name) = self.builtin_ty_to_vm_name.get(ret_ty) {
496 self.output_builtin_vars.insert(*name, self.ctx.add_var(next_unprocessed_deref()));
497 } else if self.config.testing {
498 for _ in 0..*size {
499 next_unprocessed_deref();
500 }
501 } else {
502 assert_eq!(ret_ty, &GasBuiltinType::ID);
503 let _ = next_unprocessed_deref();
504 }
505 }
506 if !self.config.testing {
507 let (ret_ty, size) = return_types.last().unwrap();
508 let opt_panic_indicator = match *size {
509 2 => None,
510 3 => Some(self.ctx.add_var(next_unprocessed_deref())),
511 _ => panic!("Unexpected output type: {:?}", ret_ty.0),
512 };
513 let ptr_start = self.ctx.add_var(next_unprocessed_deref());
516 let ptr_end = self.ctx.add_var(next_unprocessed_deref());
519 if let Some(panic_indicator) = opt_panic_indicator {
520 casm_build_extend! {self.ctx,
521 const czero = 0;
522 tempvar zero = czero;
523 hint ExternalHint::AddMarker { start: ptr_start, end: ptr_end };
524 assert zero = panic_indicator;
525 };
526 }
527 self.output_builtin_vars.insert(BuiltinName::output, ptr_end);
528 }
529 assert_eq!(unprocessed_return_size, 0);
530 assert_eq!(self.input_builtin_vars.len(), self.output_builtin_vars.len());
531 if self.has_post_calculation_loop {
532 for (cell, local_expr) in zip_eq(
534 self.output_builtin_vars.iter().filter_map(non_segment_arena_var).copied(),
535 &self.local_exprs,
536 ) {
537 let local_cell = self.ctx.add_var(local_expr.clone());
538 casm_build_extend!(self.ctx, assert local_cell = cell;);
539 }
540 }
541 }
542
543 fn validate_segment_arena(&mut self) {
545 let segment_arena =
546 self.output_builtin_vars.swap_remove(&BuiltinName::segment_arena).unwrap();
547 casm_build_extend! {self.ctx,
548 tempvar n_segments = segment_arena[-2];
549 tempvar n_finalized = segment_arena[-1];
550 assert n_segments = n_finalized;
551 jump STILL_LEFT_PRE if n_segments != 0;
552 rescope{};
553 jump DONE_VALIDATION;
554 STILL_LEFT_PRE:
555 const one = 1;
556 tempvar infos = segment_arena[-3];
557 tempvar remaining_segments = n_segments - one;
558 rescope{infos = infos, remaining_segments = remaining_segments};
559 LOOP_START:
560 jump STILL_LEFT_LOOP if remaining_segments != 0;
561 rescope{};
562 jump DONE_VALIDATION;
563 STILL_LEFT_LOOP:
564 tempvar prev_end = infos[1];
565 tempvar curr_start = infos[3];
566 const one = 1;
567 let expected_curr_start = prev_end + one;
568 hint ExternalHint::AddRelocationRule { src: curr_start, dst: expected_curr_start };
569 assert curr_start = prev_end + one;
570 const three = 3;
571 tempvar next_infos = infos + three;
572 tempvar next_remaining_segments = remaining_segments - one;
573 rescope{infos = next_infos, remaining_segments = next_remaining_segments};
574 #{ steps = 0; }
575 jump LOOP_START;
576 DONE_VALIDATION:
577 };
578 }
579
580 fn update_builtins_as_locals(&mut self) {
582 for (var, local_expr) in zip_eq(
583 self.output_builtin_vars.iter_mut().filter_map(non_segment_arena_var),
584 &self.local_exprs,
585 ) {
586 *var = self.ctx.add_var(local_expr.clone());
587 }
588 }
589
590 fn process_builtins_output(&mut self) {
592 for name in &self.builtins {
593 if let Some(var) = self.output_builtin_vars.swap_remove(name) {
594 casm_build_extend!(self.ctx, tempvar cell = var;);
595 }
596 }
597 assert!(self.output_builtin_vars.is_empty());
598 }
599}
600
601fn non_segment_arena_var<T>((name, var): (&BuiltinName, T)) -> Option<T> {
603 if *name == BuiltinName::segment_arena { None } else { Some(var) }
604}
605
606pub fn create_code_footer() -> Vec<Instruction> {
608 casm! {
609 ret;
612 }
613 .instructions
614}
615
616fn create_metadata(
618 sierra_program: &SierraProgram,
619 metadata_config: Option<MetadataComputationConfig>,
620) -> Result<Metadata, BuildError> {
621 if let Some(metadata_config) = metadata_config {
622 calc_metadata(sierra_program, metadata_config)
623 } else {
624 calc_metadata_ap_change_only(sierra_program)
625 }
626 .map_err(|err| match err {
627 MetadataError::ApChangeError(err) => BuildError::ApChangeError(err),
628 MetadataError::CostError(err) => BuildError::FailedGasCalculation(err),
629 })
630}