Skip to main content

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