cairo_lang_lowering/
objects.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
//! Intermediate representation objects after lowering from semantic.
//! This representation is SSA (static single-assignment): each variable is defined before usage and
//! assigned once. It is also normal form: each function argument is a variable, rather than a
//! compound expression.

use std::ops::{Deref, DerefMut};

use cairo_lang_debug::DebugWithDb;
use cairo_lang_defs::diagnostic_utils::StableLocation;
use cairo_lang_diagnostics::{DiagnosticNote, Diagnostics};
use cairo_lang_semantic as semantic;
use cairo_lang_semantic::{ConcreteEnumId, ConcreteVariant};
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use cairo_lang_utils::{Intern, LookupIntern};
use id_arena::{Arena, Id};

pub mod blocks;
pub use blocks::BlockId;
use semantic::expr::inference::InferenceError;
use semantic::items::constant::ConstValue;
use semantic::items::imp::ImplId;
use semantic::MatchArmSelector;

use self::blocks::FlatBlocks;
use crate::db::LoweringGroup;
use crate::diagnostic::LoweringDiagnostic;
use crate::ids::{FunctionId, LocationId, Signature};

/// The Location struct represents the source location of a lowered object. It is used to store the
/// most relevant source location for a lowering object.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Location {
    /// The stable location of the object.
    pub stable_location: StableLocation,
    /// Additional notes about the origin of the object, for example if the object was
    /// auto-generated by the compiler.
    /// New notes are appended to the end of the vector.
    pub notes: Vec<DiagnosticNote>,
    /// Function call locations where this value was inlined from.
    pub inline_locations: Vec<StableLocation>,
}
impl Location {
    pub fn new(stable_location: StableLocation) -> Self {
        Self { stable_location, notes: vec![], inline_locations: vec![] }
    }

    /// Creates a new Location with the given note as the last note.
    pub fn with_note(mut self, note: DiagnosticNote) -> Self {
        self.notes.push(note);
        self
    }

    /// Creates a new Location with the given note as the last note.
    pub fn maybe_with_note(mut self, note: Option<DiagnosticNote>) -> Self {
        let Some(note) = note else {
            return self;
        };
        self.notes.push(note);
        self
    }

    /// Creates a new Location with a note from the given text and location.
    pub fn add_note_with_location(
        self,
        db: &dyn LoweringGroup,
        text: &str,
        location: LocationId,
    ) -> Self {
        self.with_note(DiagnosticNote::with_location(
            text.into(),
            location.lookup_intern(db).stable_location.diagnostic_location(db.upcast()),
        ))
    }
}

impl DebugWithDb<dyn LoweringGroup> for Location {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &dyn LoweringGroup) -> std::fmt::Result {
        let files_db = db.upcast();
        self.stable_location.diagnostic_location(db.upcast()).fmt(f, files_db)?;

        for note in &self.notes {
            f.write_str("\nnote: ")?;
            note.fmt(f, files_db)?;
        }
        Ok(())
    }
}

impl LocationId {
    /// Returns the location with the added inlining location of it.
    pub fn inlined(self, db: &dyn LoweringGroup, inlining_location: StableLocation) -> Self {
        let mut location = self.lookup_intern(db);
        location.inline_locations.push(inlining_location);
        location.intern(db)
    }
    /// Returns all relevant stable pointers of the location.
    pub fn all_locations(self, db: &dyn LoweringGroup) -> Vec<StableLocation> {
        let location = self.lookup_intern(db);
        let mut all_locations = vec![location.stable_location];
        all_locations.extend(location.inline_locations.iter().cloned());
        all_locations
    }
}

pub type VariableId = Id<Variable>;

/// Represents a usage of a variable.
///
/// For example if we have:
///
/// fn foo(a: u32) {
///     1 + a
/// }
///
/// Then the right hand side of the tail expression `1 + a` is a VarUsage object with
/// the variable id of the variable `a` and the location:
///     1 + a
///         ^
/// Note that the location associated with the variable that was assigned to 'a' is
/// fn foo(a: u32)
///        ^
/// and it is different from the location in the VarUsage.
///
/// The tail expression `1 + a`  is also going to be assigned a variable and a VarUsage.
/// in that case, the location of both the variable and the usage will be the same.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct VarUsage {
    pub var_id: VariableId,
    pub location: LocationId,
}

