cairo_lang_sierra_to_casm/
compiler.rs

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/// Configuration for the Sierra to CASM compilation.
98#[derive(Debug, Eq, PartialEq, Clone, Copy)]
99pub struct SierraToCasmConfig {
100    /// Whether to check the gas usage of the program.
101    pub gas_usage_check: bool,
102    /// CASM bytecode size limit.
103    pub max_bytecode_size: usize,
104}
105
106/// The casm program representation.
107#[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    /// Creates an assembled representation of the program.
146    pub fn assemble(&self) -> AssembledCairoProgram {
147        self.assemble_ex(&[], &[])
148    }
149
150    /// Creates an assembled representation of the program preceded by `header` and followed by
151    /// `footer`.
152    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/// The debug information of a compilation from Sierra to casm.
188#[derive(Debug, Eq, PartialEq, Clone)]
189pub struct SierraStatementDebugInfo {
190    /// The start offset of the sierra statement within the bytecode.
191    pub start_offset: usize,
192    /// The end offset of the sierra statement within the bytecode.
193    pub end_offset: usize,
194    /// The index of the sierra statement in the instructions vector.
195    pub instruction_idx: usize,
196    /// Statement-kind-dependent information.
197    pub additional_kind_info: StatementKindDebugInfo,
198}
199
200/// Additional debug information for a Sierra statement, depending on its kind
201/// (invoke/return/dummy).
202#[derive(Debug, Eq, PartialEq, Clone)]
203pub enum StatementKindDebugInfo {
204    Return(ReturnStatementDebugInfo),
205    Invoke(InvokeStatementDebugInfo),
206}
207
208/// Additional debug information for a return Sierra statement.
209#[derive(Debug, Eq, PartialEq, Clone)]
210pub struct ReturnStatementDebugInfo {
211    /// The references of a Sierra return statement.
212    pub ref_values: Vec<ReferenceValue>,
213}
214
215/// Additional debug information for an invoke Sierra statement.
216#[derive(Debug, Eq, PartialEq, Clone)]
217pub struct InvokeStatementDebugInfo {
218    /// The result branch changes of a Sierra invoke statement.
219    pub result_branch_changes: Vec<BranchChanges>,
220    /// The references of a Sierra invoke statement.
221    pub ref_values: Vec<ReferenceValue>,
222}
223
224/// The debug information of a compilation from Sierra to casm.
225#[derive(Debug, Eq, PartialEq, Clone)]
226pub struct CairoProgramDebugInfo {
227    /// The debug information per Sierra statement.
228    pub sierra_statement_info: Vec<SierraStatementDebugInfo>,
229}
230
231/// The information about the constants used in the program.
232#[derive(Debug, Eq, PartialEq, Default, Clone)]
233pub struct ConstsInfo {
234    pub segments: OrderedHashMap<u32, ConstSegment>,
235    pub total_segments_size: usize,
236
237    /// Maps a circuit to its segment id.
238    pub circuit_segments: OrderedHashMap<ConcreteTypeId, u32>,
239}
240impl ConstsInfo {
241    /// Creates a new `ConstSegmentsInfo` from the given libfuncs.
242    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        // A lambda to add a const.
252        // Note that `segments` is passed as an argument to avoid taking borrowing it.
253        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        // Check that the segments were declared in order and without holes.
284        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            // Add 1 for the `ret` instruction.
320            total_segments_size += 1 + segment.values.len();
321        }
322        Ok(Self { segments, total_segments_size, circuit_segments })
323    }
324}
325
326/// The data for a single segment.
327#[derive(Debug, Eq, PartialEq, Default, Clone)]
328pub struct ConstSegment {
329    /// The values in the segment.
330    pub values: Vec<BigInt>,
331    /// The offset of each const within the segment.
332    pub const_offset: UnorderedHashMap<ConcreteTypeId, usize>,
333    /// The offset of the segment relative to the end of the code segment.
334    pub segment_offset: usize,
335}
336
337/// Gets a concrete type, if it is a const type returns a vector of the values to be stored in the
338/// const segment.
339fn 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                // Add the struct members' types to the stack in reverse order.
354                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                // The first argument is the variant selector, the second is the variant data.
363                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                        // Padding with zeros to full enum size.
374                        values.extend(itertools::repeat_n(
375                            BigInt::zero(),
376                            // Subtract 1 due to the variant selector.
377                            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
401/// Ensure the basic structure of the invocation is the same as the library function.
402pub 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
425/// Compiles `program` from Sierra to CASM using `metadata` for information regarding AP changes
426/// and gas usage, and config additional compilation flavours.
427pub 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    // Maps statement_idx to its debug info.
436    // The last value (for statement_idx=number-of-statements)
437    // contains the final offset (the size of the program code segment).
438    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, &registry, metadata)?;
447    let type_sizes = get_type_size_map(program, &registry)
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(&registry, 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, &param_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(&registry, &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                // Using a vector of annotations for the loop allows us to clone the annotations
591                // only in the case of more the 1 branch, which is less common.
592                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                            &registry,
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        &registry,
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
656/// Runs basic validations on the given metadata.
657pub fn validate_metadata(
658    program: &Program,
659    registry: &ProgramRegistry<CoreType, CoreLibfunc>,
660    metadata: &Metadata,
661) -> Result<(), CompilationError> {
662    // Function validations.
663    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    // Get the libfunc for the given statement index, or an error.
680    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    // Statement validations.
693    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
718/// Returns true if `statement` is an invocation of the branch_align libfunc.
719fn 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}