1use cairo_lang_casm::assembler::AssembledCairoProgram;
2use cairo_lang_casm::builder::CasmBuilder;
3use cairo_lang_casm::cell_expression::CellExpression;
4use cairo_lang_casm::hints::ExternalHint;
5use cairo_lang_casm::instructions::Instruction;
6use cairo_lang_casm::{casm, casm_build_extend, deref};
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::compiler::{CairoProgram, CompilationError, SierraToCasmConfig};
26use cairo_lang_sierra_to_casm::metadata::{
27 Metadata, MetadataComputationConfig, MetadataError, calc_metadata, calc_metadata_ap_change_only,
28};
29use cairo_lang_sierra_type_size::{TypeSizeMap, get_type_size_map};
30use cairo_lang_utils::casts::IntoOrPanic;
31use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
32use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
33use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
34use cairo_vm::types::builtin_name::BuiltinName;
35use itertools::{chain, zip_eq};
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
64pub struct RunnableBuilder {
66 sierra_program: SierraProgram,
68 metadata: Metadata,
70 sierra_program_registry: ProgramRegistry<CoreType, CoreLibfunc>,
72 type_sizes: TypeSizeMap,
74 casm_program: CairoProgram,
76 non_args_types: UnorderedHashSet<GenericTypeId>,
78}
79
80impl RunnableBuilder {
81 pub fn new(
83 sierra_program: SierraProgram,
84 metadata_config: Option<MetadataComputationConfig>,
85 ) -> Result<Self, BuildError> {
86 let gas_usage_check = metadata_config.is_some();
87 let metadata = create_metadata(&sierra_program, metadata_config)?;
88 let sierra_program_registry =
89 ProgramRegistry::<CoreType, CoreLibfunc>::new(&sierra_program)?;
90 let type_sizes = get_type_size_map(&sierra_program, &sierra_program_registry).unwrap();
91 let casm_program = cairo_lang_sierra_to_casm::compiler::compile(
92 &sierra_program,
93 &metadata,
94 SierraToCasmConfig { gas_usage_check, max_bytecode_size: usize::MAX },
95 )?;
96
97 Ok(Self {
98 sierra_program,
99 metadata,
100 sierra_program_registry,
101 type_sizes,
102 casm_program,
103 non_args_types: UnorderedHashSet::from_iter([
104 AddModType::ID,
105 BitwiseType::ID,
106 GasBuiltinType::ID,
107 EcOpType::ID,
108 MulModType::ID,
109 PedersenType::ID,
110 PoseidonType::ID,
111 RangeCheck96Type::ID,
112 RangeCheckType::ID,
113 SegmentArenaType::ID,
114 SystemType::ID,
115 ]),
116 })
117 }
118
119 pub fn sierra_program(&self) -> &SierraProgram {
121 &self.sierra_program
122 }
123
124 pub fn casm_program(&self) -> &CairoProgram {
126 &self.casm_program
127 }
128
129 pub fn metadata(&self) -> &Metadata {
131 &self.metadata
132 }
133
134 pub fn registry(&self) -> &ProgramRegistry<CoreType, CoreLibfunc> {
136 &self.sierra_program_registry
137 }
138
139 pub fn find_function(&self, name_suffix: &str) -> Result<&Function, BuildError> {
141 self.sierra_program
142 .funcs
143 .iter()
144 .find(|f| {
145 if let Some(name) = &f.id.debug_name { name.ends_with(name_suffix) } else { false }
146 })
147 .ok_or_else(|| BuildError::MissingFunction { suffix: name_suffix.to_owned() })
148 }
149
150 fn type_info(&self, ty: &ConcreteTypeId) -> &cairo_lang_sierra::extensions::types::TypeInfo {
152 self.sierra_program_registry.get_type(ty).unwrap().info()
153 }
154
155 pub fn type_long_id(&self, ty: &ConcreteTypeId) -> &ConcreteTypeLongId {
157 &self.type_info(ty).long_id
158 }
159
160 pub fn type_size(&self, ty: &ConcreteTypeId) -> i16 {
162 self.type_sizes[ty]
163 }
164
165 pub fn is_user_arg_type(&self, ty: &GenericTypeId) -> bool {
167 !self.non_args_types.contains(ty)
168 }
169
170 pub fn create_wrapper_info(
172 &self,
173 func: &Function,
174 config: EntryCodeConfig,
175 ) -> Result<CasmProgramWrapperInfo, BuildError> {
176 let (header, builtins) = self.create_entry_code(func, config)?;
177 Ok(CasmProgramWrapperInfo { header, builtins, footer: create_code_footer() })
178 }
179
180 pub fn assemble_function_program(
182 &self,
183 func: &Function,
184 config: EntryCodeConfig,
185 ) -> Result<(AssembledCairoProgram, Vec<BuiltinName>), BuildError> {
186 let info = self.create_wrapper_info(func, config)?;
187 let assembled_cairo_program = self.casm_program.assemble_ex(&info.header, &info.footer);
188 Ok((assembled_cairo_program, info.builtins))
189 }
190
191 fn create_entry_code(
194 &self,
195 func: &Function,
196 config: EntryCodeConfig,
197 ) -> Result<(Vec<Instruction>, Vec<BuiltinName>), BuildError> {
198 let param_types = self.generic_id_and_size_from_concrete(&func.signature.param_types);
199 let return_types = self.generic_id_and_size_from_concrete(&func.signature.ret_types);
200
201 let entry_point = func.entry_point.0;
202 let code_offset =
203 self.casm_program.debug_info.sierra_statement_info[entry_point].start_offset;
204 let droppable_return_value = func.signature.ret_types.iter().all(|ty| {
206 let info = self.type_info(ty);
207 info.droppable || !self.is_user_arg_type(&info.long_id.generic_id)
208 });
209 if !droppable_return_value {
210 assert!(
211 !config.finalize_segment_arena,
212 "Cannot finalize the segment arena when returning non-droppable values."
213 );
214 }
215
216 create_entry_code_from_params(¶m_types, &return_types, code_offset, config)
217 }
218
219 pub fn generic_id_and_size_from_concrete(
221 &self,
222 types: &[ConcreteTypeId],
223 ) -> Vec<(GenericTypeId, i16)> {
224 types
225 .iter()
226 .map(|pt| {
227 let info = self.type_info(pt);
228 let generic_id = &info.long_id.generic_id;
229 let size = self.type_sizes[pt];
230 (generic_id.clone(), size)
231 })
232 .collect()
233 }
234}
235
236#[derive(Clone, Debug)]
238pub struct EntryCodeConfig {
239 pub finalize_segment_arena: bool,
241 pub outputting_function: bool,
247}
248impl EntryCodeConfig {
249 pub fn testing() -> Self {
254 Self { finalize_segment_arena: false, outputting_function: false }
255 }
256
257 pub fn executable() -> Self {
259 Self { finalize_segment_arena: true, outputting_function: true }
260 }
261}
262
263pub struct CasmProgramWrapperInfo {
265 pub builtins: Vec<BuiltinName>,
267 pub header: Vec<Instruction>,
269 pub footer: Vec<Instruction>,
271}
272
273pub fn create_entry_code_from_params(
278 param_types: &[(GenericTypeId, i16)],
279 return_types: &[(GenericTypeId, i16)],
280 code_offset: usize,
281 config: EntryCodeConfig,
282) -> Result<(Vec<Instruction>, Vec<BuiltinName>), BuildError> {
283 let mut ctx = CasmBuilder::default();
284 let mut builtin_offset = 3;
285 let mut builtin_vars = OrderedHashMap::<_, _>::default();
286 let mut builtin_ty_to_vm_name = UnorderedHashMap::<_, _>::default();
287 let mut builtins = vec![];
288 for (builtin_name, builtin_ty) in [
289 (BuiltinName::mul_mod, MulModType::ID),
290 (BuiltinName::add_mod, AddModType::ID),
291 (BuiltinName::range_check96, RangeCheck96Type::ID),
292 (BuiltinName::poseidon, PoseidonType::ID),
293 (BuiltinName::ec_op, EcOpType::ID),
294 (BuiltinName::bitwise, BitwiseType::ID),
295 (BuiltinName::range_check, RangeCheckType::ID),
296 (BuiltinName::pedersen, PedersenType::ID),
297 ] {
298 if param_types.iter().any(|(ty, _)| ty == &builtin_ty) {
299 builtin_vars.insert(
301 builtin_name,
302 ctx.add_var(CellExpression::Deref(deref!([fp - builtin_offset]))),
303 );
304 builtin_ty_to_vm_name.insert(builtin_ty.clone(), builtin_name);
305 builtin_offset += 1;
306 builtins.push(builtin_name);
307 }
308 }
309 if config.outputting_function {
310 let output_builtin_var = ctx.add_var(CellExpression::Deref(deref!([fp - builtin_offset])));
311 builtin_vars.insert(BuiltinName::output, output_builtin_var);
312 builtins.push(BuiltinName::output);
313 }
314 builtins.reverse();
315
316 let emulated_builtins = UnorderedHashSet::<_>::from_iter([SystemType::ID]);
317
318 let got_segment_arena = param_types.iter().any(|(ty, _)| ty == &SegmentArenaType::ID);
319 let has_post_calculation_loop = got_segment_arena && config.finalize_segment_arena;
320
321 let mut local_exprs = vec![];
322 if has_post_calculation_loop {
323 let mut allocate_cell = || {
324 casm_build_extend!(ctx, localvar local;);
325 local_exprs.push(ctx.get_value(local, false));
326 };
327 if !config.outputting_function {
330 for (ty, size) in return_types {
331 if !builtin_ty_to_vm_name.contains_key(ty) {
333 for _ in 0..*size {
334 allocate_cell();
335 }
336 }
337 }
338 }
339 for name in builtin_vars.keys() {
340 if name != &BuiltinName::segment_arena {
341 allocate_cell();
342 }
343 }
344 if !local_exprs.is_empty() {
345 casm_build_extend!(ctx, ap += local_exprs.len(););
346 }
347 }
348 if got_segment_arena {
349 casm_build_extend! {ctx,
350 tempvar segment_arena;
351 tempvar infos;
352 hint AllocSegment into {dst: segment_arena};
353 hint AllocSegment into {dst: infos};
354 const czero = 0;
355 tempvar zero = czero;
356 assert infos = *(segment_arena++);
358 assert zero = *(segment_arena++);
359 assert zero = *(segment_arena++);
360 }
361 builtin_vars.insert(BuiltinName::segment_arena, segment_arena);
363 builtin_ty_to_vm_name.insert(SegmentArenaType::ID, BuiltinName::segment_arena);
364 }
365 let mut unallocated_count = 0;
366 let mut param_index = 0;
367 for (generic_ty, ty_size) in param_types {
368 if let Some(name) = builtin_ty_to_vm_name.get(generic_ty).cloned() {
369 let var = builtin_vars[&name];
370 casm_build_extend!(ctx, tempvar _builtin = var;);
371 } else if emulated_builtins.contains(generic_ty) {
372 casm_build_extend! {ctx,
373 tempvar system;
374 hint AllocSegment into {dst: system};
375 };
376 unallocated_count += ty_size;
377 } else if !config.outputting_function {
378 if *ty_size > 0 {
379 casm_build_extend! {ctx,
380 tempvar first;
381 const param_index = param_index;
382 hint ExternalHint::WriteRunParam { index: param_index } into { dst: first };
383 };
384 for _ in 1..*ty_size {
385 casm_build_extend!(ctx, tempvar _cell;);
386 }
387 }
388 param_index += 1;
389 unallocated_count += ty_size;
390 } else if generic_ty == &GasBuiltinType::ID {
391 casm_build_extend! {ctx,
393 const max_gas = i64::MAX;
394 tempvar gas = max_gas;
395 };
396 }
397 }
398 if config.outputting_function {
399 let output_ptr = builtin_vars[&BuiltinName::output];
400 casm_build_extend! { ctx,
401 tempvar input_start;
402 tempvar _input_end;
403 const param_index = 0;
404 hint ExternalHint::WriteRunParam { index: param_index } into { dst: input_start };
405 const user_data_offset = 1;
406 tempvar output_start = output_ptr + user_data_offset;
407 tempvar output_end = output_start;
408 };
409 unallocated_count += 2;
410 }
411 if unallocated_count > 0 {
412 casm_build_extend!(ctx, ap += unallocated_count.into_or_panic::<usize>(););
413 }
414 casm_build_extend! (ctx, let () = call FUNCTION;);
415 let mut unprocessed_return_size = return_types.iter().map(|(_, size)| size).sum::<i16>();
416 let mut next_unprocessed_deref = || {
417 let deref_cell = CellExpression::Deref(deref!([ap - unprocessed_return_size]));
418 unprocessed_return_size -= 1;
419 deref_cell
420 };
421 let mut return_data = vec![];
422 for (ret_ty, size) in return_types {
423 if let Some(name) = builtin_ty_to_vm_name.get(ret_ty) {
424 *builtin_vars.get_mut(name).unwrap() = ctx.add_var(next_unprocessed_deref());
425 } else if config.outputting_function {
426 if ret_ty == &GasBuiltinType::ID {
427 next_unprocessed_deref();
428 continue;
429 }
430 let output_ptr_var = builtin_vars[&BuiltinName::output];
431 let new_output_ptr = if *size == 3 {
433 let panic_indicator = ctx.add_var(next_unprocessed_deref());
434 let ptr_start = ctx.add_var(next_unprocessed_deref());
437 let ptr_end = ctx.add_var(next_unprocessed_deref());
440 casm_build_extend! {ctx,
441 tempvar new_output_ptr;
442 jump PANIC if panic_indicator != 0;
443 assert new_output_ptr = ptr_end;
445 jump AFTER_PANIC_HANDLING;
446 PANIC:
447 hint ExternalHint::SetMarker { marker: ptr_start };
449 hint ExternalHint::SetMarker { marker: ptr_end };
450 const one = 1;
451 assert new_output_ptr = output_ptr_var + one;
453 AFTER_PANIC_HANDLING:
454 assert panic_indicator = output_ptr_var[0];
455 };
456 new_output_ptr
457 } else if *size == 2 {
458 next_unprocessed_deref();
460 let output_ptr_end = ctx.add_var(next_unprocessed_deref());
461 casm_build_extend! {ctx,
462 const czero = 0;
463 tempvar zero = czero;
464 assert zero = *(output_ptr_var++);
465 };
466 output_ptr_end
467 } else {
468 panic!("Unexpected output size: {size}",);
469 };
470 *builtin_vars.get_mut(&BuiltinName::output).unwrap() = new_output_ptr;
471 } else {
472 for _ in 0..*size {
473 return_data.push(ctx.add_var(next_unprocessed_deref()));
474 }
475 }
476 }
477 assert_eq!(unprocessed_return_size, 0);
478 if has_post_calculation_loop {
479 let saved_post_loop =
482 chain!(&return_data, builtin_vars.iter().filter_map(non_segment_arena_var));
483 for (cell, local_expr) in zip_eq(saved_post_loop.cloned(), &local_exprs) {
484 let local_cell = ctx.add_var(local_expr.clone());
485 casm_build_extend!(ctx, assert local_cell = cell;);
486 }
487 }
488 if got_segment_arena && config.finalize_segment_arena {
489 let segment_arena = builtin_vars[&BuiltinName::segment_arena];
490 casm_build_extend! {ctx,
492 tempvar n_segments = segment_arena[-2];
493 tempvar n_finalized = segment_arena[-1];
494 assert n_segments = n_finalized;
495 jump STILL_LEFT_PRE if n_segments != 0;
496 rescope{};
497 jump DONE_VALIDATION;
498 STILL_LEFT_PRE:
499 const one = 1;
500 tempvar infos = segment_arena[-3];
501 tempvar remaining_segments = n_segments - one;
502 rescope{infos = infos, remaining_segments = remaining_segments};
503 LOOP_START:
504 jump STILL_LEFT_LOOP if remaining_segments != 0;
505 rescope{};
506 jump DONE_VALIDATION;
507 STILL_LEFT_LOOP:
508 tempvar prev_end = infos[1];
509 tempvar curr_start = infos[3];
510 const one = 1;
511 let expected_curr_start = prev_end + one;
512 hint ExternalHint::AddRelocationRule { src: curr_start, dst: expected_curr_start };
513 assert curr_start = prev_end + one;
514 const three = 3;
515 tempvar next_infos = infos + three;
516 tempvar next_remaining_segments = remaining_segments - one;
517 rescope{infos = next_infos, remaining_segments = next_remaining_segments};
518 #{ steps = 0; }
519 jump LOOP_START;
520 DONE_VALIDATION:
521 };
522 }
523 if has_post_calculation_loop {
524 let mut locals = local_exprs.into_iter();
525 for _ in 0..return_data.len() {
528 let local_cell = ctx.add_var(locals.next().unwrap().clone());
529 casm_build_extend!(ctx, tempvar _cell = local_cell;);
530 }
531 for (var, local_expr) in
532 zip_eq(builtin_vars.iter_mut().filter_map(non_segment_arena_var), locals)
533 {
534 *var = ctx.add_var(local_expr);
535 }
536 }
537 if config.outputting_function {
538 for name in [
539 BuiltinName::output,
540 BuiltinName::pedersen,
541 BuiltinName::range_check,
542 BuiltinName::bitwise,
543 BuiltinName::ec_op,
544 BuiltinName::poseidon,
545 BuiltinName::range_check96,
546 BuiltinName::add_mod,
547 BuiltinName::mul_mod,
548 ] {
549 if let Some(var) = builtin_vars.get(&name).copied() {
550 casm_build_extend!(ctx, tempvar cell = var;);
551 }
552 }
553 }
554 casm_build_extend! (ctx, ret;);
555 ctx.future_label("FUNCTION".into(), code_offset);
556 Ok((ctx.build([]).instructions, builtins))
557}
558
559fn non_segment_arena_var<T>((name, var): (&BuiltinName, T)) -> Option<T> {
561 if *name == BuiltinName::segment_arena { None } else { Some(var) }
562}
563
564pub fn create_code_footer() -> Vec<Instruction> {
566 casm! {
567 ret;
570 }
571 .instructions
572}
573
574fn create_metadata(
576 sierra_program: &SierraProgram,
577 metadata_config: Option<MetadataComputationConfig>,
578) -> Result<Metadata, BuildError> {
579 if let Some(metadata_config) = metadata_config {
580 calc_metadata(sierra_program, metadata_config)
581 } else {
582 calc_metadata_ap_change_only(sierra_program)
583 }
584 .map_err(|err| match err {
585 MetadataError::ApChangeError(err) => BuildError::ApChangeError(err),
586 MetadataError::CostError(err) => BuildError::FailedGasCalculation(err),
587 })
588}