Skip to main content

cairo_lang_lowering/lower/
context.rs

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