cairo_lang_lowering/
db.rs

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