Skip to main content

oxilean_codegen/opt_inline/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::{LcnfArg, LcnfExpr, LcnfFunDecl, LcnfLetValue, LcnfLit, LcnfType, LcnfVarId};
6use std::collections::{HashMap, HashSet};
7
8use super::types::{
9    CallFrequencyAnalyzer, CallGraph, CallSite, CalleeSizeTable, CloneSpecializer,
10    ExtendedInlinePass, ExtendedInlineStats, FreshVarGen, HotPath, InlineAnnotation,
11    InlineAnnotationRegistry, InlineBudget, InlineConfig, InlineContextStack, InlineCost,
12    InlineDecision, InlineFusionManager, InlineHeuristics, InlineHistory, InlineOrderScheduler,
13    InlinePass, InlineProfile, InlineProfitabilityEstimator, InlineReport, InlineTrace,
14    InlineTraceEntry, InliningContext, InterproceduralInlinePass, NestingDepthTracker,
15    PartialInlineDecision, PartialInlineRegion, RecursiveInlineLimiter, SpeculativeInlineRecord,
16    SpeculativeInliner, TarjanScc,
17};
18
19/// Estimate the "size" of a function declaration in abstract units.
20///
21/// Each let-binding contributes 1, each case alternative contributes 2,
22/// and the base overhead is 1.
23pub fn estimate_size(decl: &LcnfFunDecl) -> u64 {
24    1 + count_expr_size(&decl.body)
25}
26pub(super) fn count_expr_size(expr: &LcnfExpr) -> u64 {
27    match expr {
28        LcnfExpr::Let { value, body, .. } => {
29            let value_cost = match value {
30                LcnfLetValue::App(_, args) => 1 + args.len() as u64,
31                LcnfLetValue::Ctor(_, _, args) | LcnfLetValue::Reuse(_, _, _, args) => {
32                    1 + args.len() as u64
33                }
34                LcnfLetValue::Proj(..) => 1,
35                LcnfLetValue::Reset(..) => 1,
36                LcnfLetValue::Lit(_) | LcnfLetValue::Erased | LcnfLetValue::FVar(_) => 1,
37            };
38            value_cost + count_expr_size(body)
39        }
40        LcnfExpr::Case { alts, default, .. } => {
41            let alt_cost: u64 = alts.iter().map(|a| 2 + count_expr_size(&a.body)).sum();
42            let default_cost = default.as_ref().map_or(0, |d| 2 + count_expr_size(d));
43            1 + alt_cost + default_cost
44        }
45        LcnfExpr::Return(_) | LcnfExpr::TailCall(_, _) | LcnfExpr::Unreachable => 1,
46    }
47}
48/// Substitute `params` with `args` throughout `body`.
49///
50/// Each parameter whose name matches a key in `subst` is replaced by a
51/// `Return`-wrapped copy of the argument value.  Because LCNF bodies are
52/// in ANF, we wrap argument variables/literals in `let` bindings to
53/// preserve the ANF invariant.
54pub(super) fn substitute_params(
55    body: &LcnfExpr,
56    params: &[String],
57    args: &[LcnfArg],
58    gen: &mut FreshVarGen,
59) -> LcnfExpr {
60    let mut param_map: HashMap<String, LcnfArg> = HashMap::new();
61    for (param, arg) in params.iter().zip(args.iter()) {
62        param_map.insert(param.clone(), arg.clone());
63    }
64    subst_expr(body, &param_map, gen)
65}
66#[allow(clippy::only_used_in_recursion)]
67pub(super) fn subst_expr(
68    expr: &LcnfExpr,
69    map: &HashMap<String, LcnfArg>,
70    gen: &mut FreshVarGen,
71) -> LcnfExpr {
72    match expr {
73        LcnfExpr::Let {
74            id,
75            name,
76            ty,
77            value,
78            body,
79        } => {
80            let new_value = subst_let_value(value, map);
81            let new_body = subst_expr(body, map, gen);
82            LcnfExpr::Let {
83                id: *id,
84                name: name.clone(),
85                ty: ty.clone(),
86                value: new_value,
87                body: Box::new(new_body),
88            }
89        }
90        LcnfExpr::Case {
91            scrutinee,
92            scrutinee_ty,
93            alts,
94            default,
95        } => {
96            let new_scrutinee = *scrutinee;
97            let new_alts = alts
98                .iter()
99                .map(|alt| crate::lcnf::LcnfAlt {
100                    ctor_name: alt.ctor_name.clone(),
101                    ctor_tag: alt.ctor_tag,
102                    params: alt.params.clone(),
103                    body: subst_expr(&alt.body, map, gen),
104                })
105                .collect();
106            let new_default = default.as_ref().map(|d| Box::new(subst_expr(d, map, gen)));
107            LcnfExpr::Case {
108                scrutinee: new_scrutinee,
109                scrutinee_ty: scrutinee_ty.clone(),
110                alts: new_alts,
111                default: new_default,
112            }
113        }
114        LcnfExpr::Return(arg) => LcnfExpr::Return(subst_arg(arg, map)),
115        LcnfExpr::TailCall(func, args) => LcnfExpr::TailCall(
116            subst_arg(func, map),
117            args.iter().map(|a| subst_arg(a, map)).collect(),
118        ),
119        LcnfExpr::Unreachable => LcnfExpr::Unreachable,
120    }
121}
122pub(super) fn subst_let_value(
123    value: &LcnfLetValue,
124    map: &HashMap<String, LcnfArg>,
125) -> LcnfLetValue {
126    match value {
127        LcnfLetValue::App(func, args) => LcnfLetValue::App(
128            subst_arg(func, map),
129            args.iter().map(|a| subst_arg(a, map)).collect(),
130        ),
131        LcnfLetValue::Ctor(name, tag, args) => LcnfLetValue::Ctor(
132            name.clone(),
133            *tag,
134            args.iter().map(|a| subst_arg(a, map)).collect(),
135        ),
136        LcnfLetValue::Reuse(slot, name, tag, args) => LcnfLetValue::Reuse(
137            *slot,
138            name.clone(),
139            *tag,
140            args.iter().map(|a| subst_arg(a, map)).collect(),
141        ),
142        LcnfLetValue::Proj(name, idx, var) => LcnfLetValue::Proj(name.clone(), *idx, *var),
143        LcnfLetValue::Reset(var) => LcnfLetValue::Reset(*var),
144        LcnfLetValue::Lit(lit) => LcnfLetValue::Lit(lit.clone()),
145        LcnfLetValue::Erased => LcnfLetValue::Erased,
146        LcnfLetValue::FVar(id) => LcnfLetValue::FVar(*id),
147    }
148}
149pub(super) fn subst_arg(arg: &LcnfArg, map: &HashMap<String, LcnfArg>) -> LcnfArg {
150    match arg {
151        LcnfArg::Lit(lit) => LcnfArg::Lit(lit.clone()),
152        LcnfArg::Var(id) => {
153            let name_str = format!("_x{}", id.0);
154            if let Some(replacement) = map.get(&name_str) {
155                replacement.clone()
156            } else {
157                LcnfArg::Var(*id)
158            }
159        }
160        LcnfArg::Erased => LcnfArg::Erased,
161        LcnfArg::Type(ty) => LcnfArg::Type(ty.clone()),
162    }
163}
164/// Substitute `inlined_body` as the "value" portion of a let, with `continuation`
165/// as the next expression.  Because LCNF is in ANF, we simply sequence the
166/// inlined body and then the continuation under a fresh result binding.
167pub(super) fn splice_inlined(inlined: LcnfExpr, continuation: LcnfExpr) -> LcnfExpr {
168    match inlined {
169        LcnfExpr::Return(_) => continuation,
170        LcnfExpr::Unreachable => LcnfExpr::Unreachable,
171        other => sequence_exprs(other, continuation),
172    }
173}
174/// Append `next` after all terminal nodes of `first`.
175pub(super) fn sequence_exprs(first: LcnfExpr, next: LcnfExpr) -> LcnfExpr {
176    match first {
177        LcnfExpr::Let {
178            id,
179            name,
180            ty,
181            value,
182            body,
183        } => LcnfExpr::Let {
184            id,
185            name,
186            ty,
187            value,
188            body: Box::new(sequence_exprs(*body, next)),
189        },
190        LcnfExpr::Case {
191            scrutinee,
192            scrutinee_ty,
193            alts,
194            default,
195        } => LcnfExpr::Case {
196            scrutinee,
197            scrutinee_ty,
198            alts: alts
199                .into_iter()
200                .map(|alt| crate::lcnf::LcnfAlt {
201                    ctor_name: alt.ctor_name,
202                    ctor_tag: alt.ctor_tag,
203                    params: alt.params,
204                    body: sequence_exprs(alt.body, next.clone()),
205                })
206                .collect(),
207            default: default.map(|d| Box::new(sequence_exprs(*d, next.clone()))),
208        },
209        LcnfExpr::Return(_) => next,
210        LcnfExpr::TailCall(_, _) | LcnfExpr::Unreachable => first,
211    }
212}
213/// Convert an [`LcnfArg`] to a [`LcnfLetValue`] suitable for a let-binding.
214pub(super) fn arg_to_let_value(arg: &LcnfArg) -> LcnfLetValue {
215    match arg {
216        LcnfArg::Lit(lit) => LcnfLetValue::Lit(lit.clone()),
217        LcnfArg::Var(id) => LcnfLetValue::FVar(*id),
218        LcnfArg::Erased => LcnfLetValue::Erased,
219        LcnfArg::Type(_) => LcnfLetValue::Erased,
220    }
221}
222/// Run the inlining pass with default configuration and return a report.
223pub fn run_inline_pass(decls: &mut [LcnfFunDecl]) -> InlineReport {
224    let mut pass = InlinePass::default();
225    pass.run(decls);
226    pass.report().clone()
227}
228/// Run the inlining pass with custom configuration and return a report.
229pub fn run_inline_pass_with_config(
230    decls: &mut [LcnfFunDecl],
231    config: InlineConfig,
232) -> InlineReport {
233    let mut pass = InlinePass::new(config);
234    pass.run(decls);
235    pass.report().clone()
236}
237/// Collect all function names called in an expression.
238#[allow(dead_code)]
239pub fn collect_callees(expr: &LcnfExpr) -> Vec<String> {
240    let mut out = Vec::new();
241    collect_callees_rec(expr, &mut out);
242    out
243}
244#[allow(dead_code)]
245pub(super) fn collect_callees_rec(expr: &LcnfExpr, out: &mut Vec<String>) {
246    match expr {
247        LcnfExpr::Let { value, body, .. } => {
248            if let LcnfLetValue::App(LcnfArg::Lit(LcnfLit::Str(name)), _) = value {
249                if !out.contains(name) {
250                    out.push(name.clone());
251                }
252            }
253            collect_callees_rec(body, out);
254        }
255        LcnfExpr::Case { alts, default, .. } => {
256            for alt in alts {
257                collect_callees_rec(&alt.body, out);
258            }
259            if let Some(def) = default {
260                collect_callees_rec(def, out);
261            }
262        }
263        LcnfExpr::TailCall(LcnfArg::Lit(LcnfLit::Str(name)), _) => {
264            if !out.contains(name) {
265                out.push(name.clone());
266            }
267        }
268        _ => {}
269    }
270}
271#[allow(dead_code)]
272pub(super) fn inline_subst(expr: LcnfExpr, from: LcnfVarId, to: LcnfArg) -> LcnfExpr {
273    match expr {
274        LcnfExpr::Let {
275            id,
276            name,
277            ty,
278            value,
279            body,
280        } => {
281            let value2 = inline_subst_value(value, from, &to);
282            let body2 = inline_subst(*body, from, to);
283            LcnfExpr::Let {
284                id,
285                name,
286                ty,
287                value: value2,
288                body: Box::new(body2),
289            }
290        }
291        LcnfExpr::Case {
292            scrutinee,
293            scrutinee_ty,
294            alts,
295            default,
296        } => {
297            let s2 = if scrutinee == from {
298                match &to {
299                    LcnfArg::Var(v) => *v,
300                    _ => scrutinee,
301                }
302            } else {
303                scrutinee
304            };
305            let alts2 = alts
306                .into_iter()
307                .map(|alt| crate::lcnf::LcnfAlt {
308                    ctor_name: alt.ctor_name,
309                    ctor_tag: alt.ctor_tag,
310                    params: alt.params,
311                    body: inline_subst(alt.body, from, to.clone()),
312                })
313                .collect();
314            let default2 = default.map(|d| Box::new(inline_subst(*d, from, to)));
315            LcnfExpr::Case {
316                scrutinee: s2,
317                scrutinee_ty,
318                alts: alts2,
319                default: default2,
320            }
321        }
322        LcnfExpr::Return(arg) => LcnfExpr::Return(inline_subst_arg(arg, from, &to)),
323        LcnfExpr::TailCall(func, args) => LcnfExpr::TailCall(
324            inline_subst_arg(func, from, &to),
325            args.into_iter()
326                .map(|a| inline_subst_arg(a, from, &to))
327                .collect(),
328        ),
329        LcnfExpr::Unreachable => LcnfExpr::Unreachable,
330    }
331}
332#[allow(dead_code)]
333pub(super) fn inline_subst_arg(arg: LcnfArg, from: LcnfVarId, to: &LcnfArg) -> LcnfArg {
334    match &arg {
335        LcnfArg::Var(id) if *id == from => to.clone(),
336        _ => arg,
337    }
338}
339#[allow(dead_code)]
340pub(super) fn inline_subst_value(
341    value: LcnfLetValue,
342    from: LcnfVarId,
343    to: &LcnfArg,
344) -> LcnfLetValue {
345    match value {
346        LcnfLetValue::App(func, args) => LcnfLetValue::App(
347            inline_subst_arg(func, from, to),
348            args.into_iter()
349                .map(|a| inline_subst_arg(a, from, to))
350                .collect(),
351        ),
352        LcnfLetValue::Ctor(name, tag, args) => LcnfLetValue::Ctor(
353            name,
354            tag,
355            args.into_iter()
356                .map(|a| inline_subst_arg(a, from, to))
357                .collect(),
358        ),
359        LcnfLetValue::Proj(name, idx, var) => {
360            let v2 = if var == from {
361                match to {
362                    LcnfArg::Var(v) => *v,
363                    _ => var,
364                }
365            } else {
366                var
367            };
368            LcnfLetValue::Proj(name, idx, v2)
369        }
370        LcnfLetValue::FVar(var) => {
371            if var == from {
372                match to {
373                    LcnfArg::Var(v) => LcnfLetValue::FVar(*v),
374                    _ => LcnfLetValue::FVar(var),
375                }
376            } else {
377                LcnfLetValue::FVar(var)
378            }
379        }
380        LcnfLetValue::Reset(var) => {
381            let v2 = if var == from {
382                match to {
383                    LcnfArg::Var(v) => *v,
384                    _ => var,
385                }
386            } else {
387                var
388            };
389            LcnfLetValue::Reset(v2)
390        }
391        LcnfLetValue::Reuse(slot, name, tag, args) => {
392            let s2 = if slot == from {
393                match to {
394                    LcnfArg::Var(v) => *v,
395                    _ => slot,
396                }
397            } else {
398                slot
399            };
400            LcnfLetValue::Reuse(
401                s2,
402                name,
403                tag,
404                args.into_iter()
405                    .map(|a| inline_subst_arg(a, from, to))
406                    .collect(),
407            )
408        }
409        other => other,
410    }
411}
412#[cfg(test)]
413mod inline_tests {
414    use super::*;
415    use crate::lcnf::LcnfFunDecl;
416    pub(super) fn make_trivial_decl(name: &str) -> LcnfFunDecl {
417        LcnfFunDecl {
418            name: name.to_owned(),
419            original_name: None,
420            params: vec![],
421            ret_type: LcnfType::Object,
422            body: LcnfExpr::Return(LcnfArg::Erased),
423            is_recursive: false,
424            is_lifted: false,
425            inline_cost: 1,
426        }
427    }
428    pub(super) fn make_calling_decl(from: &str, to: &str) -> LcnfFunDecl {
429        LcnfFunDecl {
430            name: from.to_owned(),
431            original_name: None,
432            params: vec![],
433            ret_type: LcnfType::Object,
434            body: LcnfExpr::TailCall(LcnfArg::Lit(LcnfLit::Str(to.to_owned())), vec![]),
435            is_recursive: false,
436            is_lifted: false,
437            inline_cost: 2,
438        }
439    }
440    #[test]
441    pub(super) fn inline_decision_basics() {
442        assert!(InlineDecision::Always.should_inline(0));
443        assert!(!InlineDecision::Never.should_inline(0));
444        assert!(InlineDecision::Heuristic(0.8).should_inline(0));
445        assert!(!InlineDecision::Heuristic(0.3).should_inline(0));
446        assert!(InlineDecision::OnceOnly.should_inline(0));
447        assert!(!InlineDecision::OnceOnly.should_inline(1));
448    }
449    #[test]
450    pub(super) fn inline_cost_net_gain() {
451        let cost = InlineCost {
452            body_size: 5,
453            call_overhead: 4,
454            estimated_savings: 3,
455        };
456        assert_eq!(cost.net_gain(), 2);
457        assert!(cost.is_profitable());
458    }
459    #[test]
460    pub(super) fn call_site_benefit() {
461        let site = CallSite::new("f", "g", 1, true, false);
462        assert_eq!(site.inline_benefit(), 13);
463        let rec = CallSite::new("f", "f", 2, false, true);
464        assert_eq!(rec.inline_benefit(), 2);
465    }
466    #[test]
467    pub(super) fn inline_profile_hot() {
468        let mut p = InlineProfile::new();
469        for _ in 0..5 {
470            p.record_call("foo");
471        }
472        assert!(p.is_hot("foo", 5));
473        assert!(!p.is_hot("foo", 6));
474        assert_eq!(p.top_callees(1)[0].0, "foo");
475    }
476    #[test]
477    pub(super) fn heuristics_decide_tiny_fn() {
478        let h = InlineHeuristics::default();
479        let decl = make_trivial_decl("f");
480        let dec = h.decide(&decl, &InlineProfile::new());
481        assert_eq!(dec, InlineDecision::Always);
482    }
483    #[test]
484    pub(super) fn inlining_context_cycle() {
485        let mut ctx = InliningContext::new();
486        assert!(ctx.push_call("foo"));
487        assert!(!ctx.push_call("foo"));
488        ctx.pop_call();
489        assert!(ctx.push_call("foo"));
490    }
491    #[test]
492    pub(super) fn estimate_size_trivial() {
493        let decl = make_trivial_decl("f");
494        assert_eq!(estimate_size(&decl), 2);
495    }
496    #[test]
497    pub(super) fn inline_pass_smoke() {
498        let mut decls = vec![make_trivial_decl("main"), make_calling_decl("f", "main")];
499        let report = run_inline_pass(&mut decls);
500        let _ = report.summary();
501    }
502    #[test]
503    pub(super) fn tarjan_no_recursion() {
504        let decls = vec![make_trivial_decl("a"), make_trivial_decl("b")];
505        let mut scc = TarjanScc::new(&decls);
506        scc.compute();
507        assert!(!scc.is_recursive("a"));
508    }
509    #[test]
510    pub(super) fn budget_spend() {
511        let mut b = InlineBudget::new(100);
512        assert!(b.try_spend("f", 40));
513        assert!(b.try_spend("f", 40));
514        assert!(!b.try_spend("f", 40));
515        assert_eq!(b.remaining(), 20);
516    }
517    #[test]
518    pub(super) fn hot_path_trivial() {
519        let decl = make_trivial_decl("f");
520        assert!(!HotPath::extract(&decl).has_prefix());
521    }
522    #[test]
523    pub(super) fn speculative_inliner_committed() {
524        let mut si = SpeculativeInliner::new(0.6);
525        si.add(SpeculativeInlineRecord::new("f", "g", 0.8, "Nat"));
526        si.add(SpeculativeInlineRecord::new("f", "h", 0.3, "Bool"));
527        assert_eq!(si.committed().len(), 1);
528    }
529    #[test]
530    pub(super) fn annotation_registry() {
531        let mut reg = InlineAnnotationRegistry::new();
532        reg.register("f", InlineAnnotation::AlwaysInline);
533        reg.register("g", InlineAnnotation::NeverInline);
534        assert_eq!(
535            reg.apply("f", InlineDecision::Heuristic(0.2)),
536            InlineDecision::Always
537        );
538        assert_eq!(
539            reg.apply("g", InlineDecision::Always),
540            InlineDecision::Never
541        );
542    }
543    #[test]
544    pub(super) fn callee_size_table() {
545        let decls = vec![make_trivial_decl("a"), make_trivial_decl("b")];
546        let t = CalleeSizeTable::build(&decls);
547        assert_eq!(t.len(), 2);
548        assert!(t.size_of("a").is_some());
549    }
550    #[test]
551    pub(super) fn nesting_tracker() {
552        let mut t = NestingDepthTracker::new(2);
553        assert!(t.push());
554        assert!(t.push());
555        assert!(!t.push());
556        assert_eq!(t.limit_hit_count, 1);
557        assert_eq!(t.peak_depth, 2);
558        t.pop();
559        assert_eq!(t.remaining(), 1);
560    }
561    #[test]
562    pub(super) fn extended_stats_summary() {
563        let mut s = ExtendedInlineStats::new();
564        s.record_decision(&InlineDecision::Always, true);
565        s.record_size_change(100, 50);
566        assert_eq!(s.net_size_change(), 50);
567        assert!(s.summary().contains("InlineStats"));
568    }
569    #[test]
570    pub(super) fn call_freq_analyzer() {
571        let decls = vec![make_calling_decl("f", "g"), make_calling_decl("h", "g")];
572        let mut p = CallFrequencyAnalyzer::analyze(&decls);
573        assert_eq!(p.call_counts.get("g").copied(), Some(2));
574        CallFrequencyAnalyzer::mark_hot(&mut p, 2);
575        assert!(p.hot_functions.contains("g"));
576    }
577    #[test]
578    pub(super) fn recursive_limiter() {
579        let mut lim = RecursiveInlineLimiter::new(2);
580        assert!(lim.try_unroll("f"));
581        assert!(lim.try_unroll("f"));
582        assert!(!lim.try_unroll("f"));
583        lim.pop_unroll("f");
584        assert!(lim.try_unroll("f"));
585    }
586    #[test]
587    pub(super) fn extended_pass_init() {
588        let decls = vec![make_trivial_decl("main")];
589        let mut pass = ExtendedInlinePass::new(InlineConfig::default(), 10000);
590        pass.init_scc(&decls);
591        assert!(pass.scc.is_some());
592        assert_eq!(pass.size_table.len(), 1);
593    }
594    #[test]
595    pub(super) fn call_graph_build_and_query() {
596        let decls = vec![make_calling_decl("f", "g"), make_trivial_decl("g")];
597        let g = CallGraph::build(&decls);
598        assert_eq!(g.num_nodes(), 2);
599        assert_eq!(g.in_degree("g"), 1);
600        assert!(g.leaf_functions().contains(&"g"));
601    }
602    #[test]
603    pub(super) fn inline_order_bottom_up() {
604        let decls = vec![make_calling_decl("f", "g"), make_trivial_decl("g")];
605        let g = CallGraph::build(&decls);
606        let sched = InlineOrderScheduler::compute(&g);
607        let gp = sched.order.iter().position(|n| n == "g");
608        let fp = sched.order.iter().position(|n| n == "f");
609        if let (Some(gi), Some(fi)) = (gp, fp) {
610            assert!(gi < fi);
611        }
612    }
613    #[test]
614    pub(super) fn inline_trace_disabled() {
615        let mut t = InlineTrace::disabled();
616        t.record(InlineTraceEntry::new(0, "a", "b", "always", 1, true));
617        assert!(t.is_empty());
618    }
619    #[test]
620    pub(super) fn inline_trace_csv() {
621        let mut t = InlineTrace::new();
622        t.record(InlineTraceEntry::new(1, "f", "g", "always", 5, true));
623        let csv = t.to_csv();
624        assert!(csv.contains("f,g,always,5,true"));
625    }
626    #[test]
627    pub(super) fn inline_report_rate() {
628        let r = InlineReport {
629            total_calls_considered: 10,
630            inlined_count: 7,
631            skipped_recursive: 1,
632            skipped_too_large: 2,
633        };
634        assert!((r.inline_rate() - 0.7).abs() < 1e-9);
635        assert!(r.summary().contains("7/10"));
636    }
637    #[test]
638    pub(super) fn collect_callees_fn() {
639        let expr = LcnfExpr::TailCall(LcnfArg::Lit(LcnfLit::Str("foo".to_owned())), vec![]);
640        assert_eq!(collect_callees(&expr), vec!["foo".to_owned()]);
641    }
642}
643#[cfg(test)]
644mod inline_extended_tests {
645    use super::*;
646    #[test]
647    pub(super) fn inline_context_stack_basic() {
648        let mut stack = InlineContextStack::new();
649        assert_eq!(stack.depth(), 0);
650        stack.push("foo", 0);
651        stack.push("bar", 1);
652        assert_eq!(stack.depth(), 2);
653        assert!(stack.contains("foo"));
654        assert!(stack.contains("bar"));
655        assert!(!stack.contains("baz"));
656        let fp = stack.fingerprint();
657        assert!(fp.contains("foo"));
658        assert!(fp.contains("bar"));
659        let frame = stack.pop().expect("frame should be available to pop");
660        assert_eq!(frame.callee, "bar");
661        assert_eq!(stack.depth(), 1);
662    }
663    #[test]
664    pub(super) fn partial_inline_decision_will_inline() {
665        let d = PartialInlineDecision::full("f", 10);
666        assert!(d.will_inline());
667        let d2 = PartialInlineDecision::no_inline("g", "too_large");
668        assert!(!d2.will_inline());
669        let d3 = PartialInlineDecision::prefix("h", 3, 5);
670        assert!(d3.will_inline());
671        match d3.region {
672            PartialInlineRegion::Prefix(n) => assert_eq!(n, 3),
673            _ => panic!("expected Prefix"),
674        }
675    }
676    #[test]
677    pub(super) fn profitability_estimator_basic() {
678        let est = InlineProfitabilityEstimator::new();
679        assert!(est.is_profitable(2, 5.0, true));
680        assert!(!est.is_profitable(1000, 1.0, false));
681    }
682    #[test]
683    pub(super) fn clone_specializer_records() {
684        let mut cs = CloneSpecializer::new();
685        let name = cs.record("add", 0, "42");
686        assert!(name.starts_with("add_spec_0_42_c"));
687        assert_eq!(cs.count(), 1);
688    }
689    #[test]
690    pub(super) fn inline_history_tracking() {
691        let mut h = InlineHistory::new();
692        assert!(!h.has_seen("f", "g"));
693        h.mark_seen("f", "g");
694        assert!(h.has_seen("f", "g"));
695        assert_eq!(h.count(), 1);
696        h.reset();
697        assert!(!h.has_seen("f", "g"));
698        assert_eq!(h.count(), 0);
699    }
700    #[test]
701    pub(super) fn interprocedural_inline_pass_smoke() {
702        let config = InlineConfig::default();
703        let mut pass = InterproceduralInlinePass::new(config);
704        let mut decls: Vec<LcnfFunDecl> = vec![];
705        pass.run(&mut decls);
706        let report = pass.report();
707        assert!(report.contains("0 functions processed"));
708    }
709    #[test]
710    pub(super) fn inline_fusion_manager_basic() {
711        let mut mgr = InlineFusionManager::new();
712        let name = mgr.fuse("caller", "f", "g", 15);
713        assert!(name.starts_with("caller_fused_f_g_"));
714        assert_eq!(mgr.all_records().len(), 1);
715        assert_eq!(mgr.total_savings(), 15);
716        mgr.fuse("caller", "g", "h", 5);
717        assert_eq!(mgr.total_savings(), 20);
718    }
719    #[test]
720    pub(super) fn extended_inline_stats_default() {
721        let stats = ExtendedInlineStats::default();
722        assert_eq!(stats.total_functions_processed, 0);
723        assert!(stats.inlining_order.is_empty());
724    }
725}