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