/// A lowered function code using flat blocks.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FlatLowered {
    /// Diagnostics produced while lowering.
    pub diagnostics: Diagnostics<LoweringDiagnostic>,
    /// Function signature.
    pub signature: Signature,
    /// Arena of allocated lowered variables.
    pub variables: Arena<Variable>,
    /// Arena of allocated lowered blocks.
    pub blocks: FlatBlocks,
    /// function parameters, including implicits.
    pub parameters: Vec<VariableId>,
}

/// Remapping of lowered variable ids. Useful for convergence of branches.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct VarRemapping {
    /// Map from new_var to old_var (since new_var cannot appear twice, but old_var can).
    pub remapping: OrderedHashMap<VariableId, VarUsage>,
}
impl Deref for VarRemapping {
    type Target = OrderedHashMap<VariableId, VarUsage>;

    fn deref(&self) -> &Self::Target {
        &self.remapping
    }
}
impl DerefMut for VarRemapping {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.remapping
    }
}

/// A block of statements. Unlike [`FlatBlock`], this has no reference information,
/// and no panic ending.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FlatBlock {
    /// Statements sequence running one after the other in the block, in a linear flow.
    /// Note: Inner blocks might end with a `return`, which will exit the function in the middle.
    /// Note: Match is a possible statement, which means it has control flow logic inside, but
    /// after its execution is completed, the flow returns to the following statement of the block.
    pub statements: Vec<Statement>,
    /// Describes how this block ends: returns to the caller or exits the function.
    pub end: FlatBlockEnd,
}
impl Default for FlatBlock {
    fn default() -> Self {
        Self { statements: Default::default(), end: FlatBlockEnd::NotSet }
    }
}
impl FlatBlock {
    pub fn is_set(&self) -> bool {
        !matches!(self.end, FlatBlockEnd::NotSet)
    }
}

/// Describes what happens to the program flow at the end of a [`FlatBlock`].
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlatBlockEnd {
    /// The block was created but still needs to be populated. Block must not be in this state in
    /// the end of the lowering phase.
    NotSet,
    /// This block ends with a `return` statement, exiting the function.
    Return(Vec<VarUsage>, LocationId),
    /// This block ends with a panic.
    Panic(VarUsage),
    /// This block ends with a jump to a different block.
    Goto(BlockId, VarRemapping),
    Match {
        info: MatchInfo,
    },
}

/// Lowered variable representation.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Variable {
    /// Can the type be (trivially) dropped.
    pub droppable: Result<ImplId, InferenceError>,
    /// Can the type be (trivially) copied.
    pub copyable: Result<ImplId, InferenceError>,
    /// A Destruct impl for the type, if found.
    pub destruct_impl: Result<ImplId, InferenceError>,
    /// A PanicDestruct impl for the type, if found.
    pub panic_destruct_impl: Result<ImplId, InferenceError>,
    /// Semantic type of the variable.
    pub ty: semantic::TypeId,
    /// Location of the variable.
    pub location: LocationId,
}

/// Lowered statement.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Statement {
    // Values.
    Const(StatementConst),

    // Flow control.
    Call(StatementCall),

    // Structs (including tuples).
    StructConstruct(StatementStructConstruct),
    StructDestructure(StatementStructDestructure),

    // Enums.
    EnumConstruct(StatementEnumConstruct),

    Snapshot(StatementSnapshot),
    Desnap(StatementDesnap),
}
impl Statement {
    pub fn inputs(&self) -> &[VarUsage] {
        match &self {
            Statement::Const(_stmt) => &[],
            Statement::Call(stmt) => stmt.inputs.as_slice(),
            Statement::StructConstruct(stmt) => stmt.inputs.as_slice(),
            Statement::StructDestructure(stmt) => std::slice::from_ref(&stmt.input),
            Statement::EnumConstruct(stmt) => std::slice::from_ref(&stmt.input),
            Statement::Snapshot(stmt) => std::slice::from_ref(&stmt.input),
            Statement::Desnap(stmt) => std::slice::from_ref(&stmt.input),
        }
    }

