cairo_lang_lowering/
objects.rs

1//! Intermediate representation objects after lowering from semantic.
2//!
3//! This representation is SSA (static single-assignment): each variable is defined before usage and
4//! assigned once. It is also normal form: each function argument is a variable, rather than a
5//! compound expression.
6
7use std::ops::{Deref, DerefMut};
8
9use cairo_lang_debug::DebugWithDb;
10use cairo_lang_defs::diagnostic_utils::StableLocation;
11use cairo_lang_diagnostics::{DiagnosticNote, Diagnostics};
12use cairo_lang_semantic as semantic;
13use cairo_lang_semantic::corelib::{concrete_destruct_trait, concrete_panic_destruct_trait};
14use cairo_lang_semantic::expr::inference::InferenceError;
15use cairo_lang_semantic::expr::inference::solver::Ambiguity;
16use cairo_lang_semantic::items::constant::ConstValueId;
17use cairo_lang_semantic::items::imp::ImplLookupContextId;
18use cairo_lang_semantic::types::{TypeInfo, TypesSemantic};
19use cairo_lang_semantic::{ConcreteEnumId, ConcreteVariant};
20use cairo_lang_utils::Intern;
21use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
22use id_arena::{Arena, DefaultArenaBehavior, Id};
23
24pub mod blocks;
25pub use blocks::BlockId;
26use salsa::Database;
27use semantic::MatchArmSelector;
28
29use self::blocks::Blocks;
30use crate::diagnostic::LoweringDiagnostic;
31use crate::fmt::LoweredFormatter;
32use crate::ids::{FunctionId, LocationId, Signature};
33
34/// The Location struct represents the source location of a lowered object. It is used to store the
35/// most relevant source location for a lowering object.
36#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
37pub struct Location<'db> {
38    /// The stable location of the object.
39    pub stable_location: StableLocation<'db>,
40    /// Additional notes about the origin of the object, for example if the object was
41    /// auto-generated by the compiler.
42    /// New notes are appended to the end of the vector.
43    pub notes: Vec<DiagnosticNote<'db>>,
44    /// Function call locations where this value was inlined from.
45    pub inline_locations: Vec<StableLocation<'db>>,
46}
47impl<'db> Location<'db> {
48    pub fn new(stable_location: StableLocation<'db>) -> Self {
49        Self { stable_location, notes: vec![], inline_locations: vec![] }
50    }
51
52    /// Creates a new Location with the given note as the last note.
53    pub fn with_note(mut self, note: DiagnosticNote<'db>) -> Self {
54        self.notes.push(note);
55        self
56    }
57
58    /// Creates a new Location with the given note as the last note.
59    pub fn maybe_with_note(mut self, note: Option<DiagnosticNote<'db>>) -> Self {
60        let Some(note) = note else {
61            return self;
62        };
63        self.notes.push(note);
64        self
65    }
66
67    /// Creates a new Location with a note from the given text and location.
68    pub fn add_note_with_location(
69        self,
70        db: &'db dyn Database,
71        text: &str,
72        location: LocationId<'db>,
73    ) -> Self {
74        self.with_note(DiagnosticNote::with_location(
75            text.into(),
76            location.long(db).stable_location.span_in_file(db),
77        ))
78    }
79}
80
81impl<'db> DebugWithDb<'db> for Location<'db> {
82    type Db = dyn Database;
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db dyn Database) -> std::fmt::Result {
84        self.stable_location.span_in_file(db).fmt(f, db)?;
85
86        for note in &self.notes {
87            f.write_str("\nnote: ")?;
88            note.fmt(f, db)?;
89        }
90        Ok(())
91    }
92}
93
94impl<'db> LocationId<'db> {
95    /// Returns the location with the added inlining location of it.
96    pub fn inlined(self, db: &'db dyn Database, inlining_location: StableLocation<'db>) -> Self {
97        let mut location = self.long(db).clone();
98        location.inline_locations.push(inlining_location);
99        location.intern(db)
100    }
101    /// Returns all relevant stable pointers of the location.
102    pub fn all_locations(self, db: &'db dyn Database) -> Vec<StableLocation<'db>> {
103        let location = self.long(db);
104        let mut all_locations = vec![location.stable_location];
105        all_locations.extend(location.inline_locations.iter().cloned());
106        all_locations
107    }
108}
109
110#[derive(Clone, Debug, PartialEq, Eq)]
111pub struct VariableMarker;
112pub type VariableId = Id<VariableMarker>;
113pub type VariableArena<'db> = Arena<Variable<'db>, DefaultArenaBehavior<VariableMarker>>;
114
115/// Represents a usage of a variable.
116///
117/// For example if we have:
118///
119/// fn foo(a: u32) {
120///     1 + a
121/// }
122///
123/// Then the right hand side of the tail expression `1 + a` is a VarUsage object with
124/// the variable id of the variable `a` and the location:
125///     1 + a
126///         ^
127/// Note that the location associated with the variable that was assigned to 'a' is
128/// fn foo(a: u32)
129///        ^
130/// and it is different from the location in the VarUsage.
131///
132/// The tail expression `1 + a`  is also going to be assigned a variable and a VarUsage.
133/// in that case, the location of both the variable and the usage will be the same.
134#[derive(Copy, Clone, Debug, Eq, PartialEq)]
135pub struct VarUsage<'db> {
136    pub var_id: VariableId,
137    pub location: LocationId<'db>,
138}
139
140/// A lowered function code using flat blocks.
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct Lowered<'db> {
143    /// Diagnostics produced while lowering.
144    pub diagnostics: Diagnostics<'db, LoweringDiagnostic<'db>>,
145    /// Function signature.
146    pub signature: Signature<'db>,
147    /// Arena of allocated lowered variables.
148    pub variables: VariableArena<'db>,
149    /// Arena of allocated lowered blocks.
150    pub blocks: Blocks<'db>,
151    /// function parameters, including implicits.
152    pub parameters: Vec<VariableId>,
153}
154
155unsafe impl<'db> salsa::Update for Lowered<'db> {
156    unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
157        let old_value = unsafe { &mut *old_pointer };
158        let res = unsafe {
159            Diagnostics::maybe_update(&mut old_value.diagnostics, new_value.diagnostics)
160                | Signature::maybe_update(&mut old_value.signature, new_value.signature)
161        } | (old_value.blocks != new_value.blocks);
162        if res {
163            old_value.variables = new_value.variables;
164            old_value.parameters = new_value.parameters;
165            old_value.blocks = new_value.blocks;
166        }
167        res
168    }
169}
170
171/// Remapping of lowered variable ids. Useful for convergence of branches.
172#[derive(Clone, Debug, Default, PartialEq, Eq)]
173pub struct VarRemapping<'db> {
174    /// Map from new_var to old_var (since new_var cannot appear twice, but old_var can).
175    pub remapping: OrderedHashMap<VariableId, VarUsage<'db>>,
176}
177impl<'db> Deref for VarRemapping<'db> {
178    type Target = OrderedHashMap<VariableId, VarUsage<'db>>;
179
180    fn deref(&self) -> &Self::Target {
181        &self.remapping
182    }
183}
184impl<'db> DerefMut for VarRemapping<'db> {
185    fn deref_mut(&mut self) -> &mut Self::Target {
186        &mut self.remapping
187    }
188}
189
190/// A block of statements.
191#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct Block<'db> {
193    /// Statements sequence running one after the other in the block, in a linear flow.
194    pub statements: Vec<Statement<'db>>,
195    /// Describes how this block ends.
196    pub end: BlockEnd<'db>,
197}
198impl<'db> Default for Block<'db> {
199    fn default() -> Self {
200        Self { statements: Default::default(), end: BlockEnd::NotSet }
201    }
202}
203impl<'db> Block<'db> {
204    pub fn is_set(&self) -> bool {
205        !matches!(self.end, BlockEnd::NotSet)
206    }
207}
208
209/// Describes what happens to the program flow at the end of a [`Block`].
210#[derive(Clone, Debug, PartialEq, Eq)]
211pub enum BlockEnd<'db> {
212    /// The block was created but still needs to be populated. Block must not be in this state in
213    /// the end of the lowering phase.
214    NotSet,
215    /// This block ends with a `return` statement, exiting the function.
216    Return(Vec<VarUsage<'db>>, LocationId<'db>),
217    /// This block ends with a panic.
218    Panic(VarUsage<'db>),
219    /// This block ends with a jump to a different block.
220    Goto(BlockId, VarRemapping<'db>),
221    Match {
222        info: MatchInfo<'db>,
223    },
224}
225
226/// Lowered variable representation.
227#[derive(Clone, Debug, PartialEq, Eq)]
228pub struct Variable<'db> {
229    /// Semantic type of the variable.
230    pub ty: semantic::TypeId<'db>,
231    /// Location of the variable.
232    pub location: LocationId<'db>,
233    /// The semantic type info of the variable.
234    pub info: TypeInfo<'db>,
235}
236
237impl<'db> DebugWithDb<'db> for Variable<'db> {
238    type Db = LoweredFormatter<'db>;
239
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, _ctx: &Self::Db) -> std::fmt::Result {
241        write!(f, "Variable({:?})", self.ty)
242    }
243}
244
245impl<'db> Variable<'db> {
246    pub fn new(
247        db: &'db dyn Database,
248        ctx: ImplLookupContextId<'db>,
249        ty: semantic::TypeId<'db>,
250        location: LocationId<'db>,
251    ) -> Self {
252        Self { ty, location, info: db.type_info(ctx, ty) }
253    }
254
255    /// Returns a new variable with the type, with info calculated with the default context.
256    pub fn with_default_context(
257        db: &'db dyn Database,
258        ty: semantic::TypeId<'db>,
259        location: LocationId<'db>,
260    ) -> Self {
261        Self {
262            ty,
263            location,
264            info: TypeInfo {
265                copyable: db.copyable(ty),
266                droppable: db.droppable(ty),
267                destruct_impl: Err(InferenceError::Ambiguity(Ambiguity::WillNotInfer(
268                    concrete_destruct_trait(db, ty),
269                ))),
270                panic_destruct_impl: Err(InferenceError::Ambiguity(Ambiguity::WillNotInfer(
271                    concrete_panic_destruct_trait(db, ty),
272                ))),
273            },
274        }
275    }
276}
277
278/// Lowered statement.
279#[derive(Clone, Debug, PartialEq, Eq)]
280pub enum Statement<'db> {
281    // Values.
282    Const(StatementConst<'db>),
283
284    // Flow control.
285    Call(StatementCall<'db>),
286
287    // Structs (including tuples).
288    StructConstruct(StatementStructConstruct<'db>),
289    StructDestructure(StatementStructDestructure<'db>),
290
291    // Enums.
292    EnumConstruct(StatementEnumConstruct<'db>),
293
294    Snapshot(StatementSnapshot<'db>),
295    Desnap(StatementDesnap<'db>),
296}
297impl<'db> Statement<'db> {
298    pub fn inputs(&self) -> &[VarUsage<'db>] {
299        match &self {
300            Statement::Const(_stmt) => &[],
301            Statement::Call(stmt) => stmt.inputs.as_slice(),
302            Statement::StructConstruct(stmt) => stmt.inputs.as_slice(),
303            Statement::StructDestructure(stmt) => std::slice::from_ref(&stmt.input),
304            Statement::EnumConstruct(stmt) => std::slice::from_ref(&stmt.input),
305            Statement::Snapshot(stmt) => std::slice::from_ref(&stmt.input),
306            Statement::Desnap(stmt) => std::slice::from_ref(&stmt.input),
307        }
308    }
309
310    pub fn inputs_mut(&mut self) -> &mut [VarUsage<'db>] {
311        match self {
312            Statement::Const(_stmt) => &mut [],
313            Statement::Call(stmt) => stmt.inputs.as_mut_slice(),
314            Statement::StructConstruct(stmt) => stmt.inputs.as_mut_slice(),
315            Statement::StructDestructure(stmt) => std::slice::from_mut(&mut stmt.input),
316            Statement::EnumConstruct(stmt) => std::slice::from_mut(&mut stmt.input),
317            Statement::Snapshot(stmt) => std::slice::from_mut(&mut stmt.input),
318            Statement::Desnap(stmt) => std::slice::from_mut(&mut stmt.input),
319        }
320    }
321
322    pub fn outputs(&self) -> &[VariableId] {
323        match self {
324            Statement::Const(stmt) => std::slice::from_ref(&stmt.output),
325            Statement::Call(stmt) => stmt.outputs.as_slice(),
326            Statement::StructConstruct(stmt) => std::slice::from_ref(&stmt.output),
327            Statement::StructDestructure(stmt) => stmt.outputs.as_slice(),
328            Statement::EnumConstruct(stmt) => std::slice::from_ref(&stmt.output),
329            Statement::Snapshot(stmt) => stmt.outputs.as_slice(),
330            Statement::Desnap(stmt) => std::slice::from_ref(&stmt.output),
331        }
332    }
333
334    pub fn outputs_mut(&mut self) -> &mut [VariableId] {
335        match self {
336            Statement::Const(stmt) => std::slice::from_mut(&mut stmt.output),
337            Statement::Call(stmt) => stmt.outputs.as_mut_slice(),
338            Statement::StructConstruct(stmt) => std::slice::from_mut(&mut stmt.output),
339            Statement::StructDestructure(stmt) => stmt.outputs.as_mut_slice(),
340            Statement::EnumConstruct(stmt) => std::slice::from_mut(&mut stmt.output),
341            Statement::Snapshot(stmt) => stmt.outputs.as_mut_slice(),
342            Statement::Desnap(stmt) => std::slice::from_mut(&mut stmt.output),
343        }
344    }
345    pub fn location(&self) -> Option<LocationId<'db>> {
346        // TODO(Gil): Add location to all statements.
347        match &self {
348            Statement::Const(_) => None,
349            Statement::Call(stmt) => Some(stmt.location),
350            Statement::StructConstruct(_) => None,
351            Statement::StructDestructure(stmt) => Some(stmt.input.location),
352            Statement::EnumConstruct(stmt) => Some(stmt.input.location),
353            Statement::Snapshot(stmt) => Some(stmt.input.location),
354            Statement::Desnap(stmt) => Some(stmt.input.location),
355        }
356    }
357    pub fn location_mut(&mut self) -> Option<&mut LocationId<'db>> {
358        match self {
359            Statement::Const(_) => None,
360            Statement::Call(stmt) => Some(&mut stmt.location),
361            Statement::StructConstruct(_) => None,
362            Statement::StructDestructure(stmt) => Some(&mut stmt.input.location),
363            Statement::EnumConstruct(stmt) => Some(&mut stmt.input.location),
364            Statement::Snapshot(stmt) => Some(&mut stmt.input.location),
365            Statement::Desnap(stmt) => Some(&mut stmt.input.location),
366        }
367    }
368}
369
370/// A statement that binds a const value to a variable.
371#[derive(Clone, Debug, PartialEq, Eq)]
372pub struct StatementConst<'db> {
373    /// The value of the const.
374    pub value: ConstValueId<'db>,
375    /// The variable to bind the value to.
376    pub output: VariableId,
377    /// Is the const wrapped in a box.
378    pub boxed: bool,
379}
380impl<'db> StatementConst<'db> {
381    /// Creates a new const statement, with the option to wrap the value in a box.
382    pub fn new(value: ConstValueId<'db>, output: VariableId, boxed: bool) -> Self {
383        Self { value, output, boxed }
384    }
385    /// Creates a new const statement, not boxed.
386    pub fn new_flat(value: ConstValueId<'db>, output: VariableId) -> Self {
387        Self::new(value, output, false)
388    }
389    /// Creates a new const statement with the value wrapped in a box.
390    pub fn new_boxed(value: ConstValueId<'db>, output: VariableId) -> Self {
391        Self::new(value, output, true)
392    }
393}
394
395/// A statement that calls a user function.
396#[derive(Clone, Debug, PartialEq, Eq)]
397pub struct StatementCall<'db> {
398    /// A function to "call".
399    pub function: FunctionId<'db>,
400    /// Living variables in current scope to move to the function, as arguments.
401    pub inputs: Vec<VarUsage<'db>>,
402    /// Is the last input a coupon for the function call. See
403    /// [semantic::ExprFunctionCall::coupon_arg] for more information.
404    pub with_coupon: bool,
405    /// New variables to be introduced into the current scope from the function outputs.
406    pub outputs: Vec<VariableId>,
407    /// Is the call is to be inlined as part of the specialization wrapper function.
408    pub is_specialization_base_call: bool,
409    /// Location for the call.
410    pub location: LocationId<'db>,
411}
412
413/// A statement that construct a variant of an enum with a single argument, and binds it to a
414/// variable.
415#[derive(Clone, Debug, PartialEq, Eq)]
416pub struct StatementEnumConstruct<'db> {
417    pub variant: ConcreteVariant<'db>,
418    /// A living variable in current scope to wrap with the variant.
419    pub input: VarUsage<'db>,
420    /// The variable to bind the value to.
421    pub output: VariableId,
422}
423
424/// A statement that constructs a struct (tuple included) into a new variable.
425#[derive(Clone, Debug, PartialEq, Eq)]
426pub struct StatementStructConstruct<'db> {
427    pub inputs: Vec<VarUsage<'db>>,
428    /// The variable to bind the value to.
429    pub output: VariableId,
430}
431
432/// A statement that destructures a struct (tuple included), introducing its elements as new
433/// variables.
434#[derive(Clone, Debug, PartialEq, Eq)]
435pub struct StatementStructDestructure<'db> {
436    /// A living variable in current scope to destructure.
437    pub input: VarUsage<'db>,
438    /// The variables to bind values to.
439    pub outputs: Vec<VariableId>,
440}
441
442/// A statement that takes a snapshot of a variable.
443#[derive(Clone, Debug, PartialEq, Eq)]
444pub struct StatementSnapshot<'db> {
445    pub input: VarUsage<'db>,
446    pub outputs: [VariableId; 2],
447}
448impl<'db> StatementSnapshot<'db> {
449    pub fn new(
450        input: VarUsage<'db>,
451        output_original: VariableId,
452        output_snapshot: VariableId,
453    ) -> Self {
454        Self { input, outputs: [output_original, output_snapshot] }
455    }
456    pub fn original(&self) -> VariableId {
457        self.outputs[0]
458    }
459    pub fn snapshot(&self) -> VariableId {
460        self.outputs[1]
461    }
462}
463
464/// A statement that desnaps a variable.
465#[derive(Clone, Debug, PartialEq, Eq)]
466pub struct StatementDesnap<'db> {
467    pub input: VarUsage<'db>,
468    /// The variable to bind the value to.
469    pub output: VariableId,
470}
471
472/// An arm of a match statement.
473#[derive(Clone, Debug, PartialEq, Eq)]
474pub struct MatchArm<'db> {
475    /// The selector of the arm.
476    pub arm_selector: MatchArmSelector<'db>,
477
478    /// The block_id where the relevant arm is implemented.
479    pub block_id: BlockId,
480
481    /// The list of variable ids introduced in this arm.
482    pub var_ids: Vec<VariableId>,
483}
484
485/// A statement that calls an extern function with branches, and "calls" a possibly different block
486/// for each branch.
487#[derive(Clone, Debug, PartialEq, Eq)]
488pub struct MatchExternInfo<'db> {
489    // TODO(spapini): ConcreteExternFunctionId once it exists.
490    /// A concrete external function to call.
491    pub function: FunctionId<'db>,
492    /// Living variables in current scope to move to the function, as arguments.
493    pub inputs: Vec<VarUsage<'db>>,
494    /// Match arms. All blocks should have the same rets.
495    /// Order must be identical to the order in the definition of the enum.
496    pub arms: Vec<MatchArm<'db>>,
497    /// Location for the call.
498    pub location: LocationId<'db>,
499}
500
501/// A statement that matches an enum, and "calls" a possibly different block for each branch.
502#[derive(Clone, Debug, PartialEq, Eq)]
503pub struct MatchEnumInfo<'db> {
504    pub concrete_enum_id: ConcreteEnumId<'db>,
505    /// A living variable in current scope to match on.
506    pub input: VarUsage<'db>,
507    /// Match arms. All blocks should have the same rets.
508    /// Order must be identical to the order in the definition of the enum.
509    pub arms: Vec<MatchArm<'db>>,
510    /// Location for the match.
511    pub location: LocationId<'db>,
512}
513/// A statement that matches an index enum for matching on felt252, and "calls" a possibly different
514/// block for each branch.
515#[derive(Clone, Debug, PartialEq, Eq)]
516pub struct MatchEnumValue<'db> {
517    pub num_of_arms: usize,
518
519    /// A living variable in current scope to match on.
520    pub input: VarUsage<'db>,
521    /// Match arms. All blocks should have the same rets.
522    pub arms: Vec<MatchArm<'db>>,
523    /// Location for the match.
524    pub location: LocationId<'db>,
525}
526
527#[derive(Clone, Debug, PartialEq, Eq)]
528pub enum MatchInfo<'db> {
529    Enum(MatchEnumInfo<'db>),
530    Extern(MatchExternInfo<'db>),
531    Value(MatchEnumValue<'db>),
532}
533impl<'db> MatchInfo<'db> {
534    pub fn inputs(&self) -> &[VarUsage<'db>] {
535        match self {
536            MatchInfo::Enum(s) => std::slice::from_ref(&s.input),
537            MatchInfo::Extern(s) => s.inputs.as_slice(),
538            MatchInfo::Value(s) => std::slice::from_ref(&s.input),
539        }
540    }
541
542    pub fn inputs_mut(&mut self) -> &mut [VarUsage<'db>] {
543        match self {
544            MatchInfo::Enum(s) => std::slice::from_mut(&mut s.input),
545            MatchInfo::Extern(s) => s.inputs.as_mut_slice(),
546            MatchInfo::Value(s) => std::slice::from_mut(&mut s.input),
547        }
548    }
549    pub fn arms(&self) -> &[MatchArm<'db>] {
550        match self {
551            MatchInfo::Enum(s) => &s.arms,
552            MatchInfo::Extern(s) => &s.arms,
553            MatchInfo::Value(s) => &s.arms,
554        }
555    }
556    pub fn arms_mut(&mut self) -> &mut [MatchArm<'db>] {
557        match self {
558            MatchInfo::Enum(s) => &mut s.arms,
559            MatchInfo::Extern(s) => &mut s.arms,
560            MatchInfo::Value(s) => &mut s.arms,
561        }
562    }
563    pub fn location(&self) -> &LocationId<'db> {
564        match self {
565            MatchInfo::Enum(s) => &s.location,
566            MatchInfo::Extern(s) => &s.location,
567            MatchInfo::Value(s) => &s.location,
568        }
569    }
570    pub fn location_mut(&mut self) -> &mut LocationId<'db> {
571        match self {
572            MatchInfo::Enum(s) => &mut s.location,
573            MatchInfo::Extern(s) => &mut s.location,
574            MatchInfo::Value(s) => &mut s.location,
575        }
576    }
577}
578
579/// Used in graph algorithms, and describes how to construct the edges in function dependency graph.
580#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
581pub enum DependencyType {
582    /// A function depends on another function if it may call it.
583    Call,
584    /// A function depends on another function if its cost depends on the other function's cost.
585    Cost,
586}
587
588/// The requested lowering stage.
589#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
590pub enum LoweringStage {
591    /// Direct translation from the semantic stage and concretization.
592    Monomorphized,
593    /// After lowering stages that may change the signature of functions, such as `lower_panics`.
594    /// Specifically:
595    /// * Adds `withdraw_gas` calls.
596    /// * Adds panics.
597    /// * Adds destructor calls.
598    /// * scrub units.
599    PreOptimizations,
600    /// Lowering with baseline optimizations - specifically, adds the stages at
601    /// `baseline_optimization_strategy`.
602    PostBaseline,
603    /// Lowering with all of the optimizations - specifically, adds the stages at
604    /// `final_optimization_strategy`
605    Final,
606}