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    /// Location for the call.
408    pub location: LocationId<'db>,
409}
410
411/// A statement that construct a variant of an enum with a single argument, and binds it to a
412/// variable.
413#[derive(Clone, Debug, PartialEq, Eq)]
414pub struct StatementEnumConstruct<'db> {
415    pub variant: ConcreteVariant<'db>,
416    /// A living variable in current scope to wrap with the variant.
417    pub input: VarUsage<'db>,
418    /// The variable to bind the value to.
419    pub output: VariableId,
420}
421
422/// A statement that constructs a struct (tuple included) into a new variable.
423#[derive(Clone, Debug, PartialEq, Eq)]
424pub struct StatementStructConstruct<'db> {
425    pub inputs: Vec<VarUsage<'db>>,
426    /// The variable to bind the value to.
427    pub output: VariableId,
428}
429
430/// A statement that destructures a struct (tuple included), introducing its elements as new
431/// variables.
432#[derive(Clone, Debug, PartialEq, Eq)]
433pub struct StatementStructDestructure<'db> {
434    /// A living variable in current scope to destructure.
435    pub input: VarUsage<'db>,
436    /// The variables to bind values to.
437    pub outputs: Vec<VariableId>,
438}
439
440/// A statement that takes a snapshot of a variable.
441#[derive(Clone, Debug, PartialEq, Eq)]
442pub struct StatementSnapshot<'db> {
443    pub input: VarUsage<'db>,
444    pub outputs: [VariableId; 2],
445}
446impl<'db> StatementSnapshot<'db> {
447    pub fn new(
448        input: VarUsage<'db>,
449        output_original: VariableId,
450        output_snapshot: VariableId,
451    ) -> Self {
452        Self { input, outputs: [output_original, output_snapshot] }
453    }
454    pub fn original(&self) -> VariableId {
455        self.outputs[0]
456    }
457    pub fn snapshot(&self) -> VariableId {
458        self.outputs[1]
459    }
460}
461
462/// A statement that desnaps a variable.
463#[derive(Clone, Debug, PartialEq, Eq)]
464pub struct StatementDesnap<'db> {
465    pub input: VarUsage<'db>,
466    /// The variable to bind the value to.
467    pub output: VariableId,
468}
469
470/// An arm of a match statement.
471#[derive(Clone, Debug, PartialEq, Eq)]
472pub struct MatchArm<'db> {
473    /// The selector of the arm.
474    pub arm_selector: MatchArmSelector<'db>,
475
476    /// The block_id where the relevant arm is implemented.
477    pub block_id: BlockId,
478
479    /// The list of variable ids introduced in this arm.
480    pub var_ids: Vec<VariableId>,
481}
482
483/// A statement that calls an extern function with branches, and "calls" a possibly different block
484/// for each branch.
485#[derive(Clone, Debug, PartialEq, Eq)]
486pub struct MatchExternInfo<'db> {
487    // TODO(spapini): ConcreteExternFunctionId once it exists.
488    /// A concrete external function to call.
489    pub function: FunctionId<'db>,
490    /// Living variables in current scope to move to the function, as arguments.
491    pub inputs: Vec<VarUsage<'db>>,
492    /// Match arms. All blocks should have the same rets.
493    /// Order must be identical to the order in the definition of the enum.
494    pub arms: Vec<MatchArm<'db>>,
495    /// Location for the call.
496    pub location: LocationId<'db>,
497}
498
499/// A statement that matches an enum, and "calls" a possibly different block for each branch.
500#[derive(Clone, Debug, PartialEq, Eq)]
501pub struct MatchEnumInfo<'db> {
502    pub concrete_enum_id: ConcreteEnumId<'db>,
503    /// A living variable in current scope to match on.
504    pub input: VarUsage<'db>,
505    /// Match arms. All blocks should have the same rets.
506    /// Order must be identical to the order in the definition of the enum.
507    pub arms: Vec<MatchArm<'db>>,
508    /// Location for the match.
509    pub location: LocationId<'db>,
510}
511/// A statement that matches an index enum for matching on felt252, and "calls" a possibly different
512/// block for each branch.
513#[derive(Clone, Debug, PartialEq, Eq)]
514pub struct MatchEnumValue<'db> {
515    pub num_of_arms: usize,
516
517    /// A living variable in current scope to match on.
518    pub input: VarUsage<'db>,
519    /// Match arms. All blocks should have the same rets.
520    pub arms: Vec<MatchArm<'db>>,
521    /// Location for the match.
522    pub location: LocationId<'db>,
523}
524
525#[derive(Clone, Debug, PartialEq, Eq)]
526pub enum MatchInfo<'db> {
527    Enum(MatchEnumInfo<'db>),
528    Extern(MatchExternInfo<'db>),
529    Value(MatchEnumValue<'db>),
530}
531impl<'db> MatchInfo<'db> {
532    pub fn inputs(&self) -> &[VarUsage<'db>] {
533        match self {
534            MatchInfo::Enum(s) => std::slice::from_ref(&s.input),
535            MatchInfo::Extern(s) => s.inputs.as_slice(),
536            MatchInfo::Value(s) => std::slice::from_ref(&s.input),
537        }
538    }
539
540    pub fn inputs_mut(&mut self) -> &mut [VarUsage<'db>] {
541        match self {
542            MatchInfo::Enum(s) => std::slice::from_mut(&mut s.input),
543            MatchInfo::Extern(s) => s.inputs.as_mut_slice(),
544            MatchInfo::Value(s) => std::slice::from_mut(&mut s.input),
545        }
546    }
547    pub fn arms(&self) -> &[MatchArm<'db>] {
548        match self {
549            MatchInfo::Enum(s) => &s.arms,
550            MatchInfo::Extern(s) => &s.arms,
551            MatchInfo::Value(s) => &s.arms,
552        }
553    }
554    pub fn arms_mut(&mut self) -> &mut [MatchArm<'db>] {
555        match self {
556            MatchInfo::Enum(s) => &mut s.arms,
557            MatchInfo::Extern(s) => &mut s.arms,
558            MatchInfo::Value(s) => &mut s.arms,
559        }
560    }
561    pub fn location(&self) -> &LocationId<'db> {
562        match self {
563            MatchInfo::Enum(s) => &s.location,
564            MatchInfo::Extern(s) => &s.location,
565            MatchInfo::Value(s) => &s.location,
566        }
567    }
568    pub fn location_mut(&mut self) -> &mut LocationId<'db> {
569        match self {
570            MatchInfo::Enum(s) => &mut s.location,
571            MatchInfo::Extern(s) => &mut s.location,
572            MatchInfo::Value(s) => &mut s.location,
573        }
574    }
575}
576
577/// Used in graph algorithms, and describes how to construct the edges in function dependency graph.
578#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
579pub enum DependencyType {
580    /// A function depends on another function if it may call it.
581    Call,
582    /// A function depends on another function if its cost depends on the other function's cost.
583    Cost,
584}
585
586/// The requested lowering stage.
587#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
588pub enum LoweringStage {
589    /// Direct translation from the semantic stage and concretization.
590    Monomorphized,
591    /// After lowering stages that may change the signature of functions, such as `lower_panics`.
592    /// Specifically:
593    /// * Adds `withdraw_gas` calls.
594    /// * Adds panics.
595    /// * Adds destructor calls.
596    /// * scrub units.
597    PreOptimizations,
598    /// Lowering with baseline optimizations - specifically, adds the stages at
599    /// `baseline_optimization_strategy`.
600    PostBaseline,
601    /// Lowering with all of the optimizations - specifically, adds the stages at
602    /// `final_optimization_strategy`
603    Final,
604}