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 pub variables: VariableArena<'db>,
41 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 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 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
82pub struct EncapsulatingLoweringContext<'db> {
87 pub db: &'db dyn Database,
88 pub semantic_function_id: defs::ids::FunctionWithBodyId<'db>,
90 pub function_body: &'db semantic::FunctionBody<'db>,
92 pub semantic_defs: UnorderedHashMap<semantic::VarId<'db>, semantic::Binding<'db>>,
96 pub expr_formatter: ExprFormatter<'db>,
98 pub usages: Usages<'db>,
100 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#[derive(Clone)]
124pub struct LoopEarlyReturnInfo<'db> {
125 pub normal_return_variant: ConcreteVariant<'db>,
126 pub early_return_variant: ConcreteVariant<'db>,
127}
128
129pub struct LoopContext<'db> {
131 pub loop_expr_id: semantic::ExprId,
133 pub early_return_info: Option<LoopEarlyReturnInfo<'db>>,
135}
136
137pub struct LoweringContext<'db, 'mt> {
138 pub encapsulating_ctx: Option<&'mt mut EncapsulatingLoweringContext<'db>>,
139 pub variables: VariableAllocator<'db>,
141 pub signature: EnrichedSemanticSignature<'db>,
143 pub function_id: FunctionWithBodyId<'db>,
145 pub concrete_function_id: ConcreteFunctionWithBodyId<'db>,
148 pub current_loop_ctx: Option<LoopContext<'db>>,
150 pub diagnostics: LoweringDiagnostics<'db>,
152 pub blocks: BlocksBuilder<'db>,
154 pub return_type: semantic::TypeId<'db>,
156 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 pub fn new_var(&mut self, req: VarRequest<'db>) -> VariableId {
204 self.variables.new_var(req)
205 }
206
207 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 pub fn get_location(&self, stable_ptr: SyntaxStablePtrId<'db>) -> LocationId<'db> {
217 self.variables.get_location(stable_ptr)
218 }
219}
220
221pub struct VarRequest<'db> {
223 pub ty: semantic::TypeId<'db>,
224 pub location: LocationId<'db>,
225}
226
227#[derive(Clone, Debug)]
229pub enum LoweredExpr<'db> {
230 AtVariable(VarUsage<'db>),
232 Tuple {
234 exprs: Vec<LoweredExpr<'db>>,
235 location: LocationId<'db>,
236 },
237 FixedSizeArray {
239 ty: semantic::TypeId<'db>,
240 exprs: Vec<LoweredExpr<'db>>,
241 location: LocationId<'db>,
242 },
243 ExternEnum(LoweredExprExternEnum<'db>),
245 MemberPath(ExprVarMemberPath<'db>, LocationId<'db>),
247 Snapshot {
248 expr: Box<LoweredExpr<'db>>,
249 location: LocationId<'db>,
250 },
251}
252impl<'db> LoweredExpr<'db> {
253 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#[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 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 Ref(semantic::ExprVarMemberPath<'db>),
423 Temp(semantic::TypeId<'db>),
425}
426impl<'db> RefArg<'db> {
427 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#[derive(Debug, Clone)]
440pub enum LoweringFlowError<'db> {
441 Failed(DiagnosticAdded),
443 Panic(VarUsage<'db>, LocationId<'db>),
446 Return(VarUsage<'db>, LocationId<'db>),
449 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
464pub 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}