    pub fn inputs_mut(&mut self) -> &mut [VarUsage] {
        match self {
            Statement::Const(_stmt) => &mut [],
            Statement::Call(stmt) => stmt.inputs.as_mut_slice(),
            Statement::StructConstruct(stmt) => stmt.inputs.as_mut_slice(),
            Statement::StructDestructure(stmt) => std::slice::from_mut(&mut stmt.input),
            Statement::EnumConstruct(stmt) => std::slice::from_mut(&mut stmt.input),
            Statement::Snapshot(stmt) => std::slice::from_mut(&mut stmt.input),
            Statement::Desnap(stmt) => std::slice::from_mut(&mut stmt.input),
        }
    }

    pub fn outputs(&self) -> &[VariableId] {
        match &self {
            Statement::Const(stmt) => std::slice::from_ref(&stmt.output),
            Statement::Call(stmt) => stmt.outputs.as_slice(),
            Statement::StructConstruct(stmt) => std::slice::from_ref(&stmt.output),
            Statement::StructDestructure(stmt) => stmt.outputs.as_slice(),
            Statement::EnumConstruct(stmt) => std::slice::from_ref(&stmt.output),
            Statement::Snapshot(stmt) => stmt.outputs.as_slice(),
            Statement::Desnap(stmt) => std::slice::from_ref(&stmt.output),
        }
    }
    pub fn location(&self) -> Option<LocationId> {
        // TODO(Gil): Add location to all statements.
        match &self {
            Statement::Const(_) => None,
            Statement::Call(stmt) => Some(stmt.location),
            Statement::StructConstruct(_) => None,
            Statement::StructDestructure(stmt) => Some(stmt.input.location),
            Statement::EnumConstruct(stmt) => Some(stmt.input.location),
            Statement::Snapshot(stmt) => Some(stmt.input.location),
            Statement::Desnap(stmt) => Some(stmt.input.location),
        }
    }
    pub fn location_mut(&mut self) -> Option<&mut LocationId> {
        match self {
            Statement::Const(_) => None,
            Statement::Call(stmt) => Some(&mut stmt.location),
            Statement::StructConstruct(_) => None,
            Statement::StructDestructure(stmt) => Some(&mut stmt.input.location),
            Statement::EnumConstruct(stmt) => Some(&mut stmt.input.location),
            Statement::Snapshot(stmt) => Some(&mut stmt.input.location),
            Statement::Desnap(stmt) => Some(&mut stmt.input.location),
        }
    }
}

/// A statement that binds a const value to a variable.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementConst {
    /// The value of the const.
    pub value: ConstValue,
    /// The variable to bind the value to.
    pub output: VariableId,
}

/// A statement that calls a user function.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementCall {
    /// A function to "call".
    pub function: FunctionId,
    /// Living variables in current scope to move to the function, as arguments.
    pub inputs: Vec<VarUsage>,
    /// Is the last input a coupon for the function call. See
    /// [semantic::ExprFunctionCall::coupon_arg] for more information.
    pub with_coupon: bool,
    /// New variables to be introduced into the current scope from the function outputs.
    pub outputs: Vec<VariableId>,
    /// Location for the call.
    pub location: LocationId,
}

/// A statement that construct a variant of an enum with a single argument, and binds it to a
/// variable.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementEnumConstruct {
    pub variant: ConcreteVariant,
    /// A living variable in current scope to wrap with the variant.
    pub input: VarUsage,
    /// The variable to bind the value to.
    pub output: VariableId,
}

/// A statement that constructs a struct (tuple included) into a new variable.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementStructConstruct {
    pub inputs: Vec<VarUsage>,
    /// The variable to bind the value to.
    pub output: VariableId,
}

/// A statement that destructures a struct (tuple included), introducing its elements as new
/// variables.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementStructDestructure {
    /// A living variable in current scope to destructure.
    pub input: VarUsage,
    /// The variables to bind values to.
    pub outputs: Vec<VariableId>,
}

