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