cairo_lang_lowering/
db.rs

1use cairo_lang_debug::DebugWithDb;
2use cairo_lang_defs as defs;
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{
5    ExternFunctionId, LanguageElementId, ModuleId, ModuleItemId, NamedLanguageElementLongId,
6};
7use cairo_lang_diagnostics::{Diagnostics, DiagnosticsBuilder, Maybe, MaybeAsRef};
8use cairo_lang_filesystem::ids::{FileId, Tracked};
9use cairo_lang_semantic::items::enm::SemanticEnumEx;
10use cairo_lang_semantic::items::imp::ImplSemantic;
11use cairo_lang_semantic::items::macro_call::MacroCallSemantic;
12use cairo_lang_semantic::items::structure::StructSemantic;
13use cairo_lang_semantic::items::trt::TraitSemantic;
14use cairo_lang_semantic::{self as semantic, ConcreteTypeId, TypeId, TypeLongId, corelib};
15use cairo_lang_utils::Intern;
16use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
17use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
18use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
19use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
20use defs::ids::NamedLanguageElementId;
21use itertools::Itertools;
22use num_traits::ToPrimitive;
23use salsa::{Database, Setter};
24
25use crate::add_withdraw_gas::add_withdraw_gas;
26use crate::borrow_check::{BorrowCheckResult, borrow_check, borrow_check_possible_withdraw_gas};
27use crate::cache::load_cached_crate_functions;
28use crate::concretize::concretize_lowered;
29use crate::destructs::add_destructs;
30use crate::diagnostic::{LoweringDiagnostic, LoweringDiagnosticKind};
31use crate::graph_algorithms::feedback_set::flag_add_withdraw_gas;
32use crate::ids::{ConcreteFunctionWithBodyId, FunctionId, FunctionLongId, GenericOrSpecialized};
33use crate::inline::get_inline_diagnostics;
34use crate::inline::statements_weights::{ApproxCasmInlineWeight, InlineWeight};
35use crate::lower::{MultiLowering, lower_semantic_function};
36use crate::optimizations::config::Optimizations;
37use crate::optimizations::scrub_units::scrub_units;
38use crate::optimizations::strategy::OptimizationStrategyId;
39use crate::panic::lower_panics;
40use crate::specialization::specialized_function_lowered;
41use crate::{
42    BlockEnd, BlockId, DependencyType, Location, Lowered, LoweringStage, MatchInfo, Statement, ids,
43};
44
45type CodeSizeEstimator = fn(&dyn Database, ConcreteFunctionWithBodyId<'_>) -> Maybe<isize>;
46#[salsa::input]
47pub struct LoweringGroupInput {
48    #[returns(ref)]
49    pub optimizations: Option<Optimizations>,
50    /// A configurable function to get estimated size of the function with the given id.
51    #[returns(ref)]
52    code_size_estimator: Option<CodeSizeEstimator>,
53}
54
55#[salsa::tracked(returns(ref))]
56pub fn lowering_group_input(db: &dyn Database) -> LoweringGroupInput {
57    LoweringGroupInput::new(db, None, None)
58}
59
60/// Trait for information over the lowering.
61pub trait LoweringGroup: Database {
62    /// Computes the lowered representation of a function with a body, along with all it generated
63    /// functions (e.g. closures, lambdas, loops, ...).
64    fn priv_function_with_body_multi_lowering<'db>(
65        &'db self,
66        function_id: defs::ids::FunctionWithBodyId<'db>,
67    ) -> Maybe<&'db MultiLowering<'db>> {
68        priv_function_with_body_multi_lowering(self.as_dyn_database(), (), function_id)
69            .maybe_as_ref()
70    }
71
72    /// Returns a mapping from function ids to their multi-lowerings for the given loaded from a
73    /// cache for the given crate.
74    fn cached_multi_lowerings<'db>(
75        &'db self,
76        crate_id: cairo_lang_filesystem::ids::CrateId<'db>,
77    ) -> Option<&'db OrderedHashMap<defs::ids::FunctionWithBodyId<'db>, MultiLowering<'db>>> {
78        cached_multi_lowerings(self.as_dyn_database(), crate_id).as_ref()
79    }
80
81    /// Computes the lowered representation of a function with a body before borrow checking.
82    fn function_with_body_lowering<'db>(
83        &'db self,
84        function_id: ids::FunctionWithBodyId<'db>,
85    ) -> Maybe<&'db Lowered<'db>> {
86        function_with_body_lowering(self.as_dyn_database(), function_id)
87    }
88
89    /// Computes the borrow check result of a function.
90    fn borrow_check<'db>(
91        &'db self,
92        function_id: ids::FunctionWithBodyId<'db>,
93    ) -> Maybe<&'db BorrowCheckResult<'db>> {
94        borrow_check_tracked(self.as_dyn_database(), function_id).maybe_as_ref()
95    }
96
97    /// Computes the lowered representation of a function at the requested lowering stage.
98    fn lowered_body<'db>(
99        &'db self,
100        function_id: ids::ConcreteFunctionWithBodyId<'db>,
101        stage: LoweringStage,
102    ) -> Maybe<&'db Lowered<'db>> {
103        lowered_body(self.as_dyn_database(), function_id, stage).maybe_as_ref()
104    }
105
106    /// Returns the set of direct callees which are functions with body of a concrete function with
107    /// a body (i.e. excluding libfunc callees), at the given stage.
108    fn lowered_direct_callees<'db>(
109        &'db self,
110        function_id: ids::ConcreteFunctionWithBodyId<'db>,
111        dependency_type: DependencyType,
112        stage: LoweringStage,
113    ) -> Maybe<&'db [ids::FunctionId<'db>]> {
114        Ok(lowered_direct_callees(self.as_dyn_database(), function_id, dependency_type, stage)
115            .maybe_as_ref()?)
116    }
117
118    /// Returns the set of direct callees which are functions with body of a concrete function with
119    /// a body (i.e. excluding libfunc callees), at the given stage.
120    fn lowered_direct_callees_with_body<'db>(
121        &'db self,
122        function_id: ids::ConcreteFunctionWithBodyId<'db>,
123        dependency_type: DependencyType,
124        stage: LoweringStage,
125    ) -> Maybe<&'db [ids::ConcreteFunctionWithBodyId<'db>]> {
126        Ok(lowered_direct_callees_with_body(
127            self.as_dyn_database(),
128            function_id,
129            dependency_type,
130            stage,
131        )
132        .maybe_as_ref()?)
133    }
134
135    /// Aggregates function level lowering diagnostics.
136    fn function_with_body_lowering_diagnostics<'db>(
137        &'db self,
138        function_id: ids::FunctionWithBodyId<'db>,
139    ) -> Maybe<Diagnostics<'db, LoweringDiagnostic<'db>>> {
140        function_with_body_lowering_diagnostics(self.as_dyn_database(), function_id)
141    }
142    /// Aggregates semantic function level lowering diagnostics - along with all its generated
143    /// function.
144    fn semantic_function_with_body_lowering_diagnostics<'db>(
145        &'db self,
146        function_id: defs::ids::FunctionWithBodyId<'db>,
147    ) -> Maybe<Diagnostics<'db, LoweringDiagnostic<'db>>> {
148        semantic_function_with_body_lowering_diagnostics(self.as_dyn_database(), (), function_id)
149    }
150    /// Aggregates module level lowering diagnostics.
151    fn module_lowering_diagnostics<'db>(
152        &'db self,
153        module_id: ModuleId<'db>,
154    ) -> Maybe<Diagnostics<'db, LoweringDiagnostic<'db>>> {
155        module_lowering_diagnostics(self.as_dyn_database(), (), module_id)
156    }
157
158    /// Aggregates file level lowering diagnostics.
159    fn file_lowering_diagnostics<'db>(
160        &'db self,
161        file_id: FileId<'db>,
162    ) -> Maybe<Diagnostics<'db, LoweringDiagnostic<'db>>> {
163        file_lowering_diagnostics(self.as_dyn_database(), file_id)
164    }
165
166    // ### Queries related to implicits ###
167
168    /// Returns all the implicit parameters that the function requires (according to both its
169    /// signature and the functions it calls). The items in the returned vector are unique and the
170    /// order is consistent, but not necessarily related to the order of the explicit implicits in
171    /// the signature of the function.
172    fn function_implicits<'db>(
173        &'db self,
174        function: ids::FunctionId<'db>,
175    ) -> Maybe<Vec<TypeId<'db>>> {
176        crate::implicits::function_implicits(self.as_dyn_database(), function)
177    }
178
179    // ### Queries related to panics ###
180
181    /// Returns whether the function may panic.
182    fn function_may_panic<'db>(&'db self, function: ids::FunctionId<'db>) -> Maybe<bool> {
183        crate::panic::function_may_panic(self.as_dyn_database(), function)
184    }
185
186    /// Checks if the function has a block that ends with panic.
187    fn has_direct_panic<'db>(
188        &'db self,
189        function_id: ids::ConcreteFunctionWithBodyId<'db>,
190    ) -> Maybe<bool> {
191        crate::panic::has_direct_panic(self.as_dyn_database(), function_id)
192    }
193
194    // ### cycles ###
195
196    /// Returns the set of direct callees of a function with a body.
197    fn function_with_body_direct_callees<'db>(
198        &'db self,
199        function_id: ids::FunctionWithBodyId<'db>,
200        dependency_type: DependencyType,
201    ) -> Maybe<&'db OrderedHashSet<ids::FunctionId<'db>>> {
202        crate::graph_algorithms::cycles::function_with_body_direct_callees(
203            self.as_dyn_database(),
204            function_id,
205            dependency_type,
206        )
207        .maybe_as_ref()
208    }
209    /// Returns the set of direct callees which are functions with body of a function with a body
210    /// (i.e. excluding libfunc callees).
211    fn function_with_body_direct_function_with_body_callees<'db>(
212        &'db self,
213        function_id: ids::FunctionWithBodyId<'db>,
214        dependency_type: DependencyType,
215    ) -> Maybe<&'db OrderedHashSet<ids::FunctionWithBodyId<'db>>> {
216        crate::graph_algorithms::cycles::function_with_body_direct_function_with_body_callees(
217            self.as_dyn_database(),
218            function_id,
219            dependency_type,
220        )
221        .maybe_as_ref()
222    }
223
224    /// Returns `true` if the function (in its final lowering representation) calls (possibly
225    /// indirectly) itself, or if it calls (possibly indirectly) such a function. For example, if f0
226    /// calls f1, f1 calls f2, f2 calls f3, and f3 calls f2, then [Self::final_contains_call_cycle]
227    /// will return `true` for all of these functions.
228    fn final_contains_call_cycle<'db>(
229        &'db self,
230        function_id: ids::ConcreteFunctionWithBodyId<'db>,
231    ) -> Maybe<bool> {
232        crate::graph_algorithms::cycles::final_contains_call_cycle(
233            self.as_dyn_database(),
234            function_id,
235        )
236    }
237
238    /// Returns `true` if the function calls (possibly indirectly) itself. For example, if f0 calls
239    /// f1, f1 calls f2, f2 calls f3, and f3 calls f2, then [Self::in_cycle] will return
240    /// `true` for f2 and f3, but false for f0 and f1.
241    fn in_cycle<'db>(
242        &'db self,
243        function_id: ids::FunctionWithBodyId<'db>,
244        dependency_type: DependencyType,
245    ) -> Maybe<bool> {
246        crate::graph_algorithms::cycles::in_cycle(
247            self.as_dyn_database(),
248            function_id,
249            dependency_type,
250        )
251    }
252
253    /// A concrete version of `in_cycle`.
254    fn concrete_in_cycle<'db>(
255        &'db self,
256        function_id: ids::ConcreteFunctionWithBodyId<'db>,
257        dependency_type: DependencyType,
258        stage: LoweringStage,
259    ) -> Maybe<bool> {
260        crate::graph_algorithms::cycles::concrete_in_cycle(
261            self.as_dyn_database(),
262            function_id,
263            dependency_type,
264            stage,
265        )
266    }
267
268    // ### Strongly connected components ###
269
270    /// Returns the representative of the concrete function's strongly connected component. The
271    /// representative is consistently chosen for all the concrete functions in the same SCC.
272    fn lowered_scc_representative<'db>(
273        &'db self,
274        function: ids::ConcreteFunctionWithBodyId<'db>,
275        dependency_type: DependencyType,
276        stage: LoweringStage,
277    ) -> ConcreteSCCRepresentative<'db> {
278        crate::graph_algorithms::strongly_connected_components::lowered_scc_representative(
279            self.as_dyn_database(),
280            function,
281            dependency_type,
282            stage,
283        )
284    }
285
286    /// Returns all the concrete functions in the same strongly connected component as the given
287    /// concrete function.
288    fn lowered_scc<'db>(
289        &'db self,
290        function_id: ids::ConcreteFunctionWithBodyId<'db>,
291        dependency_type: DependencyType,
292        stage: LoweringStage,
293    ) -> Vec<ids::ConcreteFunctionWithBodyId<'db>> {
294        crate::graph_algorithms::strongly_connected_components::lowered_scc(
295            self.as_dyn_database(),
296            function_id,
297            dependency_type,
298            stage,
299        )
300    }
301
302    /// Returns all the functions in the same strongly connected component as the given function.
303    fn function_with_body_scc<'db>(
304        &'db self,
305        function_id: ids::FunctionWithBodyId<'db>,
306        dependency_type: DependencyType,
307    ) -> &'db [ids::FunctionWithBodyId<'db>] {
308        crate::scc::function_with_body_scc(self.as_dyn_database(), function_id, dependency_type)
309    }
310
311    // ### Feedback set ###
312
313    /// Returns the feedback-vertex-set of the given concrete function. A feedback-vertex-set is the
314    /// set of vertices whose removal leaves a graph without cycles.
315    fn function_with_body_feedback_set<'db>(
316        &'db self,
317        function: ids::ConcreteFunctionWithBodyId<'db>,
318        stage: LoweringStage,
319    ) -> Maybe<&'db OrderedHashSet<ids::ConcreteFunctionWithBodyId<'db>>> {
320        crate::graph_algorithms::feedback_set::function_with_body_feedback_set(
321            self.as_dyn_database(),
322            function,
323            stage,
324        )
325        .maybe_as_ref()
326    }
327
328    /// Returns whether the given function needs an additional withdraw_gas call.
329    fn needs_withdraw_gas<'db>(
330        &'db self,
331        function: ids::ConcreteFunctionWithBodyId<'db>,
332    ) -> Maybe<bool> {
333        crate::graph_algorithms::feedback_set::needs_withdraw_gas(self.as_dyn_database(), function)
334    }
335
336    /// Internal query for reorder_statements to cache the function ids that can be moved.
337    fn priv_movable_function_ids<'db>(&'db self) -> &'db UnorderedHashSet<ExternFunctionId<'db>> {
338        crate::optimizations::config::priv_movable_function_ids(self.as_dyn_database())
339    }
340
341    // Internal query for a heuristic to decide if a given `function_id` should be inlined.
342    fn priv_should_inline<'db>(
343        &'db self,
344        function_id: ids::ConcreteFunctionWithBodyId<'db>,
345    ) -> Maybe<bool> {
346        crate::inline::priv_should_inline(self.as_dyn_database(), function_id)
347    }
348
349    // Internal query for if a function is marked as `#[inline(never)]`.
350    fn priv_never_inline<'db>(
351        &'db self,
352        function_id: ids::ConcreteFunctionWithBodyId<'db>,
353    ) -> Maybe<bool> {
354        crate::inline::priv_never_inline(self.as_dyn_database(), function_id)
355    }
356
357    /// Returns whether a function should be specialized.
358    fn priv_should_specialize<'db>(
359        &'db self,
360        function_id: ids::ConcreteFunctionWithBodyId<'db>,
361    ) -> Maybe<bool> {
362        crate::specialization::priv_should_specialize(self.as_dyn_database(), function_id)
363    }
364
365    /// Returns the configuration struct that controls the behavior of the optimization passes.
366    fn optimizations(&self) -> &Optimizations {
367        let db = self.as_dyn_database();
368        lowering_group_input(db).optimizations(db).as_ref().unwrap()
369    }
370
371    /// Returns the final optimization strategy that is applied on top of
372    /// inlined_function_optimization_strategy.
373    fn final_optimization_strategy<'db>(&'db self) -> OptimizationStrategyId<'db> {
374        crate::optimizations::strategy::final_optimization_strategy(self.as_dyn_database())
375    }
376
377    /// Returns the baseline optimization strategy.
378    /// This strategy is used for inlining decision and as a starting point for the final lowering.
379    fn baseline_optimization_strategy<'db>(&'db self) -> OptimizationStrategyId<'db> {
380        crate::optimizations::strategy::baseline_optimization_strategy(self.as_dyn_database())
381    }
382
383    /// Returns the expected size of a type.
384    fn type_size<'db>(&'db self, ty: TypeId<'db>) -> usize {
385        type_size(self.as_dyn_database(), ty)
386    }
387
388    /// Returns the estimated size of the function with the given id.
389    fn estimate_size<'db>(&'db self, function_id: ConcreteFunctionWithBodyId<'db>) -> Maybe<isize> {
390        estimate_size(self.as_dyn_database(), function_id)
391    }
392}
393impl<T: Database + ?Sized> LoweringGroup for T {}
394
395pub fn init_lowering_group(
396    db: &mut (dyn Database + 'static),
397    optimizations: Optimizations,
398    code_size_estimator: Option<CodeSizeEstimator>,
399) {
400    lowering_group_input(db).set_optimizations(db).to(Some(optimizations));
401    lowering_group_input(db).set_code_size_estimator(db).to(code_size_estimator);
402}
403
404#[derive(Debug, Eq, PartialEq, Clone, Hash)]
405pub struct GenericSCCRepresentative<'db>(pub ids::FunctionWithBodyId<'db>);
406
407#[derive(Debug, Eq, PartialEq, Clone, Hash, salsa::Update)]
408pub struct ConcreteSCCRepresentative<'db>(pub ids::ConcreteFunctionWithBodyId<'db>);
409
410// *** Main lowering phases in order.
411
412#[salsa::tracked(returns(ref))]
413fn priv_function_with_body_multi_lowering<'db>(
414    db: &'db dyn Database,
415    _tracked: Tracked,
416    function_id: defs::ids::FunctionWithBodyId<'db>,
417) -> Maybe<MultiLowering<'db>> {
418    let crate_id = function_id.parent_module(db).owning_crate(db);
419    if let Some(map) = db.cached_multi_lowerings(crate_id) {
420        if let Some(multi_lowering) = map.get(&function_id) {
421            return Ok(multi_lowering.clone());
422        } else {
423            panic!("function not found in cached lowering {:?}", function_id.debug(db));
424        }
425    };
426
427    lower_semantic_function(db, function_id)
428}
429
430#[salsa::tracked(returns(ref))]
431fn cached_multi_lowerings<'db>(
432    db: &'db dyn Database,
433    crate_id: cairo_lang_filesystem::ids::CrateId<'db>,
434) -> Option<OrderedHashMap<defs::ids::FunctionWithBodyId<'db>, MultiLowering<'db>>> {
435    load_cached_crate_functions(db, crate_id)
436}
437
438fn function_with_body_lowering<'db>(
439    db: &'db dyn Database,
440    function_id: ids::FunctionWithBodyId<'db>,
441) -> Maybe<&'db Lowered<'db>> {
442    let semantic_function_id = function_id.base_semantic_function(db);
443    let multi_lowering = db.priv_function_with_body_multi_lowering(semantic_function_id)?;
444    Ok(match &function_id.long(db) {
445        ids::FunctionWithBodyLongId::Semantic(_) => &multi_lowering.main_lowering,
446        ids::FunctionWithBodyLongId::Generated { key, .. } => {
447            &multi_lowering.generated_lowerings[key]
448        }
449    })
450}
451
452#[salsa::tracked(returns(ref))]
453fn borrow_check_tracked<'db>(
454    db: &'db dyn Database,
455    function_id: ids::FunctionWithBodyId<'db>,
456) -> Maybe<BorrowCheckResult<'db>> {
457    let lowered = db.function_with_body_lowering(function_id)?;
458    Ok(borrow_check(db, function_id.to_concrete(db)?.is_panic_destruct_fn(db)?, lowered))
459}
460
461#[salsa::tracked(returns(ref))]
462fn lowered_body<'db>(
463    db: &'db dyn Database,
464    function: ids::ConcreteFunctionWithBodyId<'db>,
465    stage: LoweringStage,
466) -> Maybe<Lowered<'db>> {
467    Ok(match stage {
468        LoweringStage::Monomorphized => match function.generic_or_specialized(db) {
469            GenericOrSpecialized::Generic(generic_function_id) => {
470                db.function_with_body_lowering_diagnostics(generic_function_id)?
471                    .check_error_free()?;
472                let mut lowered = db.function_with_body_lowering(generic_function_id)?.clone();
473                concretize_lowered(db, &mut lowered, &function.substitution(db)?)?;
474                lowered
475            }
476            GenericOrSpecialized::Specialized(specialized) => {
477                specialized_function_lowered(db, specialized)?
478            }
479        },
480        LoweringStage::PreOptimizations => {
481            let mut lowered = db.lowered_body(function, LoweringStage::Monomorphized)?.clone();
482            add_withdraw_gas(db, function, &mut lowered)?;
483            lower_panics(db, function, &mut lowered)?;
484            add_destructs(db, function, &mut lowered);
485            scrub_units(db, &mut lowered);
486            lowered
487        }
488        LoweringStage::PostBaseline => {
489            let mut lowered = db.lowered_body(function, LoweringStage::PreOptimizations)?.clone();
490            db.baseline_optimization_strategy().apply_strategy(db, function, &mut lowered)?;
491            lowered
492        }
493        LoweringStage::Final => {
494            let mut lowered = db.lowered_body(function, LoweringStage::PostBaseline)?.clone();
495            db.final_optimization_strategy().apply_strategy(db, function, &mut lowered)?;
496            lowered
497        }
498    })
499}
500
501/// Given the lowering of a function, returns the set of direct dependencies of that function,
502/// according to the given [DependencyType]. See [DependencyType] for more information about
503/// what is considered a dependency.
504pub(crate) fn get_direct_callees<'db>(
505    db: &dyn Database,
506    lowered_function: &Lowered<'db>,
507    dependency_type: DependencyType,
508    block_extra_calls: &UnorderedHashMap<BlockId, Vec<FunctionId<'db>>>,
509) -> Vec<ids::FunctionId<'db>> {
510    let mut direct_callees = Vec::new();
511    if lowered_function.blocks.is_empty() {
512        return direct_callees;
513    }
514    let withdraw_gas_fns =
515        corelib::core_withdraw_gas_fns(db).map(|id| FunctionLongId::Semantic(id).intern(db));
516    let mut visited = vec![false; lowered_function.blocks.len()];
517    let mut stack = vec![BlockId(0)];
518    while let Some(block_id) = stack.pop() {
519        if visited[block_id.0] {
520            continue;
521        }
522        visited[block_id.0] = true;
523        let block = &lowered_function.blocks[block_id];
524        for statement in &block.statements {
525            if let Statement::Call(statement_call) = statement {
526                // If the dependency_type is DependencyType::Cost and this call has a coupon input,
527                // then the call statement has a constant cost and therefore there
528                // is no cost dependency in the called function.
529                if dependency_type != DependencyType::Cost || !statement_call.with_coupon {
530                    direct_callees.push(statement_call.function);
531                }
532            }
533        }
534        if let Some(extra_calls) = block_extra_calls.get(&block_id) {
535            direct_callees.extend(extra_calls.iter().copied());
536        }
537        match &block.end {
538            BlockEnd::NotSet | BlockEnd::Return(..) | BlockEnd::Panic(_) => {}
539            BlockEnd::Goto(next, _) => stack.push(*next),
540            BlockEnd::Match { info } => {
541                let mut arms = info.arms().iter();
542                if let MatchInfo::Extern(s) = info {
543                    direct_callees.push(s.function);
544                    if DependencyType::Cost == dependency_type
545                        && withdraw_gas_fns.contains(&s.function)
546                    {
547                        // Not following the option when successfully fetched gas.
548                        arms.next();
549                    }
550                }
551                stack.extend(arms.map(|arm| arm.block_id));
552            }
553        }
554    }
555    direct_callees
556}
557
558/// Given a vector of FunctionIds returns the vector of FunctionWithBodyIds of the
559/// [ids::ConcreteFunctionWithBodyId]s.
560///
561/// If `dependency_type` is `DependencyType::Cost`, returns the coupon functions when
562/// `coupon_buy` and `coupon_refund` are encountered.
563/// For example, for `coupon_buy::<foo::Coupon>()`, `foo` will be added to the list.
564fn functions_with_body_from_function_ids<'db>(
565    db: &'db dyn Database,
566    function_ids: &'db [ids::FunctionId<'db>],
567    dependency_type: DependencyType,
568) -> Maybe<Vec<ids::ConcreteFunctionWithBodyId<'db>>> {
569    Ok(function_ids
570        .iter()
571        .map(|concrete| {
572            if dependency_type == DependencyType::Cost
573                && let Some(function_with_body) = extract_coupon_function(db, *concrete)?
574            {
575                return Ok(Some(function_with_body));
576            }
577            concrete.body(db)
578        })
579        .collect::<Maybe<Vec<_>>>()?
580        .into_iter()
581        .flatten()
582        .collect_vec())
583}
584
585/// Given a [ids::FunctionId] that represents `coupon_buy` or `coupon_refund`, returns the coupon's
586/// function.
587///
588/// For example, `coupon_buy::<foo::Coupon>` will return `foo`.
589fn extract_coupon_function<'db>(
590    db: &'db dyn Database,
591    concrete: ids::FunctionId<'db>,
592) -> Maybe<Option<ids::ConcreteFunctionWithBodyId<'db>>> {
593    // Check that the function is a semantic function.
594    let ids::FunctionLongId::Semantic(function_id) = concrete.long(db) else {
595        return Ok(None);
596    };
597
598    // Check that it's an extern function named "coupon_buy" or "coupon_refund".
599    let concrete_function = function_id.get_concrete(db);
600    let generic_function = concrete_function.generic_function;
601    let semantic::items::functions::GenericFunctionId::Extern(extern_function_id) =
602        generic_function
603    else {
604        return Ok(None);
605    };
606    let name = extern_function_id.long(db).name(db).long(db);
607    if !(name == "coupon_buy" || name == "coupon_refund") {
608        return Ok(None);
609    }
610
611    // Extract the coupon function from the generic argument.
612    let [semantic::GenericArgumentId::Type(type_id)] = concrete_function.generic_args[..] else {
613        panic!("Unexpected generic_args for coupon_buy().");
614    };
615    let semantic::TypeLongId::Coupon(coupon_function) = type_id.long(db) else {
616        panic!("Unexpected generic_args for coupon_buy().");
617    };
618
619    // Convert [semantic::FunctionId] to [ids::ConcreteFunctionWithBodyId].
620    let Some(coupon_function_with_body_id) = coupon_function.get_concrete(db).body(db)? else {
621        panic!("Unexpected generic_args for coupon_buy().");
622    };
623
624    Ok(Some(ids::ConcreteFunctionWithBodyId::from_semantic(db, coupon_function_with_body_id)))
625}
626
627#[salsa::tracked(returns(ref))]
628fn lowered_direct_callees<'db>(
629    db: &'db dyn Database,
630    function_id: ids::ConcreteFunctionWithBodyId<'db>,
631    dependency_type: DependencyType,
632    stage: LoweringStage,
633) -> Maybe<Vec<ids::FunctionId<'db>>> {
634    let lowered_function = db.lowered_body(function_id, stage)?;
635    Ok(get_direct_callees(db, lowered_function, dependency_type, &Default::default()))
636}
637
638#[salsa::tracked(returns(ref))]
639fn lowered_direct_callees_with_body<'db>(
640    db: &'db dyn Database,
641    function_id: ids::ConcreteFunctionWithBodyId<'db>,
642    dependency_type: DependencyType,
643    stage: LoweringStage,
644) -> Maybe<Vec<ids::ConcreteFunctionWithBodyId<'db>>> {
645    functions_with_body_from_function_ids(
646        db,
647        db.lowered_direct_callees(function_id, dependency_type, stage)?,
648        dependency_type,
649    )
650}
651
652#[salsa::tracked]
653fn function_with_body_lowering_diagnostics<'db>(
654    db: &'db dyn Database,
655    function_id: ids::FunctionWithBodyId<'db>,
656) -> Maybe<Diagnostics<'db, LoweringDiagnostic<'db>>> {
657    let mut diagnostics = DiagnosticsBuilder::default();
658
659    if let Ok(lowered) = db.function_with_body_lowering(function_id) {
660        diagnostics.extend(lowered.diagnostics.clone());
661        if let Ok(bc) = db.borrow_check(function_id) {
662            diagnostics.extend(bc.diagnostics.clone());
663        }
664        if flag_add_withdraw_gas(db) && db.in_cycle(function_id, DependencyType::Cost)? {
665            let location =
666                Location::new(function_id.base_semantic_function(db).stable_location(db));
667            if !lowered.signature.panicable {
668                diagnostics.add(LoweringDiagnostic {
669                    location: location.clone(),
670                    kind: LoweringDiagnosticKind::NoPanicFunctionCycle,
671                });
672            }
673            borrow_check_possible_withdraw_gas(db, location.intern(db), lowered, &mut diagnostics)
674        }
675    }
676
677    if let Ok(diag) = get_inline_diagnostics(db, function_id) {
678        diagnostics.extend(diag);
679    }
680
681    Ok(diagnostics.build())
682}
683
684#[salsa::tracked]
685fn semantic_function_with_body_lowering_diagnostics<'db>(
686    db: &'db dyn Database,
687    _tracked: Tracked,
688    semantic_function_id: defs::ids::FunctionWithBodyId<'db>,
689) -> Maybe<Diagnostics<'db, LoweringDiagnostic<'db>>> {
690    let mut diagnostics = DiagnosticsBuilder::default();
691
692    if let Ok(multi_lowering) = db.priv_function_with_body_multi_lowering(semantic_function_id) {
693        let function_id = ids::FunctionWithBodyLongId::Semantic(semantic_function_id).intern(db);
694        diagnostics
695            .extend(db.function_with_body_lowering_diagnostics(function_id).unwrap_or_default());
696        for (key, _) in multi_lowering.generated_lowerings.iter() {
697            let function_id =
698                ids::FunctionWithBodyLongId::Generated { parent: semantic_function_id, key: *key }
699                    .intern(db);
700            diagnostics.extend(
701                db.function_with_body_lowering_diagnostics(function_id).unwrap_or_default(),
702            );
703        }
704    }
705
706    Ok(diagnostics.build())
707}
708
709#[salsa::tracked]
710fn module_lowering_diagnostics<'db>(
711    db: &'db dyn Database,
712    _tracked: Tracked,
713    module_id: ModuleId<'db>,
714) -> Maybe<Diagnostics<'db, LoweringDiagnostic<'db>>> {
715    let mut diagnostics = DiagnosticsBuilder::default();
716    for item in module_id.module_data(db)?.items(db).iter() {
717        match item {
718            ModuleItemId::FreeFunction(free_function) => {
719                let function_id = defs::ids::FunctionWithBodyId::Free(*free_function);
720                diagnostics
721                    .extend(db.semantic_function_with_body_lowering_diagnostics(function_id)?);
722            }
723            ModuleItemId::Constant(_) => {}
724            ModuleItemId::Submodule(_) => {}
725            ModuleItemId::Use(_) => {}
726            ModuleItemId::Struct(_) => {}
727            ModuleItemId::Enum(_) => {}
728            ModuleItemId::TypeAlias(_) => {}
729            ModuleItemId::ImplAlias(_) => {}
730            ModuleItemId::Trait(trait_id) => {
731                for trait_func in db.trait_functions(*trait_id)?.values() {
732                    if matches!(db.trait_function_body(*trait_func), Ok(Some(_))) {
733                        let function_id = defs::ids::FunctionWithBodyId::Trait(*trait_func);
734                        diagnostics.extend(
735                            db.semantic_function_with_body_lowering_diagnostics(function_id)?,
736                        );
737                    }
738                }
739            }
740            ModuleItemId::Impl(impl_def_id) => {
741                for impl_func in db.impl_functions(*impl_def_id)?.values() {
742                    let function_id = defs::ids::FunctionWithBodyId::Impl(*impl_func);
743                    diagnostics
744                        .extend(db.semantic_function_with_body_lowering_diagnostics(function_id)?);
745                }
746            }
747            ModuleItemId::ExternType(_) => {}
748            ModuleItemId::ExternFunction(_) => {}
749            ModuleItemId::MacroDeclaration(_) => {}
750        }
751    }
752    for macro_call in db.module_macro_calls_ids(module_id)?.iter() {
753        if let Ok(macro_module_id) = db.macro_call_module_id(*macro_call)
754            && let Ok(lowering_diags) = db.module_lowering_diagnostics(macro_module_id)
755        {
756            diagnostics.extend(lowering_diags);
757        }
758    }
759    Ok(diagnostics.build())
760}
761
762#[salsa::tracked]
763fn file_lowering_diagnostics<'db>(
764    db: &'db dyn Database,
765    file_id: FileId<'db>,
766) -> Maybe<Diagnostics<'db, LoweringDiagnostic<'db>>> {
767    let mut diagnostics = DiagnosticsBuilder::default();
768    for module_id in db.file_modules(file_id)?.iter().copied() {
769        if let Ok(module_diagnostics) = db.module_lowering_diagnostics(module_id) {
770            diagnostics.extend(module_diagnostics)
771        }
772    }
773    Ok(diagnostics.build())
774}
775
776#[salsa::tracked]
777fn type_size<'db>(db: &'db dyn Database, ty: TypeId<'db>) -> usize {
778    match ty.long(db) {
779        TypeLongId::Concrete(concrete_type_id) => match concrete_type_id {
780            ConcreteTypeId::Struct(struct_id) => db
781                .concrete_struct_members(*struct_id)
782                .unwrap()
783                .iter()
784                .map(|(_, member)| db.type_size(member.ty))
785                .sum::<usize>(),
786            ConcreteTypeId::Enum(enum_id) => {
787                1 + db
788                    .concrete_enum_variants(*enum_id)
789                    .unwrap()
790                    .into_iter()
791                    .map(|variant| db.type_size(variant.ty))
792                    .max()
793                    .unwrap_or_default()
794            }
795            ConcreteTypeId::Extern(extern_id) => {
796                match extern_id.extern_type_id(db).name(db).long(db).as_str() {
797                    "Array" | "SquashedFelt252Dict" | "EcPoint" => 2,
798                    "EcState" => 3,
799                    "Uint128MulGuarantee" => 4,
800                    _ => 1,
801                }
802            }
803        },
804        TypeLongId::Tuple(types) => types.iter().map(|ty| db.type_size(*ty)).sum::<usize>(),
805        TypeLongId::Snapshot(ty) => db.type_size(*ty),
806        TypeLongId::FixedSizeArray { type_id, size } => {
807            db.type_size(*type_id)
808                * size
809                    .long(db)
810                    .to_int()
811                    .expect("Expected ConstValue::Int for size")
812                    .to_usize()
813                    .unwrap()
814        }
815        TypeLongId::Closure(closure_ty) => {
816            closure_ty.captured_types.iter().map(|ty| db.type_size(*ty)).sum()
817        }
818        TypeLongId::Coupon(_) => 0,
819        TypeLongId::GenericParameter(_)
820        | TypeLongId::Var(_)
821        | TypeLongId::ImplType(_)
822        | TypeLongId::Missing(_) => {
823            panic!("Function should only be called with fully concrete types")
824        }
825    }
826}
827
828fn estimate_size<'db>(
829    db: &'db dyn Database,
830    function_id: ConcreteFunctionWithBodyId<'db>,
831) -> Maybe<isize> {
832    let code_size_estimator = lowering_group_input(db).code_size_estimator(db);
833    if let Some(estimator) = code_size_estimator {
834        estimator(db, function_id)
835    } else {
836        // Calling fallback approximated size heuristic.
837        let lowered = db.lowered_body(function_id, LoweringStage::PostBaseline)?;
838        Ok(ApproxCasmInlineWeight::new(db, lowered).lowered_weight(lowered))
839    }
840}