cairo_lang_sierra_to_casm/
references.rs

1use cairo_lang_casm::ap_change::ApplyApChange;
2use cairo_lang_casm::cell_expression::CellExpression;
3use cairo_lang_casm::operand::{CellRef, Register};
4use cairo_lang_sierra::ids::{ConcreteTypeId, VarId};
5use cairo_lang_sierra::program::{Function, StatementIdx};
6use cairo_lang_sierra_type_size::TypeSizeMap;
7use cairo_lang_utils::casts::IntoOrPanic;
8use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
9use cairo_lang_utils::write_comma_separated;
10use thiserror::Error;
11
12use crate::invocations::InvocationError;
13
14#[derive(Error, Debug, Eq, PartialEq)]
15pub enum ReferencesError {
16    #[error("Invalid function declaration.")]
17    InvalidFunctionDeclaration(Function),
18    #[error(
19        "One of the arguments does not match the expected type of the libfunc or return statement."
20    )]
21    InvalidReferenceTypeForArgument,
22    #[error("Unknown type `{0}`.")]
23    UnknownType(ConcreteTypeId),
24}
25
26pub type StatementRefs = OrderedHashMap<VarId, ReferenceValue>;
27
28/// A Sierra reference to a value.
29/// Corresponds to an argument or return value of a Sierra statement.
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct ReferenceValue {
32    pub expression: ReferenceExpression,
33    pub ty: ConcreteTypeId,
34    /// The index of the variable on the continuous-stack. 0 represents the oldest element on the
35    /// stack.
36    pub stack_idx: Option<usize>,
37    /// The location the value was introduced.
38    pub introduction_point: IntroductionPoint,
39}
40impl ReferenceValue {
41    /// Should never actually fail - since this was built by the type system.
42    /// This is just a sanity check, and therefore it panics instead of returning an error.
43    pub fn validate(&self, type_sizes: &TypeSizeMap) {
44        let size = *type_sizes.get(&self.ty).expect("ReferenceValue has unknown type");
45        let actual_size: i16 = self.expression.cells.len().into_or_panic();
46        assert_eq!(actual_size, size, "ReferenceValue type size mismatch.");
47    }
48}
49
50/// The location where a value was introduced.
51#[derive(Clone, Debug, Eq, PartialEq)]
52pub struct IntroductionPoint {
53    /// The index of the statement creating the value, None if introduced as a function param.
54    pub source_statement_idx: Option<StatementIdx>,
55    /// The index of the statement the value was created into.
56    pub destination_statement_idx: StatementIdx,
57    /// The output index of the generating statement of the var.
58    pub output_idx: usize,
59}
60
61impl core::fmt::Display for IntroductionPoint {
62    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63        if let Some(source_statement_idx) = self.source_statement_idx {
64            write!(
65                f,
66                "#{source_statement_idx}->#{}[{}]",
67                self.destination_statement_idx, self.output_idx
68            )
69        } else {
70            write!(f, "Function@{}[{}]", self.destination_statement_idx, self.output_idx)
71        }
72    }
73}
74
75/// A Sierra reference to a value.
76/// Returned from a libfunc.
77#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct OutputReferenceValue {
79    pub expression: ReferenceExpression,
80    pub ty: ConcreteTypeId,
81    /// The index of the variable on the continuous-stack.
82    pub stack_idx: Option<usize>,
83    /// The statement and output index where the value was introduced.
84    /// Statement may be New if it is to be populated later.
85    pub introduction_point: OutputReferenceValueIntroductionPoint,
86}
87
88/// The location where a value was introduced for output reference values.
89#[derive(Clone, Debug, Eq, PartialEq)]
90pub enum OutputReferenceValueIntroductionPoint {
91    /// A new point introduced by a libfunc. The inner value is the output index.
92    New(usize),
93    /// Some other known value.
94    Existing(IntroductionPoint),
95}
96
97/// A collection of Cell Expression which represents one logical object.
98#[derive(Clone, Debug, Eq, PartialEq)]
99pub struct ReferenceExpression {
100    pub cells: Vec<CellExpression>,
101}
102
103impl ReferenceExpression {
104    /// Builds a reference expression containing only a single cell
105    pub fn from_cell(cell_expr: CellExpression) -> Self {
106        Self { cells: vec![cell_expr] }
107    }
108
109    /// Builds a zero-sized reference expression.
110    pub fn zero_sized() -> Self {
111        Self { cells: vec![] }
112    }
113
114    /// If returns the cells as an array of the requested size if the size is correct.
115    pub fn try_unpack<const SIZE: usize>(
116        &self,
117    ) -> Result<&[CellExpression; SIZE], InvocationError> {
118        <&[CellExpression; SIZE]>::try_from(&self.cells[..])
119            .map_err(|_| InvocationError::InvalidReferenceExpressionForArgument)
120    }
121
122    /// If there is only one cell in the ReferenceExpression returns the contained CellExpression.
123    pub fn try_unpack_single(&self) -> Result<&CellExpression, InvocationError> {
124        Ok(&self.try_unpack::<1>()?[0])
125    }
126}
127
128impl ApplyApChange for ReferenceExpression {
129    fn apply_known_ap_change(self, ap_change: usize) -> Option<Self> {
130        Some(ReferenceExpression {
131            cells: self
132                .cells
133                .into_iter()
134                .map(|cell_expr| cell_expr.apply_known_ap_change(ap_change))
135                .collect::<Option<Vec<_>>>()?,
136        })
137    }
138
139    fn can_apply_unknown(&self) -> bool {
140        self.cells.iter().all(|cell| cell.can_apply_unknown())
141    }
142}
143
144impl core::fmt::Display for ReferenceExpression {
145    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
146        write!(f, "[")?;
147        write_comma_separated(f, &self.cells)?;
148        write!(f, "]")
149    }
150}
151
152/// Builds the HashMap of references to the parameters of a function.
153pub fn build_function_parameters_refs(
154    func: &Function,
155    type_sizes: &TypeSizeMap,
156) -> Result<StatementRefs, ReferencesError> {
157    let mut refs = StatementRefs::default();
158    let mut offset = -3_i16;
159    for (param_idx, param) in func.params.iter().rev().enumerate() {
160        let size = type_sizes
161            .get(&param.ty)
162            .ok_or_else(|| ReferencesError::UnknownType(param.ty.clone()))?;
163        if refs
164            .insert(param.id.clone(), ReferenceValue {
165                expression: ReferenceExpression {
166                    cells: ((offset - size + 1)..(offset + 1))
167                        .map(|i| {
168                            CellExpression::Deref(CellRef { register: Register::FP, offset: i })
169                        })
170                        .collect(),
171                },
172                ty: param.ty.clone(),
173                stack_idx: None,
174                introduction_point: IntroductionPoint {
175                    source_statement_idx: None,
176                    destination_statement_idx: func.entry_point,
177                    output_idx: param_idx,
178                },
179            })
180            .is_some()
181        {
182            return Err(ReferencesError::InvalidFunctionDeclaration(func.clone()));
183        }
184        offset -= size;
185    }
186    Ok(refs)
187}
188
189/// Checks that the list of references contains types matching the given types.
190pub fn check_types_match(
191    refs: &[ReferenceValue],
192    types: &[ConcreteTypeId],
193) -> Result<(), ReferencesError> {
194    if itertools::equal(types.iter(), refs.iter().map(|r| &r.ty)) {
195        Ok(())
196    } else {
197        Err(ReferencesError::InvalidReferenceTypeForArgument)
198    }
199}