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