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