1use std::fmt::Display;
2
3use cairo_lang_casm::assembler::AssembledCairoProgram;
4use cairo_lang_casm::instructions::{Instruction, InstructionBody, RetInstruction};
5use cairo_lang_sierra::extensions::ConcreteLibfunc;
6use cairo_lang_sierra::extensions::circuit::{CircuitConcreteLibfunc, CircuitInfo, VALUE_SIZE};
7use cairo_lang_sierra::extensions::const_type::ConstConcreteLibfunc;
8use cairo_lang_sierra::extensions::core::{
9 CoreConcreteLibfunc, CoreLibfunc, CoreType, CoreTypeConcrete,
10};
11use cairo_lang_sierra::extensions::coupon::CouponConcreteLibfunc;
12use cairo_lang_sierra::extensions::gas::GasConcreteLibfunc;
13use cairo_lang_sierra::extensions::lib_func::SierraApChange;
14use cairo_lang_sierra::ids::{ConcreteLibfuncId, ConcreteTypeId, VarId};
15use cairo_lang_sierra::program::{
16 BranchTarget, GenericArg, Invocation, Program, Statement, StatementIdx,
17};
18use cairo_lang_sierra::program_registry::{ProgramRegistry, ProgramRegistryError};
19use cairo_lang_sierra_type_size::ProgramRegistryInfo;
20use cairo_lang_utils::casts::IntoOrPanic;
21use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
22use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
23use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
24use itertools::{chain, zip_eq};
25use num_bigint::BigInt;
26use num_traits::{ToPrimitive, Zero};
27use thiserror::Error;
28
29use crate::annotations::{AnnotationError, ProgramAnnotations, StatementAnnotations};
30use crate::circuit::CircuitsInfo;
31use crate::invocations::enm::get_variant_selector;
32use crate::invocations::{
33 BranchChanges, InvocationError, ProgramInfo, check_references_on_stack, compile_invocation,
34};
35use crate::metadata::Metadata;
36use crate::references::{ReferenceValue, ReferencesError, check_types_match};
37use crate::relocations::{RelocationEntry, relocate_instructions};
38
39#[cfg(test)]
40#[path = "compiler_test.rs"]
41mod test;
42
43#[derive(Error, Debug, Eq, PartialEq)]
44pub enum CompilationError {
45 #[error("Error from program registry: {0}")]
46 ProgramRegistryError(Box<ProgramRegistryError>),
47 #[error(transparent)]
48 AnnotationError(#[from] AnnotationError),
49 #[error("#{statement_idx}: {error}")]
50 InvocationError { statement_idx: StatementIdx, error: InvocationError },
51 #[error("#{statement_idx}: Return arguments are not on the stack.")]
52 ReturnArgumentsNotOnStack { statement_idx: StatementIdx },
53 #[error("#{statement_idx}: {error}")]
54 ReferencesError { statement_idx: StatementIdx, error: ReferencesError },
55 #[error("#{statement_idx}: Invocation mismatched to libfunc")]
56 LibfuncInvocationMismatch { statement_idx: StatementIdx },
57 #[error("{var_id} is dangling at #{statement_idx}.")]
58 DanglingReferences { statement_idx: StatementIdx, var_id: VarId },
59 #[error("#{source_statement_idx}->#{destination_statement_idx}: Expected branch align")]
60 ExpectedBranchAlign {
61 source_statement_idx: StatementIdx,
62 destination_statement_idx: StatementIdx,
63 },
64 #[error("Const data does not match the declared const type.")]
65 ConstDataMismatch,
66 #[error("Unsupported const type.")]
67 UnsupportedConstType,
68 #[error("Unsupported circuit type.")]
69 UnsupportedCircuitType,
70 #[error("Const segments must appear in ascending order without holes.")]
71 ConstSegmentsOutOfOrder,
72 #[error("Code size limit exceeded.")]
73 CodeSizeLimitExceeded,
74 #[error("Unknown function id in metadata.")]
75 MetadataUnknownFunctionId,
76 #[error("Statement #{0} out of bounds in metadata.")]
77 MetadataStatementOutOfBound(StatementIdx),
78 #[error("Statement #{0} should not have gas variables.")]
79 StatementNotSupportingGasVariables(StatementIdx),
80 #[error("Statement #{0} should not have ap-change variables.")]
81 StatementNotSupportingApChangeVariables(StatementIdx),
82 #[error("Expected all gas variables to be positive.")]
83 MetadataNegativeGasVariable,
84}
85
86impl CompilationError {
87 pub fn stmt_indices(&self) -> Vec<StatementIdx> {
88 match self {
89 CompilationError::AnnotationError(err) => err.stmt_indices(),
90 _ => vec![],
91 }
92 }
93}
94
95#[derive(Debug, Eq, PartialEq, Clone, Copy)]
97pub struct SierraToCasmConfig {
98 pub gas_usage_check: bool,
100 pub max_bytecode_size: usize,
102}
103
104#[derive(Debug, Eq, PartialEq, Clone)]
106pub struct CairoProgram {
107 pub instructions: Vec<Instruction>,
108 pub debug_info: CairoProgramDebugInfo,
109 pub consts_info: ConstsInfo,
110}
111impl Display for CairoProgram {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 if std::env::var("PRINT_CASM_BYTECODE_OFFSETS").is_ok() {
114 let mut bytecode_offset = 0;
115 for instruction in &self.instructions {
116 writeln!(f, "{instruction}; // {bytecode_offset}")?;
117 bytecode_offset += instruction.body.op_size();
118 }
119 for segment in self.consts_info.segments.values() {
120 writeln!(f, "ret; // {bytecode_offset}")?;
121 bytecode_offset += 1;
122 for value in &segment.values {
123 writeln!(f, "dw {value}; // {bytecode_offset}")?;
124 bytecode_offset += 1;
125 }
126 }
127 } else {
128 for instruction in &self.instructions {
129 writeln!(f, "{instruction};")?;
130 }
131 for segment in self.consts_info.segments.values() {
132 writeln!(f, "ret;")?;
133 for value in &segment.values {
134 writeln!(f, "dw {value};")?;
135 }
136 }
137 }
138 Ok(())
139 }
140}
141
142impl CairoProgram {
143 pub fn assemble(&self) -> AssembledCairoProgram {
145 self.assemble_ex(&[], &[])
146 }
147
148 pub fn assemble_ex<'a>(
151 &'a self,
152 header: impl IntoIterator<Item = &'a Instruction>,
153 footer: &[Instruction],
154 ) -> AssembledCairoProgram {
155 let mut bytecode = vec![];
156 let mut hints = vec![];
157 for instruction in chain!(header, &self.instructions) {
158 if !instruction.hints.is_empty() {
159 hints.push((bytecode.len(), instruction.hints.clone()))
160 }
161 bytecode.extend(instruction.assemble().encode().into_iter())
162 }
163 let [ref ret_bytecode] = Instruction::new(InstructionBody::Ret(RetInstruction {}), false)
164 .assemble()
165 .encode()[..]
166 else {
167 panic!("`ret` instruction should be a single word.")
168 };
169 for segment in self.consts_info.segments.values() {
170 bytecode.push(ret_bytecode.clone());
171 bytecode.extend(segment.values.clone());
172 }
173 for instruction in footer {
174 assert!(
175 instruction.hints.is_empty(),
176 "All footer instructions must have no hints since these cannot be added to the \
177 hints dict."
178 );
179 bytecode.extend(instruction.assemble().encode().into_iter())
180 }
181 AssembledCairoProgram { bytecode, hints }
182 }
183
184 pub fn sierra_statement_index_by_pc(&self, pc: usize) -> StatementIdx {
186 StatementIdx(
190 self.debug_info.sierra_statement_info.partition_point(|x| x.start_offset <= pc) - 1,
191 )
192 }
193}
194
195#[derive(Debug, Eq, PartialEq, Clone)]
197pub struct SierraStatementDebugInfo {
198 pub start_offset: usize,
200 pub end_offset: usize,
202 pub instruction_idx: usize,
204 pub additional_kind_info: StatementKindDebugInfo,
206}
207
208#[derive(Debug, Eq, PartialEq, Clone)]
211pub enum StatementKindDebugInfo {
212 Return(ReturnStatementDebugInfo),
213 Invoke(InvokeStatementDebugInfo),
214}
215
216#[derive(Debug, Eq, PartialEq, Clone)]
218pub struct ReturnStatementDebugInfo {
219 pub ref_values: Vec<ReferenceValue>,
221}
222
223#[derive(Debug, Eq, PartialEq, Clone)]
225pub struct InvokeStatementDebugInfo {
226 pub result_branch_changes: Vec<BranchChanges>,
228 pub ref_values: Vec<ReferenceValue>,
230}
231
232#[derive(Debug, Eq, PartialEq, Clone)]
234pub struct CairoProgramDebugInfo {
235 pub sierra_statement_info: Vec<SierraStatementDebugInfo>,
237}
238
239#[derive(Debug, Eq, PartialEq, Default, Clone)]
241pub struct ConstsInfo {
242 pub segments: OrderedHashMap<u32, ConstSegment>,
243 pub total_segments_size: usize,
244
245 pub circuit_segments: OrderedHashMap<ConcreteTypeId, u32>,
247}
248impl ConstsInfo {
249 pub fn new<'a>(
251 program_info: &ProgramRegistryInfo,
252 libfunc_ids: impl Iterator<Item = &'a ConcreteLibfuncId> + Clone,
253 circuit_infos: &OrderedHashMap<ConcreteTypeId, CircuitInfo>,
254 const_segments_max_size: usize,
255 ) -> Result<Self, CompilationError> {
256 let mut segments_data_size = 0;
257
258 let mut add_const = |segments: &mut OrderedHashMap<u32, ConstSegment>,
261 segment_id,
262 ty,
263 const_data: Vec<BigInt>| {
264 let segment: &mut ConstSegment = segments.entry(segment_id).or_default();
265
266 segments_data_size += const_data.len();
267 segment.const_offset.insert(ty, segment.values.len());
268 segment.values.extend(const_data);
269 if segments_data_size + segments.len() > const_segments_max_size {
270 return Err(CompilationError::CodeSizeLimitExceeded);
271 }
272 Ok(())
273 };
274
275 let mut segments = OrderedHashMap::default();
276
277 for id in libfunc_ids.clone() {
278 if let CoreConcreteLibfunc::Const(ConstConcreteLibfunc::AsBox(as_box)) =
279 program_info.registry.get_libfunc(id).unwrap()
280 {
281 add_const(
282 &mut segments,
283 as_box.segment_id,
284 as_box.const_type.clone(),
285 extract_const_value(program_info, &as_box.const_type).unwrap(),
286 )?;
287 }
288 }
289
290 if segments
292 .keys()
293 .enumerate()
294 .any(|(i, segment_id)| i != segment_id.into_or_panic::<usize>())
295 {
296 return Err(CompilationError::ConstSegmentsOutOfOrder);
297 }
298
299 let mut next_segment = segments.len() as u32;
300 let mut circuit_segments = OrderedHashMap::default();
301
302 for id in libfunc_ids {
303 if let CoreConcreteLibfunc::Circuit(CircuitConcreteLibfunc::GetDescriptor(libfunc)) =
304 program_info.registry.get_libfunc(id).unwrap()
305 {
306 let circ_ty = &libfunc.ty;
307 let info = circuit_infos.get(circ_ty).unwrap();
308 let mut const_value: Vec<BigInt> = vec![];
309 let mut push_offset =
310 |offset: usize| const_value.push((offset * VALUE_SIZE).into());
311 for gate_offsets in chain!(info.add_offsets.iter(), info.mul_offsets.iter()) {
312 push_offset(gate_offsets.lhs);
313 push_offset(gate_offsets.rhs);
314 push_offset(gate_offsets.output);
315 }
316
317 add_const(&mut segments, next_segment, circ_ty.clone(), const_value)?;
318 circuit_segments.insert(circ_ty.clone(), next_segment);
319 next_segment += 1;
320 }
321 }
322
323 let mut total_segments_size = 0;
324 for (_, segment) in segments.iter_mut() {
325 segment.segment_offset = total_segments_size;
326 total_segments_size += 1 + segment.values.len();
328 }
329 Ok(Self { segments, total_segments_size, circuit_segments })
330 }
331}
332
333#[derive(Debug, Eq, PartialEq, Default, Clone)]
335pub struct ConstSegment {
336 pub values: Vec<BigInt>,
338 pub const_offset: UnorderedHashMap<ConcreteTypeId, usize>,
340 pub segment_offset: usize,
342}
343
344fn extract_const_value(
347 program_info: &ProgramRegistryInfo,
348 ty: &ConcreteTypeId,
349) -> Result<Vec<BigInt>, CompilationError> {
350 let mut values = Vec::new();
351 let mut types_stack = vec![ty.clone()];
352 while let Some(ty) = types_stack.pop() {
353 let CoreTypeConcrete::Const(const_type) = program_info.registry.get_type(&ty).unwrap()
354 else {
355 return Err(CompilationError::UnsupportedConstType);
356 };
357 let inner_type = program_info.registry.get_type(&const_type.inner_ty).unwrap();
358 match inner_type {
359 CoreTypeConcrete::Struct(_) => {
360 for arg in const_type.inner_data.iter().rev() {
362 match arg {
363 GenericArg::Type(arg_ty) => types_stack.push(arg_ty.clone()),
364 _ => return Err(CompilationError::ConstDataMismatch),
365 }
366 }
367 }
368 CoreTypeConcrete::Enum(enm) => {
369 match &const_type.inner_data[..] {
371 [GenericArg::Value(variant_index), GenericArg::Type(ty)] => {
372 let variant_index = variant_index.to_usize().unwrap();
373 values.push(
374 get_variant_selector(enm.variants.len(), variant_index).unwrap().into(),
375 );
376 let full_enum_size: usize =
377 program_info.type_sizes[&const_type.inner_ty].into_or_panic();
378 let variant_size: usize =
379 program_info.type_sizes[&enm.variants[variant_index]].into_or_panic();
380 values.extend(itertools::repeat_n(
382 BigInt::zero(),
383 full_enum_size - variant_size - 1,
385 ));
386 types_stack.push(ty.clone());
387 }
388 _ => return Err(CompilationError::ConstDataMismatch),
389 }
390 }
391 CoreTypeConcrete::NonZero(_) => match &const_type.inner_data[..] {
392 [GenericArg::Type(inner)] => {
393 types_stack.push(inner.clone());
394 }
395 _ => return Err(CompilationError::ConstDataMismatch),
396 },
397 CoreTypeConcrete::EcPoint(_) => match &const_type.inner_data[..] {
398 [GenericArg::Value(x), GenericArg::Value(y)] => {
399 values.push(x.clone());
400 values.push(y.clone());
401 }
402 _ => return Err(CompilationError::ConstDataMismatch),
403 },
404 _ => match &const_type.inner_data[..] {
405 [GenericArg::Value(value)] => {
406 values.push(value.clone());
407 }
408 _ => return Err(CompilationError::ConstDataMismatch),
409 },
410 };
411 }
412 Ok(values)
413}
414
415pub fn check_basic_structure(
417 statement_idx: StatementIdx,
418 invocation: &Invocation,
419 libfunc: &CoreConcreteLibfunc,
420) -> Result<(), CompilationError> {
421 if invocation.args.len() != libfunc.param_signatures().len()
422 || !itertools::equal(
423 invocation.branches.iter().map(|branch| branch.results.len()),
424 libfunc.output_types().iter().map(|types| types.len()),
425 )
426 || match libfunc.fallthrough() {
427 Some(expected_fallthrough) => {
428 invocation.branches[expected_fallthrough].target != BranchTarget::Fallthrough
429 }
430 None => false,
431 }
432 {
433 Err(CompilationError::LibfuncInvocationMismatch { statement_idx })
434 } else {
435 Ok(())
436 }
437}
438
439pub fn compile(
442 program: &Program,
443 program_info: &ProgramRegistryInfo,
444 metadata: &Metadata,
445 config: SierraToCasmConfig,
446) -> Result<CairoProgram, Box<CompilationError>> {
447 let mut instructions = Vec::new();
448 let mut relocations: Vec<RelocationEntry> = Vec::new();
449
450 let mut sierra_statement_info: Vec<SierraStatementDebugInfo> =
454 Vec::with_capacity(program.statements.len());
455
456 validate_metadata(program, &program_info.registry, metadata)?;
457 let mut backwards_jump_indices = UnorderedHashSet::<_>::default();
458 for (statement_id, statement) in program.statements.iter().enumerate() {
459 if let Statement::Invocation(invocation) = statement {
460 for branch in &invocation.branches {
461 if let BranchTarget::Statement(target) = branch.target
462 && target.0 < statement_id
463 {
464 backwards_jump_indices.insert(target);
465 }
466 }
467 }
468 }
469 let mut program_annotations = ProgramAnnotations::create(
470 program.statements.len(),
471 backwards_jump_indices,
472 &program.funcs,
473 metadata,
474 config.gas_usage_check,
475 &program_info.type_sizes,
476 )
477 .map_err(|err| Box::new(err.into()))?;
478
479 let circuits_info = CircuitsInfo::new(
480 &program_info.registry,
481 program.type_declarations.iter().map(|td| &td.id),
482 )?;
483
484 let mut program_offset: usize = 0;
485 for (statement_id, statement) in program.statements.iter().enumerate() {
486 let statement_idx = StatementIdx(statement_id);
487
488 if program_offset > config.max_bytecode_size {
489 return Err(Box::new(CompilationError::CodeSizeLimitExceeded));
490 }
491 match statement {
492 Statement::Return(ref_ids) => {
493 let (annotations, return_refs) = program_annotations
494 .get_annotations_after_take_args(statement_idx, ref_ids.iter())
495 .map_err(|err| Box::new(err.into()))?;
496 return_refs.iter().for_each(|r| r.validate(&program_info.type_sizes));
497
498 if let Some(var_id) = annotations.refs.keys().next() {
499 return Err(Box::new(CompilationError::DanglingReferences {
500 statement_idx,
501 var_id: var_id.clone(),
502 }));
503 };
504
505 program_annotations
506 .validate_final_annotations(
507 statement_idx,
508 &annotations,
509 &program.funcs,
510 metadata,
511 &return_refs,
512 )
513 .map_err(|err| Box::new(err.into()))?;
514 check_references_on_stack(&return_refs).map_err(|error| match error {
515 InvocationError::InvalidReferenceExpressionForArgument => {
516 CompilationError::ReturnArgumentsNotOnStack { statement_idx }
517 }
518 _ => CompilationError::InvocationError { statement_idx, error },
519 })?;
520
521 let start_offset = program_offset;
522
523 let ret_instruction = RetInstruction {};
524 program_offset += ret_instruction.op_size();
525
526 sierra_statement_info.push(SierraStatementDebugInfo {
527 start_offset,
528 end_offset: program_offset,
529 instruction_idx: instructions.len(),
530 additional_kind_info: StatementKindDebugInfo::Return(
531 ReturnStatementDebugInfo { ref_values: return_refs },
532 ),
533 });
534
535 instructions.push(Instruction::new(InstructionBody::Ret(ret_instruction), false));
536 }
537 Statement::Invocation(invocation) => {
538 let (annotations, invoke_refs) = program_annotations
539 .get_annotations_after_take_args(statement_idx, invocation.args.iter())
540 .map_err(|err| Box::new(err.into()))?;
541
542 let libfunc = program_info
543 .registry
544 .get_libfunc(&invocation.libfunc_id)
545 .map_err(CompilationError::ProgramRegistryError)?;
546 check_basic_structure(statement_idx, invocation, libfunc)?;
547
548 let param_types: Vec<_> = libfunc
549 .param_signatures()
550 .iter()
551 .map(|param_signature| param_signature.ty.clone())
552 .collect();
553 check_types_match(&invoke_refs, ¶m_types).map_err(|error| {
554 Box::new(AnnotationError::ReferencesError { statement_idx, error }.into())
555 })?;
556 invoke_refs.iter().for_each(|r| r.validate(&program_info.type_sizes));
557 let compiled_invocation = compile_invocation(
558 ProgramInfo {
559 metadata,
560 type_sizes: &program_info.type_sizes,
561 circuits_info: &circuits_info,
562 const_data_values: &|ty| extract_const_value(program_info, ty).unwrap(),
563 },
564 invocation,
565 libfunc,
566 statement_idx,
567 &invoke_refs,
568 annotations.environment,
569 )
570 .map_err(|error| CompilationError::InvocationError { statement_idx, error })?;
571
572 let start_offset = program_offset;
573
574 for instruction in &compiled_invocation.instructions {
575 program_offset += instruction.body.op_size();
576 }
577
578 sierra_statement_info.push(SierraStatementDebugInfo {
579 start_offset,
580 end_offset: program_offset,
581 instruction_idx: instructions.len(),
582 additional_kind_info: StatementKindDebugInfo::Invoke(
583 InvokeStatementDebugInfo {
584 result_branch_changes: compiled_invocation.results.clone(),
585 ref_values: invoke_refs,
586 },
587 ),
588 });
589
590 for entry in compiled_invocation.relocations {
591 relocations.push(RelocationEntry {
592 instruction_idx: instructions.len() + entry.instruction_idx,
593 relocation: entry.relocation,
594 });
595 }
596 instructions.extend(compiled_invocation.instructions);
597
598 let branching_libfunc = compiled_invocation.results.len() > 1;
599 let mut all_updated_annotations = vec![StatementAnnotations {
602 environment: compiled_invocation.environment,
603 ..annotations
604 }];
605 while all_updated_annotations.len() < compiled_invocation.results.len() {
606 all_updated_annotations.push(all_updated_annotations[0].clone());
607 }
608
609 for ((branch_info, branch_changes), updated_annotations) in
610 zip_eq(&invocation.branches, compiled_invocation.results)
611 .zip(all_updated_annotations)
612 {
613 let destination_statement_idx = statement_idx.next(&branch_info.target);
614 if branching_libfunc
615 && !is_branch_align(
616 &program_info.registry,
617 &program.statements[destination_statement_idx.0],
618 )?
619 {
620 return Err(Box::new(CompilationError::ExpectedBranchAlign {
621 source_statement_idx: statement_idx,
622 destination_statement_idx,
623 }));
624 }
625
626 program_annotations
627 .propagate_annotations(
628 statement_idx,
629 destination_statement_idx,
630 updated_annotations,
631 branch_info,
632 branch_changes,
633 branching_libfunc,
634 )
635 .map_err(|err| Box::new(err.into()))?;
636 }
637 }
638 }
639 }
640
641 let statement_offsets: Vec<usize> = std::iter::once(0)
642 .chain(sierra_statement_info.iter().map(|s: &SierraStatementDebugInfo| s.end_offset))
643 .collect();
644
645 let const_segments_max_size = config
646 .max_bytecode_size
647 .checked_sub(program_offset)
648 .ok_or_else(|| Box::new(CompilationError::CodeSizeLimitExceeded))?;
649 let consts_info = ConstsInfo::new(
650 program_info,
651 program.libfunc_declarations.iter().map(|ld| &ld.id),
652 &circuits_info.circuits,
653 const_segments_max_size,
654 )?;
655 relocate_instructions(&relocations, &statement_offsets, &consts_info, &mut instructions);
656
657 Ok(CairoProgram {
658 instructions,
659 consts_info,
660 debug_info: CairoProgramDebugInfo { sierra_statement_info },
661 })
662}
663
664pub fn validate_metadata(
666 program: &Program,
667 registry: &ProgramRegistry<CoreType, CoreLibfunc>,
668 metadata: &Metadata,
669) -> Result<(), CompilationError> {
670 for function_id in metadata.ap_change_info.function_ap_change.keys() {
672 registry
673 .get_function(function_id)
674 .map_err(|_| CompilationError::MetadataUnknownFunctionId)?;
675 }
676 for (function_id, costs) in metadata.gas_info.function_costs.iter() {
677 registry
678 .get_function(function_id)
679 .map_err(|_| CompilationError::MetadataUnknownFunctionId)?;
680 for (_token_type, value) in costs.iter() {
681 if *value < 0 {
682 return Err(CompilationError::MetadataNegativeGasVariable);
683 }
684 }
685 }
686
687 let get_libfunc = |idx: &StatementIdx| -> Result<&CoreConcreteLibfunc, CompilationError> {
689 if let Statement::Invocation(invocation) =
690 program.get_statement(idx).ok_or(CompilationError::MetadataStatementOutOfBound(*idx))?
691 {
692 registry
693 .get_libfunc(&invocation.libfunc_id)
694 .map_err(CompilationError::ProgramRegistryError)
695 } else {
696 Err(CompilationError::StatementNotSupportingApChangeVariables(*idx))
697 }
698 };
699
700 for idx in metadata.ap_change_info.variable_values.keys() {
702 if !matches!(get_libfunc(idx)?, CoreConcreteLibfunc::BranchAlign(_)) {
703 return Err(CompilationError::StatementNotSupportingApChangeVariables(*idx));
704 }
705 }
706 for ((idx, _token), value) in metadata.gas_info.variable_values.iter() {
707 if *value < 0 {
708 return Err(CompilationError::MetadataNegativeGasVariable);
709 }
710 if !matches!(
711 get_libfunc(idx)?,
712 CoreConcreteLibfunc::BranchAlign(_)
713 | CoreConcreteLibfunc::Coupon(CouponConcreteLibfunc::Refund(_))
714 | CoreConcreteLibfunc::Gas(
715 GasConcreteLibfunc::WithdrawGas(_)
716 | GasConcreteLibfunc::BuiltinWithdrawGas(_)
717 | GasConcreteLibfunc::RedepositGas(_)
718 )
719 ) {
720 return Err(CompilationError::StatementNotSupportingGasVariables(*idx));
721 }
722 }
723 Ok(())
724}
725
726fn is_branch_align(
728 registry: &ProgramRegistry<CoreType, CoreLibfunc>,
729 statement: &Statement,
730) -> Result<bool, CompilationError> {
731 if let Statement::Invocation(invocation) = statement {
732 let libfunc = registry
733 .get_libfunc(&invocation.libfunc_id)
734 .map_err(CompilationError::ProgramRegistryError)?;
735 if let [branch_signature] = libfunc.branch_signatures()
736 && branch_signature.ap_change == SierraApChange::BranchAlign
737 {
738 return Ok(true);
739 }
740 }
741
742 Ok(false)
743}