cairo_lang_lowering/
db.rs

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