/// A statement that takes a snapshot of a variable.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementSnapshot {
    pub input: VarUsage,
    outputs: [VariableId; 2],
}
impl StatementSnapshot {
    pub fn new(input: VarUsage, output_original: VariableId, output_snapshot: VariableId) -> Self {
        Self { input, outputs: [output_original, output_snapshot] }
    }
    pub fn original(&self) -> VariableId {
        self.outputs[0]
    }
    pub fn snapshot(&self) -> VariableId {
        self.outputs[1]
    }
}

/// A statement that desnaps a variable.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatementDesnap {
    pub input: VarUsage,
    /// The variable to bind the value to.
    pub output: VariableId,
}

/// An arm of a match statement.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchArm {
    /// The selector of the arm.
    pub arm_selector: MatchArmSelector,

    /// The block_id where the relevant arm is implemented.
    pub block_id: BlockId,

    /// The list of variable ids introduced in this arm.
    pub var_ids: Vec<VariableId>,
}

/// A statement that calls an extern function with branches, and "calls" a possibly different block
/// for each branch.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchExternInfo {
    // TODO(spapini): ConcreteExternFunctionId once it exists.
    /// A concrete external function to call.
    pub function: FunctionId,
    /// Living variables in current scope to move to the function, as arguments.
    pub inputs: Vec<VarUsage>,
    /// Match arms. All blocks should have the same rets.
    /// Order must be identical to the order in the definition of the enum.
    pub arms: Vec<MatchArm>,
    /// Location for the call.
    pub location: LocationId,
}

/// A statement that matches an enum, and "calls" a possibly different block for each branch.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchEnumInfo {
    pub concrete_enum_id: ConcreteEnumId,
    /// A living variable in current scope to match on.
    pub input: VarUsage,
    /// Match arms. All blocks should have the same rets.
    /// Order must be identical to the order in the definition of the enum.
    pub arms: Vec<MatchArm>,
    /// Location for the match.
    pub location: LocationId,
}
/// A statement that matches an index enum for matching on felt252, and "calls" a possibly different
/// block for each branch.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MatchEnumValue {
    pub num_of_arms: usize,

    /// A living variable in current scope to match on.
    pub input: VarUsage,
    /// Match arms. All blocks should have the same rets.
    pub arms: Vec<MatchArm>,
    /// Location for the match.
    pub location: LocationId,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MatchInfo {
    Enum(MatchEnumInfo),
    Extern(MatchExternInfo),
    Value(MatchEnumValue),
}
impl MatchInfo {
    pub fn inputs(&self) -> &[VarUsage] {
        match self {
            MatchInfo::Enum(s) => std::slice::from_ref(&s.input),
            MatchInfo::Extern(s) => s.inputs.as_slice(),
            MatchInfo::Value(s) => std::slice::from_ref(&s.input),
        }
    }

    pub fn inputs_mut(&mut self) -> &mut [VarUsage] {
        match self {
            MatchInfo::Enum(s) => std::slice::from_mut(&mut s.input),
            MatchInfo::Extern(s) => s.inputs.as_mut_slice(),
            MatchInfo::Value(s) => std::slice::from_mut(&mut s.input),
        }
    }
    pub fn arms(&self) -> &[MatchArm] {
        match self {
            MatchInfo::Enum(s) => &s.arms,
            MatchInfo::Extern(s) => &s.arms,
            MatchInfo::Value(s) => &s.arms,
        }
    }
    pub fn location(&self) -> &LocationId {
        match self {
            MatchInfo::Enum(s) => &s.location,
            MatchInfo::Extern(s) => &s.location,
            MatchInfo::Value(s) => &s.location,
        }
    }
    pub fn location_mut(&mut self) -> &mut LocationId {
        match self {
            MatchInfo::Enum(s) => &mut s.location,
            MatchInfo::Extern(s) => &mut s.location,
            MatchInfo::Value(s) => &mut s.location,
        }
    }
}

/// Used in graph algorithms, and describes how to construct the edges in function dependency graph.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DependencyType {
    /// A function depends on another function if it may call it.
    Call,
    /// A function depends on another function if its cost depends on the other function's cost.
    Cost,
}