Skip to main content

cairo_lang_lowering/lower/
context.rs

1use std::ops::{Deref, DerefMut, Index};
2
3use cairo_lang_defs::ids::LanguageElementId;
4use cairo_lang_diagnostics::{DiagnosticAdded, Maybe};
5use cairo_lang_filesystem::ids::SmolStrId;
6use cairo_lang_semantic::ConcreteVariant;
7use cairo_lang_semantic::expr::fmt::ExprFormatter;
8use cairo_lang_semantic::items::enm::SemanticEnumEx;
9use cairo_lang_semantic::items::function_with_body::FunctionWithBodySemantic;
10use cairo_lang_semantic::items::imp::{ImplLookupContext, ImplLookupContextId};
11use cairo_lang_semantic::usage::{MemberPath, Usages};
12use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
13use cairo_lang_utils::Intern;
14use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
15use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
16use defs::diagnostic_utils::StableLocation;
17use itertools::{Itertools, zip_eq};
18use salsa::Database;
19use semantic::corelib::{core_module, get_ty_by_name};
20use semantic::types::wrap_in_snapshots;
21use semantic::{ExprVarMemberPath, MatchArmSelector, TypeLongId};
22use {cairo_lang_defs as defs, cairo_lang_semantic as semantic};
23
24use super::block_builder::BlockBuilder;
25use super::generators;
26use crate::blocks::BlocksBuilder;
27use crate::diagnostic::LoweringDiagnostics;
28use crate::ids::{
29    ConcreteFunctionWithBodyId, EnrichedSemanticSignature, FunctionWithBodyId,
30    GeneratedFunctionKey, LocationId, SemanticFunctionIdEx,
31};
32use crate::lower::external::{extern_facade_expr, extern_facade_return_tys};
33use crate::objects::Variable;
34use crate::{Lowered, MatchArm, MatchExternInfo, MatchInfo, VarUsage, VariableArena, VariableId};
35
36pub struct VariableAllocator<'db> {
37    pub db: &'db dyn Database,
38    /// Arena of allocated lowered variables.
39    pub variables: VariableArena<'db>,
40    // Lookup context for impls.
41    pub lookup_context: ImplLookupContextId<'db>,
42}
43impl<'db> VariableAllocator<'db> {
44    pub fn new(
45        db: &'db dyn Database,
46        function_id: defs::ids::FunctionWithBodyId<'db>,
47        variables: VariableArena<'db>,
48    ) -> Maybe<Self> {
49        let generic_params = db.function_with_body_generic_params(function_id)?;
50        let generic_param_ids = generic_params.iter().map(|p| p.id()).collect_vec();
51        Ok(Self {
52            db,
53            variables,
54            lookup_context: ImplLookupContext::new(
55                function_id.parent_module(db),
56                generic_param_ids,
57                db,
58            )
59            .intern(db),
60        })
61    }
62
63    /// Allocates a new variable in the context's variable arena according to the context.
64    pub fn new_var(&mut self, req: VarRequest<'db>) -> VariableId {
65        self.variables.alloc(Variable::new(self.db, self.lookup_context, req.ty, req.location))
66    }
67
68    /// Retrieves the LocationId of a stable syntax pointer in the current function file.
69    pub fn get_location(&self, stable_ptr: SyntaxStablePtrId<'db>) -> LocationId<'db> {
70        LocationId::from_stable_location(self.db, StableLocation::new(stable_ptr))
71    }
72}
73impl<'db> Index<VariableId> for VariableAllocator<'db> {
74    type Output = Variable<'db>;
75
76    fn index(&self, index: VariableId) -> &Self::Output {
77        &self.variables[index]
78    }
79}
80
81/// Lowering context for the encapsulating semantic function.
82///
83/// Each semantic function may generate multiple lowered functions. This context is common to all
84/// the generated lowered functions of an encapsulating semantic function.
85pub struct EncapsulatingLoweringContext<'db> {
86    pub db: &'db dyn Database,
87    /// Id for the current function being lowered.
88    pub semantic_function_id: defs::ids::FunctionWithBodyId<'db>,
89    /// Semantic model for current function body.
90    pub function_body: &'db semantic::FunctionBody<'db>,
91    /// Definitions encountered for semantic bindings. Since Constants are not lowered, this is
92    /// only used for variables.
93    // TODO(spapini): consider moving to semantic model.
94    pub semantic_defs: UnorderedHashMap<semantic::VarId<'db>, semantic::Binding<'db>>,
95    /// Expression formatter of the free function.
96    pub expr_formatter: ExprFormatter<'db>,
97    /// Block usages for the entire encapsulating function.
98    pub usages: Usages<'db>,
99    /// Lowerings of generated functions.
100    pub lowerings: OrderedHashMap<GeneratedFunctionKey<'db>, Lowered<'db>>,
101}
102impl<'db> EncapsulatingLoweringContext<'db> {
103    pub fn new(
104        db: &'db dyn Database,
105        semantic_function_id: defs::ids::FunctionWithBodyId<'db>,
106    ) -> Maybe<Self> {
107        let function_body = db.function_body(semantic_function_id)?;
108        let usages = Usages::from_function_body(function_body);
109        Ok(Self {
110            db,
111            semantic_function_id,
112            function_body,
113            semantic_defs: Default::default(),
114            expr_formatter: ExprFormatter { db, function_id: semantic_function_id },
115            usages,
116            lowerings: Default::default(),
117        })
118    }
119}
120
121/// The loop result variants for a loop with an early return.
122#[derive(Clone)]
123pub struct LoopEarlyReturnInfo<'db> {
124    pub normal_return_variant: ConcreteVariant<'db>,
125    pub early_return_variant: ConcreteVariant<'db>,
126}
127
128/// Context for lowering a loop.
129pub struct LoopContext<'db> {
130    /// The loop expression
131    pub loop_expr_id: semantic::ExprId,
132    /// Optional info related to early return from the loop.
133    pub early_return_info: Option<LoopEarlyReturnInfo<'db>>,
134}
135
136pub struct LoweringContext<'db, 'mt> {
137    pub encapsulating_ctx: Option<&'mt mut EncapsulatingLoweringContext<'db>>,
138    /// Variable allocator.
139    pub variables: VariableAllocator<'db>,
140    /// Current function signature.
141    pub signature: EnrichedSemanticSignature<'db>,
142    /// Id for the current function being lowered.
143    pub function_id: FunctionWithBodyId<'db>,
144    /// Id for the current concrete function to be used when generating recursive calls.
145    /// This is the generic function specialized with its own generic parameters.
146    pub concrete_function_id: ConcreteFunctionWithBodyId<'db>,
147    /// Current loop context.
148    pub current_loop_ctx: Option<LoopContext<'db>>,
149    /// Current emitted diagnostics.
150    pub diagnostics: LoweringDiagnostics<'db>,
151    /// Lowered blocks of the function.
152    pub blocks: BlocksBuilder<'db>,
153    // The return type in the current context, for loops this differs from signature.return_type.
154    pub return_type: semantic::TypeId<'db>,
155    /// The semantic variables that are captured as snapshots.
156    ///
157    /// For example, if we have a loop body that uses `@x.a`, then `x.a` will be added to
158    /// `snapped_semantics`.
159    pub snapped_semantics: OrderedHashMap<MemberPath<'db>, VariableId>,
160}
161impl<'db, 'mt> LoweringContext<'db, 'mt> {
162    pub fn new(
163        global_ctx: &'mt mut EncapsulatingLoweringContext<'db>,
164        function_id: FunctionWithBodyId<'db>,
165        signature: EnrichedSemanticSignature<'db>,
166        return_type: semantic::TypeId<'db>,
167    ) -> Maybe<Self>
168    where
169        'db: 'mt,
170    {
171        let db = global_ctx.db;
172        let concrete_function_id = function_id.to_concrete(db)?;
173        let semantic_function = function_id.base_semantic_function(db);
174        Ok(Self {
175            encapsulating_ctx: Some(global_ctx),
176            variables: VariableAllocator::new(db, semantic_function, Default::default())?,
177            signature,
178            function_id,
179            concrete_function_id,
180            current_loop_ctx: None,
181            diagnostics: LoweringDiagnostics::default(),
182            blocks: Default::default(),
183            return_type,
184            snapped_semantics: Default::default(),
185        })
186    }
187}
188impl<'db, 'mt> Deref for LoweringContext<'db, 'mt> {
189    type Target = EncapsulatingLoweringContext<'db>;
190
191    fn deref(&self) -> &Self::Target {
192        self.encapsulating_ctx.as_ref().unwrap()
193    }
194}
195impl DerefMut for LoweringContext<'_, '_> {
196    fn deref_mut(&mut self) -> &mut Self::Target {
197        self.encapsulating_ctx.as_mut().unwrap()
198    }
199}
200impl<'db, 'mt> LoweringContext<'db, 'mt> {
201    /// Allocates a new variable in the context's variable arena according to the context.
202    pub fn new_var(&mut self, req: VarRequest<'db>) -> VariableId {
203        self.variables.new_var(req)
204    }
205
206    /// Same as `new_var` but returns it as a `VarUsage`.
207    /// This is useful when the variable definition and usage locations are the same.
208    pub fn new_var_usage(&mut self, req: VarRequest<'db>) -> VarUsage<'db> {
209        let location = req.location;
210
211        VarUsage { var_id: self.variables.new_var(req), location }
212    }
213
214    /// Retrieves the LocationId of a stable syntax pointer in the current function file.
215    pub fn get_location(&self, stable_ptr: SyntaxStablePtrId<'db>) -> LocationId<'db> {
216        self.variables.get_location(stable_ptr)
217    }
218}
219
220/// Request for a lowered variable allocation.
221pub struct VarRequest<'db> {
222    pub ty: semantic::TypeId<'db>,
223    pub location: LocationId<'db>,
224}
225
226/// Representation of the value of a computed expression.
227#[derive(Clone, Debug)]
228pub enum LoweredExpr<'db> {
229    /// The expression value lies in a variable.
230    AtVariable(VarUsage<'db>),
231    /// The expression value is a tuple.
232    Tuple {
233        exprs: Vec<LoweredExpr<'db>>,
234        location: LocationId<'db>,
235    },
236    /// The expression value is a fixed size array.
237    FixedSizeArray {
238        ty: semantic::TypeId<'db>,
239        exprs: Vec<LoweredExpr<'db>>,
240        location: LocationId<'db>,
241    },
242    /// The expression value is an enum result from an extern call.
243    ExternEnum(LoweredExprExternEnum<'db>),
244    /// The expression value resides at a specific member path.
245    MemberPath(ExprVarMemberPath<'db>, LocationId<'db>),
246    Snapshot {
247        expr: Box<LoweredExpr<'db>>,
248        location: LocationId<'db>,
249    },
250}
251impl<'db> LoweredExpr<'db> {
252    /// Returns a [VarUsage] corresponding to the lowered expression.
253    pub fn as_var_usage(
254        self,
255        ctx: &mut LoweringContext<'db, '_>,
256        builder: &mut BlockBuilder<'db>,
257    ) -> LoweringResult<'db, VarUsage<'db>> {
258        match self {
259            LoweredExpr::AtVariable(var_usage) => Ok(var_usage),
260            LoweredExpr::Tuple { exprs, location } => {
261                let inputs: Vec<_> = exprs
262                    .into_iter()
263                    .map(|expr| expr.as_var_usage(ctx, builder))
264                    .collect::<Result<Vec<_>, _>>()?;
265                let tys =
266                    inputs.iter().map(|var_usage| ctx.variables[var_usage.var_id].ty).collect();
267                let ty = semantic::TypeLongId::Tuple(tys).intern(ctx.db);
268                Ok(generators::StructConstruct { inputs, ty, location }
269                    .add(ctx, &mut builder.statements))
270            }
271            LoweredExpr::ExternEnum(extern_enum) => extern_enum.as_var_usage(ctx, builder),
272            LoweredExpr::MemberPath(member_path, _location) => {
273                Ok(builder.get_ref(ctx, &member_path).unwrap())
274            }
275            LoweredExpr::Snapshot { expr, location } => {
276                if let LoweredExpr::MemberPath(member_path, _location) = &*expr
277                    && let Some(var_usage) = builder.get_snap_ref(ctx, member_path)
278                {
279                    return Ok(VarUsage { var_id: var_usage.var_id, location });
280                }
281
282                let input = expr.clone().as_var_usage(ctx, builder)?;
283                let (original, snapshot) =
284                    generators::Snapshot { input, location }.add(ctx, &mut builder.statements);
285                if let LoweredExpr::MemberPath(member_path, _location) = &*expr {
286                    builder.update_ref(ctx, member_path, original);
287                }
288
289                Ok(VarUsage { var_id: snapshot, location })
290            }
291            LoweredExpr::FixedSizeArray { exprs, location, ty } => {
292                let inputs = exprs
293                    .into_iter()
294                    .map(|expr| expr.as_var_usage(ctx, builder))
295                    .collect::<Result<Vec<_>, _>>()?;
296                Ok(generators::StructConstruct { inputs, ty, location }
297                    .add(ctx, &mut builder.statements))
298            }
299        }
300    }
301
302    pub fn ty(&self, ctx: &mut LoweringContext<'db, '_>) -> semantic::TypeId<'db> {
303        match self {
304            LoweredExpr::AtVariable(var_usage) => ctx.variables[var_usage.var_id].ty,
305            LoweredExpr::Tuple { exprs, .. } => {
306                semantic::TypeLongId::Tuple(exprs.iter().map(|expr| expr.ty(ctx)).collect())
307                    .intern(ctx.db)
308            }
309            LoweredExpr::ExternEnum(extern_enum) => semantic::TypeLongId::Concrete(
310                semantic::ConcreteTypeId::Enum(extern_enum.concrete_enum_id),
311            )
312            .intern(ctx.db),
313            LoweredExpr::MemberPath(member_path, _) => member_path.ty(),
314            LoweredExpr::Snapshot { expr, .. } => wrap_in_snapshots(ctx.db, expr.ty(ctx), 1),
315            LoweredExpr::FixedSizeArray { ty, .. } => *ty,
316        }
317    }
318    pub fn location(&self) -> LocationId<'db> {
319        match &self {
320            LoweredExpr::AtVariable(VarUsage { location, .. })
321            | LoweredExpr::Tuple { location, .. }
322            | LoweredExpr::ExternEnum(LoweredExprExternEnum { location, .. })
323            | LoweredExpr::MemberPath(_, location)
324            | LoweredExpr::Snapshot { location, .. } => *location,
325            LoweredExpr::FixedSizeArray { location, .. } => *location,
326        }
327    }
328}
329
330/// Lazy expression value of an extern call returning an enum.
331#[derive(Clone, Debug)]
332pub struct LoweredExprExternEnum<'db> {
333    pub function: semantic::FunctionId<'db>,
334    pub concrete_enum_id: semantic::ConcreteEnumId<'db>,
335    pub inputs: Vec<VarUsage<'db>>,
336    pub ref_args: Vec<RefArg<'db>>,
337    pub location: LocationId<'db>,
338}
339
340impl<'db> LoweredExprExternEnum<'db> {
341    pub fn as_var_usage(
342        self,
343        ctx: &mut LoweringContext<'db, '_>,
344        builder: &mut BlockBuilder<'db>,
345    ) -> LoweringResult<'db, VarUsage<'db>> {
346        let concrete_variants = ctx
347            .db
348            .concrete_enum_variants(self.concrete_enum_id)
349            .map_err(LoweringFlowError::Failed)?;
350
351        let mut arm_var_ids = vec![];
352        let (sealed_blocks, block_ids): (Vec<_>, Vec<_>) = concrete_variants
353            .clone()
354            .into_iter()
355            .map(|concrete_variant| {
356                let mut subscope = builder.child_block_builder(ctx.blocks.alloc_empty());
357                let block_id = subscope.block_id;
358
359                let mut var_ids = vec![];
360                // Bind the ref parameters.
361                for ref_arg in &self.ref_args {
362                    let var = ctx.new_var(VarRequest { ty: ref_arg.ty(), location: self.location });
363                    var_ids.push(var);
364                    if let RefArg::Ref(member_path) = ref_arg {
365                        subscope.update_ref(ctx, member_path, var);
366                    }
367                }
368
369                let before_returns = var_ids.len();
370                var_ids.extend(
371                    extern_facade_return_tys(ctx.db, &concrete_variant.ty)
372                        .iter()
373                        .map(|ty| ctx.new_var(VarRequest { ty: *ty, location: self.location })),
374                );
375                let returns = var_ids[before_returns..].iter().copied();
376                let maybe_input =
377                    extern_facade_expr(ctx, concrete_variant.ty, returns, self.location)
378                        .as_var_usage(ctx, &mut subscope);
379                arm_var_ids.push(var_ids);
380                let input = match maybe_input {
381                    Ok(var_usage) => var_usage,
382                    Err(err) => {
383                        return handle_lowering_flow_error(ctx, subscope, err)
384                            .map(|_| (None, block_id));
385                    }
386                };
387                let result = generators::EnumConstruct {
388                    input,
389                    variant: concrete_variant,
390                    location: self.location,
391                }
392                .add(ctx, &mut subscope.statements);
393                Ok((subscope.goto_callsite(Some(result)), block_id))
394            })
395            .collect::<Result<Vec<_>, _>>()
396            .map_err(LoweringFlowError::Failed)?
397            .into_iter()
398            .unzip();
399
400        let match_info = MatchInfo::Extern(MatchExternInfo {
401            function: self.function.lowered(ctx.db),
402            inputs: self.inputs,
403            arms: zip_eq(zip_eq(concrete_variants, block_ids), arm_var_ids)
404                .map(|((variant_id, block_id), var_ids)| MatchArm {
405                    arm_selector: MatchArmSelector::VariantId(variant_id),
406                    block_id,
407                    var_ids,
408                })
409                .collect(),
410            location: self.location,
411        });
412        builder
413            .merge_and_end_with_match(ctx, match_info, sealed_blocks, self.location)?
414            .as_var_usage(ctx, builder)
415    }
416}
417
418#[derive(Clone, Debug)]
419pub enum RefArg<'db> {
420    /// Reference to a variable member path.
421    Ref(semantic::ExprVarMemberPath<'db>),
422    /// Reference to a temporary variable.
423    Temp(semantic::TypeId<'db>),
424}
425impl<'db> RefArg<'db> {
426    /// Returns the type of the reference argument.
427    pub fn ty(&self) -> semantic::TypeId<'db> {
428        match self {
429            RefArg::Ref(member_path) => member_path.ty(),
430            RefArg::Temp(ty) => *ty,
431        }
432    }
433}
434
435pub type LoweringResult<'db, T> = Result<T, LoweringFlowError<'db>>;
436
437/// Cases where the flow of lowering an expression should halt.
438#[derive(Debug, Clone)]
439pub enum LoweringFlowError<'db> {
440    /// Computation failure. A corresponding diagnostic should be emitted.
441    Failed(DiagnosticAdded),
442    /// The expression that was being lowered always panics, and does not flow to the parent
443    /// builder.
444    Panic(VarUsage<'db>, LocationId<'db>),
445    /// The expression that was being lowered always returns from the function, and does not flow to
446    /// the parent builder.
447    Return(VarUsage<'db>, LocationId<'db>),
448    /// Every match arm is terminating - does not flow to parent builder
449    /// e.g. returns or panics.
450    Match(MatchInfo<'db>),
451}
452impl<'db> LoweringFlowError<'db> {
453    pub fn is_unreachable(&self) -> bool {
454        match self {
455            LoweringFlowError::Failed(_) => false,
456            LoweringFlowError::Panic(_, _)
457            | LoweringFlowError::Return(_, _)
458            | LoweringFlowError::Match(_) => true,
459        }
460    }
461}
462
463/// Converts a lowering flow error to the appropriate block builder end, if possible.
464pub fn handle_lowering_flow_error<'db, 'mt>(
465    ctx: &mut LoweringContext<'db, 'mt>,
466    mut builder: BlockBuilder<'db>,
467    err: LoweringFlowError<'db>,
468) -> Maybe<()> {
469    match err {
470        LoweringFlowError::Failed(diag_added) => Err(diag_added),
471        LoweringFlowError::Return(return_var, location) => builder.ret(ctx, return_var, location),
472        LoweringFlowError::Panic(data_var, location) => {
473            let panic_instance = generators::StructConstruct {
474                inputs: vec![],
475                ty: get_ty_by_name(
476                    ctx.db,
477                    core_module(ctx.db),
478                    SmolStrId::from(ctx.db, "Panic"),
479                    vec![],
480                ),
481                location,
482            }
483            .add(ctx, &mut builder.statements);
484            let err_instance = generators::StructConstruct {
485                inputs: vec![panic_instance, data_var],
486                ty: TypeLongId::Tuple(vec![
487                    ctx.variables[panic_instance.var_id].ty,
488                    ctx.variables[data_var.var_id].ty,
489                ])
490                .intern(ctx.db),
491                location,
492            }
493            .add(ctx, &mut builder.statements);
494            builder.panic(ctx, err_instance)
495        }
496        LoweringFlowError::Match(info) => {
497            builder.unreachable_match(ctx, info);
498            Ok(())
499        }
500    }
501}