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