Skip to main content

oxilean_codegen/opt_inline/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use crate::lcnf::{LcnfArg, LcnfExpr, LcnfFunDecl, LcnfLetValue, LcnfLit, LcnfType, LcnfVarId};
7use std::collections::{HashMap, HashSet};
8
9#[allow(dead_code)]
10#[derive(Default, Debug)]
11pub struct InlineTrace {
12    pub(super) entries: Vec<InlineTraceEntry>,
13    pub enabled: bool,
14}
15#[allow(dead_code)]
16impl InlineTrace {
17    pub fn new() -> Self {
18        InlineTrace {
19            entries: Vec::new(),
20            enabled: true,
21        }
22    }
23    pub fn disabled() -> Self {
24        InlineTrace {
25            entries: Vec::new(),
26            enabled: false,
27        }
28    }
29    pub fn record(&mut self, entry: InlineTraceEntry) {
30        if self.enabled {
31            self.entries.push(entry);
32        }
33    }
34    pub fn len(&self) -> usize {
35        self.entries.len()
36    }
37    pub fn is_empty(&self) -> bool {
38        self.entries.is_empty()
39    }
40    pub fn inlined_entries(&self) -> Vec<&InlineTraceEntry> {
41        self.entries.iter().filter(|e| e.did_inline).collect()
42    }
43    pub fn skipped_entries(&self) -> Vec<&InlineTraceEntry> {
44        self.entries.iter().filter(|e| !e.did_inline).collect()
45    }
46    pub fn entries_for_callee(&self, callee: &str) -> Vec<&InlineTraceEntry> {
47        self.entries.iter().filter(|e| e.callee == callee).collect()
48    }
49    pub fn to_csv(&self) -> String {
50        let header = "sweep,caller,callee,decision,callee_size,did_inline";
51        let rows: Vec<String> = self.entries.iter().map(|e| e.to_csv()).collect();
52        std::iter::once(header.to_owned())
53            .chain(rows)
54            .collect::<Vec<_>>()
55            .join("\n")
56    }
57    pub fn clear(&mut self) {
58        self.entries.clear();
59    }
60}
61#[allow(dead_code)]
62#[derive(Clone, Debug, Default)]
63pub struct RecursiveInlineLimiter {
64    pub unroll_depth: HashMap<String, u32>,
65    pub max_unroll: u32,
66    pub enforcements: u64,
67}
68#[allow(dead_code)]
69impl RecursiveInlineLimiter {
70    pub fn new(max_unroll: u32) -> Self {
71        RecursiveInlineLimiter {
72            max_unroll,
73            ..Default::default()
74        }
75    }
76    pub fn try_unroll(&mut self, fn_name: &str) -> bool {
77        let depth = self.unroll_depth.entry(fn_name.to_owned()).or_insert(0);
78        if *depth >= self.max_unroll {
79            self.enforcements += 1;
80            return false;
81        }
82        *depth += 1;
83        true
84    }
85    pub fn pop_unroll(&mut self, fn_name: &str) {
86        if let Some(d) = self.unroll_depth.get_mut(fn_name) {
87            if *d > 0 {
88                *d -= 1;
89            }
90        }
91    }
92    pub fn depth_of(&self, fn_name: &str) -> u32 {
93        self.unroll_depth.get(fn_name).copied().unwrap_or(0)
94    }
95    pub fn reset(&mut self) {
96        self.unroll_depth.clear();
97    }
98}
99#[allow(dead_code)]
100#[derive(Clone, Debug, PartialEq, Eq)]
101pub enum InlineAnnotation {
102    AlwaysInline,
103    NeverInline,
104    Default,
105    HotOnly { threshold: u64 },
106}
107/// Estimates the profitability of inlining a given call site.
108#[allow(dead_code)]
109#[derive(Debug, Clone, Default)]
110pub struct InlineProfitabilityEstimator {
111    /// Weight applied to callee size penalty.
112    pub(super) size_weight: f64,
113    /// Weight applied to call overhead savings.
114    pub(super) call_overhead_weight: f64,
115    /// Weight for hot-path bonus.
116    pub(super) hot_path_weight: f64,
117}
118#[allow(dead_code)]
119impl InlineProfitabilityEstimator {
120    /// Create estimator with default weights.
121    pub fn new() -> Self {
122        Self {
123            size_weight: 1.0,
124            call_overhead_weight: 2.0,
125            hot_path_weight: 3.0,
126        }
127    }
128    /// Estimate profitability score (positive = profitable).
129    /// - `callee_size`: estimated IR node count
130    /// - `call_overhead`: estimated call overhead in cycles
131    /// - `is_hot`: whether call site is on a hot path
132    pub fn estimate(&self, callee_size: usize, call_overhead: f64, is_hot: bool) -> f64 {
133        let size_penalty = callee_size as f64 * self.size_weight;
134        let overhead_savings = call_overhead * self.call_overhead_weight;
135        let hot_bonus = if is_hot {
136            self.hot_path_weight * 10.0
137        } else {
138            0.0
139        };
140        overhead_savings + hot_bonus - size_penalty
141    }
142    /// Returns `true` if inlining is estimated to be profitable.
143    pub fn is_profitable(&self, callee_size: usize, call_overhead: f64, is_hot: bool) -> bool {
144        self.estimate(callee_size, call_overhead, is_hot) > 0.0
145    }
146}
147#[allow(dead_code)]
148#[derive(Default, Debug)]
149pub struct InlineAnnotationRegistry {
150    pub(super) annotations: HashMap<String, InlineAnnotation>,
151}
152#[allow(dead_code)]
153impl InlineAnnotationRegistry {
154    pub fn new() -> Self {
155        Self::default()
156    }
157    pub fn register(&mut self, fn_name: impl Into<String>, ann: InlineAnnotation) {
158        self.annotations.insert(fn_name.into(), ann);
159    }
160    pub fn get(&self, fn_name: &str) -> &InlineAnnotation {
161        self.annotations
162            .get(fn_name)
163            .unwrap_or(&InlineAnnotation::Default)
164    }
165    pub fn has_annotation(&self, fn_name: &str) -> bool {
166        self.annotations.contains_key(fn_name)
167    }
168    pub fn apply(&self, fn_name: &str, decision: InlineDecision) -> InlineDecision {
169        match self.get(fn_name) {
170            InlineAnnotation::AlwaysInline => InlineDecision::Always,
171            InlineAnnotation::NeverInline => InlineDecision::Never,
172            InlineAnnotation::Default => decision,
173            InlineAnnotation::HotOnly { threshold } => {
174                InlineDecision::Heuristic(*threshold as f64 / 100.0)
175            }
176        }
177    }
178    pub fn len(&self) -> usize {
179        self.annotations.len()
180    }
181    pub fn is_empty(&self) -> bool {
182        self.annotations.is_empty()
183    }
184}
185#[allow(dead_code)]
186#[derive(Debug, Clone)]
187pub struct InlineBudget {
188    pub total_budget: u64,
189    pub consumed: u64,
190    pub reserved_always: u64,
191    pub per_fn_limit: HashMap<String, u64>,
192    pub per_fn_spent: HashMap<String, u64>,
193}
194#[allow(dead_code)]
195impl InlineBudget {
196    pub fn new(total_budget: u64) -> Self {
197        InlineBudget {
198            total_budget,
199            consumed: 0,
200            reserved_always: total_budget / 4,
201            per_fn_limit: HashMap::new(),
202            per_fn_spent: HashMap::new(),
203        }
204    }
205    pub fn set_fn_limit(&mut self, fn_name: impl Into<String>, limit: u64) {
206        self.per_fn_limit.insert(fn_name.into(), limit);
207    }
208    pub fn try_spend(&mut self, caller: &str, cost: u64) -> bool {
209        if self.consumed + cost > self.total_budget {
210            return false;
211        }
212        if let Some(&limit) = self.per_fn_limit.get(caller) {
213            let spent = self.per_fn_spent.get(caller).copied().unwrap_or(0);
214            if spent + cost > limit {
215                return false;
216            }
217            *self.per_fn_spent.entry(caller.to_owned()).or_insert(0) += cost;
218        }
219        self.consumed += cost;
220        true
221    }
222    pub fn remaining(&self) -> u64 {
223        self.total_budget.saturating_sub(self.consumed)
224    }
225    pub fn is_exhausted(&self) -> bool {
226        self.consumed >= self.total_budget
227    }
228    pub fn fraction_consumed(&self) -> f64 {
229        if self.total_budget == 0 {
230            1.0
231        } else {
232            self.consumed as f64 / self.total_budget as f64
233        }
234    }
235    pub fn reset_per_fn(&mut self) {
236        self.per_fn_spent.clear();
237    }
238    pub fn heavy_spenders(&self, threshold: u64) -> Vec<(&str, u64)> {
239        self.per_fn_spent
240            .iter()
241            .filter(|(_, &s)| s >= threshold)
242            .map(|(n, &s)| (n.as_str(), s))
243            .collect()
244    }
245}
246/// Represents a fusion of two adjacent inlined callees at the same call site.
247#[allow(dead_code)]
248#[derive(Debug, Clone)]
249pub struct InlineFusionRecord {
250    pub caller: String,
251    pub first_callee: String,
252    pub second_callee: String,
253    pub fused_name: String,
254    pub savings_estimate: i32,
255}
256#[allow(dead_code)]
257#[derive(Clone, Debug)]
258pub struct InlineTraceEntry {
259    pub sweep: u32,
260    pub caller: String,
261    pub callee: String,
262    pub decision: String,
263    pub callee_size: u64,
264    pub did_inline: bool,
265}
266#[allow(dead_code)]
267impl InlineTraceEntry {
268    pub fn new(
269        sweep: u32,
270        caller: impl Into<String>,
271        callee: impl Into<String>,
272        decision: impl Into<String>,
273        callee_size: u64,
274        did_inline: bool,
275    ) -> Self {
276        InlineTraceEntry {
277            sweep,
278            caller: caller.into(),
279            callee: callee.into(),
280            decision: decision.into(),
281            callee_size,
282            did_inline,
283        }
284    }
285    pub fn to_csv(&self) -> String {
286        format!(
287            "{},{},{},{},{},{}",
288            self.sweep, self.caller, self.callee, self.decision, self.callee_size, self.did_inline
289        )
290    }
291}
292/// A single frame in the inline context stack.
293#[allow(dead_code)]
294#[derive(Debug, Clone)]
295pub struct InlineContextFrame {
296    /// The function being inlined at this frame.
297    pub callee: String,
298    /// The call-site depth at which this inlining occurred.
299    pub depth: usize,
300    /// Unique ID for this inlining event.
301    pub event_id: u64,
302}
303/// Context-sensitive inline stack, used to detect context-specific
304/// inlining opportunities (e.g., inlining differently depending on call chain).
305#[allow(dead_code)]
306#[derive(Debug, Clone, Default)]
307pub struct InlineContextStack {
308    pub(super) frames: Vec<InlineContextFrame>,
309    pub(super) next_event_id: u64,
310}
311#[allow(dead_code)]
312impl InlineContextStack {
313    /// Create a new empty context stack.
314    pub fn new() -> Self {
315        Self::default()
316    }
317    /// Push a new frame onto the stack.
318    pub fn push(&mut self, callee: &str, depth: usize) -> u64 {
319        let id = self.next_event_id;
320        self.next_event_id += 1;
321        self.frames.push(InlineContextFrame {
322            callee: callee.to_owned(),
323            depth,
324            event_id: id,
325        });
326        id
327    }
328    /// Pop the most recent frame.
329    pub fn pop(&mut self) -> Option<InlineContextFrame> {
330        self.frames.pop()
331    }
332    /// Returns the current depth of the context stack.
333    pub fn depth(&self) -> usize {
334        self.frames.len()
335    }
336    /// Returns `true` if `callee` appears anywhere in the current context.
337    pub fn contains(&self, callee: &str) -> bool {
338        self.frames.iter().any(|f| f.callee == callee)
339    }
340    /// Returns the context fingerprint as a concatenation of callee names.
341    pub fn fingerprint(&self) -> String {
342        self.frames
343            .iter()
344            .map(|f| f.callee.as_str())
345            .collect::<Vec<_>>()
346            .join("->")
347    }
348}
349/// Tracks the current inlining call stack to detect and prevent cycles.
350#[derive(Debug, Clone)]
351pub struct InliningContext {
352    /// Current recursion depth of the inliner.
353    pub depth: u32,
354    /// Stack of function names currently being inlined.
355    pub call_stack: Vec<String>,
356    /// Named-value substitutions active in the current scope.
357    pub substitutions: HashMap<String, LcnfExpr>,
358}
359impl InliningContext {
360    /// Create a fresh context at depth 0.
361    pub fn new() -> Self {
362        InliningContext {
363            depth: 0,
364            call_stack: Vec::new(),
365            substitutions: HashMap::new(),
366        }
367    }
368    /// Attempt to push `name` onto the call stack.
369    ///
370    /// Returns `false` (and does NOT push) when `name` is already on the
371    /// stack, indicating a recursive cycle.
372    pub fn push_call(&mut self, name: &str) -> bool {
373        if self.has_cycle(name) {
374            return false;
375        }
376        self.call_stack.push(name.to_owned());
377        self.depth += 1;
378        true
379    }
380    /// Pop the most recently pushed call from the stack.
381    pub fn pop_call(&mut self) {
382        self.call_stack.pop();
383        if self.depth > 0 {
384            self.depth -= 1;
385        }
386    }
387    /// Returns `true` when `name` is already present in the call stack.
388    pub fn has_cycle(&self, name: &str) -> bool {
389        self.call_stack.iter().any(|n| n == name)
390    }
391}
392/// Top-level configuration for the inlining pass.
393#[derive(Debug, Clone)]
394pub struct InlineConfig {
395    /// Tunable heuristic parameters.
396    pub heuristics: InlineHeuristics,
397    /// Whether to allow (limited) inlining of recursive functions.
398    pub enable_recursive_inlining: bool,
399    /// Whether to prioritise inlining of hot (frequently-called) functions.
400    pub enable_hot_inlining: bool,
401    /// Maximum number of inlining sweeps over the declaration list.
402    pub max_passes: u32,
403}
404/// Describes which portion of a callee body is eligible for partial inlining.
405#[allow(dead_code)]
406#[derive(Debug, Clone)]
407pub enum PartialInlineRegion {
408    /// Inline the first N let-bindings of the function body.
409    Prefix(usize),
410    /// Inline the return value only (tail region).
411    TailOnly,
412    /// Inline everything (full inline).
413    Full,
414    /// Do not inline.
415    None,
416}
417/// Statistics gathered by a completed inlining pass.
418#[derive(Debug, Clone, Default)]
419pub struct InlineReport {
420    /// Total number of call sites examined.
421    pub total_calls_considered: usize,
422    /// Number of call sites that were actually inlined.
423    pub inlined_count: usize,
424    /// Calls skipped because the callee is recursive.
425    pub skipped_recursive: usize,
426    /// Calls skipped because the callee body is too large.
427    pub skipped_too_large: usize,
428}
429impl InlineReport {
430    /// Human-readable one-line summary of the pass results.
431    pub fn summary(&self) -> String {
432        format!(
433            "inline: {}/{} inlined (recursive_skip={}, size_skip={})",
434            self.inlined_count,
435            self.total_calls_considered,
436            self.skipped_recursive,
437            self.skipped_too_large,
438        )
439    }
440    /// Fraction of considered call sites that were inlined (0.0–1.0).
441    pub fn inline_rate(&self) -> f64 {
442        if self.total_calls_considered == 0 {
443            0.0
444        } else {
445            self.inlined_count as f64 / self.total_calls_considered as f64
446        }
447    }
448}
449/// A single call site encountered during the inlining pass.
450#[derive(Debug, Clone)]
451pub struct CallSite {
452    /// Name of the calling function.
453    pub caller: String,
454    /// Name of the called function.
455    pub callee: String,
456    /// Unique identifier for this call site (within the pass).
457    pub call_id: u64,
458    /// Whether the call is in tail position.
459    pub is_tail_call: bool,
460    /// Whether the callee is (mutually) recursive.
461    pub is_self_recursive: bool,
462}
463impl CallSite {
464    /// Create a new call site record.
465    pub fn new(
466        caller: impl Into<String>,
467        callee: impl Into<String>,
468        call_id: u64,
469        is_tail_call: bool,
470        is_self_recursive: bool,
471    ) -> Self {
472        CallSite {
473            caller: caller.into(),
474            callee: callee.into(),
475            call_id,
476            is_tail_call,
477            is_self_recursive,
478        }
479    }
480    /// Estimated benefit of inlining this call site (in abstract units).
481    ///
482    /// Tail calls get a small bonus because inlining them can enable TCO.
483    /// Self-recursive calls are penalised because inlining them risks blowup.
484    pub fn inline_benefit(&self) -> u64 {
485        let base: u64 = 10;
486        let tail_bonus: u64 = if self.is_tail_call { 3 } else { 0 };
487        let recursive_penalty: u64 = if self.is_self_recursive { 8 } else { 0 };
488        base.saturating_add(tail_bonus)
489            .saturating_sub(recursive_penalty)
490    }
491}
492/// Cost model for inlining a single call site.
493#[derive(Debug, Clone)]
494pub struct InlineCost {
495    /// Estimated code-size contribution of the callee body.
496    pub body_size: u64,
497    /// Overhead of the call itself (stack frame, arg passing, return).
498    pub call_overhead: u64,
499    /// Estimated savings from inlining (e.g. eliminated argument allocations,
500    /// constant propagation opportunities).  May be negative.
501    pub estimated_savings: i64,
502}
503impl InlineCost {
504    /// Construct a zeroed cost estimate.
505    pub fn new() -> Self {
506        InlineCost {
507            body_size: 0,
508            call_overhead: 4,
509            estimated_savings: 0,
510        }
511    }
512    /// Net gain from inlining: positive means inlining is beneficial.
513    pub fn net_gain(&self) -> i64 {
514        self.estimated_savings + self.call_overhead as i64 - self.body_size as i64
515    }
516    /// Returns `true` when inlining is expected to be profitable.
517    pub fn is_profitable(&self) -> bool {
518        self.net_gain() > 0
519    }
520}
521#[allow(dead_code)]
522#[derive(Default, Debug)]
523pub struct SpeculativeInliner {
524    pub records: Vec<SpeculativeInlineRecord>,
525    pub threshold: f64,
526}
527#[allow(dead_code)]
528impl SpeculativeInliner {
529    pub fn new(threshold: f64) -> Self {
530        SpeculativeInliner {
531            records: Vec::new(),
532            threshold,
533        }
534    }
535    pub fn add(&mut self, record: SpeculativeInlineRecord) {
536        self.records.push(record);
537    }
538    pub fn committed(&self) -> Vec<&SpeculativeInlineRecord> {
539        self.records
540            .iter()
541            .filter(|r| r.should_speculate(self.threshold))
542            .collect()
543    }
544    pub fn pending(&self) -> Vec<&SpeculativeInlineRecord> {
545        self.records.iter().filter(|r| !r.confirmed).collect()
546    }
547    pub fn confirm_callee(&mut self, callee: &str) {
548        for r in &mut self.records {
549            if r.callee == callee {
550                r.confirm();
551            }
552        }
553    }
554    pub fn total_confidence(&self) -> f64 {
555        self.records.iter().map(|r| r.confidence).sum()
556    }
557}
558/// Decision about whether a function should be inlined at a call site.
559#[derive(Debug, Clone, PartialEq)]
560pub enum InlineDecision {
561    /// Always inline regardless of call count or size.
562    Always,
563    /// Never inline this function.
564    Never,
565    /// Inline based on a heuristic score (0.0–1.0 probability).
566    Heuristic(f64),
567    /// Inline only the first call site encountered.
568    OnceOnly,
569}
570impl InlineDecision {
571    /// Decide whether to inline given the current call count for this site.
572    ///
573    /// `call_count` is the number of times this decision has already resulted
574    /// in an inline for the same callee.
575    pub fn should_inline(&self, call_count: u64) -> bool {
576        match self {
577            InlineDecision::Always => true,
578            InlineDecision::Never => false,
579            InlineDecision::Heuristic(score) => *score >= 0.5,
580            InlineDecision::OnceOnly => call_count == 0,
581        }
582    }
583}
584/// Tracks and manages inline fusion decisions.
585#[allow(dead_code)]
586#[derive(Debug, Default)]
587pub struct InlineFusionManager {
588    pub(super) records: Vec<InlineFusionRecord>,
589    pub(super) next_id: u32,
590}
591#[allow(dead_code)]
592impl InlineFusionManager {
593    pub fn new() -> Self {
594        Self::default()
595    }
596    /// Record a fusion event and return the fused name.
597    pub fn fuse(&mut self, caller: &str, first: &str, second: &str, savings: i32) -> String {
598        let id = self.next_id;
599        self.next_id += 1;
600        let fused_name = format!("{}_fused_{}_{}_{}", caller, first, second, id);
601        self.records.push(InlineFusionRecord {
602            caller: caller.to_owned(),
603            first_callee: first.to_owned(),
604            second_callee: second.to_owned(),
605            fused_name: fused_name.clone(),
606            savings_estimate: savings,
607        });
608        fused_name
609    }
610    /// Returns all fusion records.
611    pub fn all_records(&self) -> &[InlineFusionRecord] {
612        &self.records
613    }
614    /// Total savings estimate across all fusions.
615    pub fn total_savings(&self) -> i32 {
616        self.records.iter().map(|r| r.savings_estimate).sum()
617    }
618}
619#[allow(dead_code)]
620#[derive(Default, Debug)]
621pub struct CallGraph {
622    pub edges: Vec<CallGraphEdge>,
623    pub functions: HashSet<String>,
624}
625#[allow(dead_code)]
626impl CallGraph {
627    pub fn new() -> Self {
628        Self::default()
629    }
630    pub fn build(decls: &[LcnfFunDecl]) -> Self {
631        let mut g = Self::new();
632        for decl in decls {
633            g.functions.insert(decl.name.clone());
634            for callee in collect_callees(&decl.body) {
635                g.edges.push(CallGraphEdge::new(
636                    decl.name.clone(),
637                    callee.clone(),
638                    1,
639                    false,
640                ));
641                g.functions.insert(callee);
642            }
643        }
644        g
645    }
646    pub fn callers_of(&self, fn_name: &str) -> Vec<&str> {
647        self.edges
648            .iter()
649            .filter(|e| e.to == fn_name)
650            .map(|e| e.from.as_str())
651            .collect()
652    }
653    pub fn callees_of(&self, fn_name: &str) -> Vec<&str> {
654        self.edges
655            .iter()
656            .filter(|e| e.from == fn_name)
657            .map(|e| e.to.as_str())
658            .collect()
659    }
660    pub fn in_degree(&self, fn_name: &str) -> usize {
661        self.edges.iter().filter(|e| e.to == fn_name).count()
662    }
663    pub fn out_degree(&self, fn_name: &str) -> usize {
664        self.edges.iter().filter(|e| e.from == fn_name).count()
665    }
666    pub fn num_edges(&self) -> usize {
667        self.edges.len()
668    }
669    pub fn num_nodes(&self) -> usize {
670        self.functions.len()
671    }
672    pub fn single_caller_functions(&self) -> Vec<&str> {
673        self.functions
674            .iter()
675            .filter(|f| self.in_degree(f) == 1)
676            .map(|f| f.as_str())
677            .collect()
678    }
679    pub fn leaf_functions(&self) -> Vec<&str> {
680        self.functions
681            .iter()
682            .filter(|f| self.out_degree(f) == 0)
683            .map(|f| f.as_str())
684            .collect()
685    }
686}
687/// Record of a clone-and-specialize operation.
688#[allow(dead_code)]
689#[derive(Debug, Clone)]
690pub struct CloneSpecRecord {
691    pub original_callee: String,
692    pub specialized_name: String,
693    pub arg_index: usize,
694    pub specialized_to: String,
695}
696/// Simple monotonic counter for fresh variable IDs used during inlining.
697pub struct FreshVarGen {
698    pub(super) next: u64,
699}
700impl FreshVarGen {
701    pub(super) fn new(start: u64) -> Self {
702        FreshVarGen { next: start }
703    }
704    pub(super) fn fresh(&mut self) -> LcnfVarId {
705        let id = self.next;
706        self.next += 1;
707        LcnfVarId(id)
708    }
709}
710/// A node in the call-graph SCC analysis.
711#[allow(dead_code)]
712#[derive(Clone, Debug)]
713pub struct SccNode {
714    pub name: String,
715    pub callees: Vec<String>,
716    pub disc: Option<u32>,
717    pub low: Option<u32>,
718    pub on_stack: bool,
719}
720#[allow(dead_code)]
721impl SccNode {
722    pub fn new(name: impl Into<String>, callees: Vec<String>) -> Self {
723        SccNode {
724            name: name.into(),
725            callees,
726            disc: None,
727            low: None,
728            on_stack: false,
729        }
730    }
731}
732#[allow(dead_code)]
733pub struct InlineOrderScheduler {
734    pub order: Vec<String>,
735    pub is_valid: bool,
736}
737#[allow(dead_code)]
738impl InlineOrderScheduler {
739    pub fn compute(graph: &CallGraph) -> Self {
740        let mut visited = HashSet::new();
741        let mut order = Vec::new();
742        for fn_name in &graph.functions {
743            Self::dfs(fn_name, graph, &mut visited, &mut order);
744        }
745        InlineOrderScheduler {
746            order,
747            is_valid: true,
748        }
749    }
750    pub(super) fn dfs(
751        node: &str,
752        graph: &CallGraph,
753        visited: &mut HashSet<String>,
754        order: &mut Vec<String>,
755    ) {
756        if !visited.insert(node.to_owned()) {
757            return;
758        }
759        for callee in graph.callees_of(node) {
760            Self::dfs(callee, graph, visited, order);
761        }
762        order.push(node.to_owned());
763    }
764    pub fn reverse(&mut self) {
765        self.order.reverse();
766    }
767    pub fn len(&self) -> usize {
768        self.order.len()
769    }
770    pub fn is_empty(&self) -> bool {
771        self.order.is_empty()
772    }
773}
774#[allow(dead_code)]
775pub struct PostInlineCleanup {
776    pub dead_removed: usize,
777    pub copies_collapsed: usize,
778    pub tail_calls_simplified: usize,
779}
780#[allow(dead_code)]
781impl PostInlineCleanup {
782    pub fn new() -> Self {
783        PostInlineCleanup {
784            dead_removed: 0,
785            copies_collapsed: 0,
786            tail_calls_simplified: 0,
787        }
788    }
789    pub fn run(&mut self, decl: &mut LcnfFunDecl) {
790        let new_body = self.cleanup_expr(decl.body.clone());
791        decl.body = new_body;
792    }
793    pub(super) fn cleanup_expr(&mut self, expr: LcnfExpr) -> LcnfExpr {
794        match expr {
795            LcnfExpr::Let {
796                id,
797                name,
798                ty,
799                value,
800                body,
801            } => {
802                if let LcnfLetValue::FVar(src) = &value {
803                    self.copies_collapsed += 1;
804                    let src_arg = LcnfArg::Var(*src);
805                    let new_body = self.cleanup_expr(*body);
806                    return inline_subst(new_body, id, src_arg);
807                }
808                let new_body = self.cleanup_expr(*body);
809                LcnfExpr::Let {
810                    id,
811                    name,
812                    ty,
813                    value,
814                    body: Box::new(new_body),
815                }
816            }
817            LcnfExpr::Case {
818                scrutinee,
819                scrutinee_ty,
820                alts,
821                default,
822            } => {
823                let alts2 = alts
824                    .into_iter()
825                    .map(|alt| crate::lcnf::LcnfAlt {
826                        ctor_name: alt.ctor_name,
827                        ctor_tag: alt.ctor_tag,
828                        params: alt.params,
829                        body: self.cleanup_expr(alt.body),
830                    })
831                    .collect();
832                let default2 = default.map(|d| Box::new(self.cleanup_expr(*d)));
833                LcnfExpr::Case {
834                    scrutinee,
835                    scrutinee_ty,
836                    alts: alts2,
837                    default: default2,
838                }
839            }
840            other => other,
841        }
842    }
843    pub fn summary(&self) -> String {
844        format!(
845            "PostInlineCleanup: dead={}, copies_collapsed={}",
846            self.dead_removed, self.copies_collapsed
847        )
848    }
849}
850#[allow(dead_code)]
851pub struct ExtendedInlinePass {
852    pub config: InlineConfig,
853    pub scc: Option<TarjanScc>,
854    pub budget: InlineBudget,
855    pub annotations: InlineAnnotationRegistry,
856    pub size_table: CalleeSizeTable,
857    pub speculative: SpeculativeInliner,
858    pub recursive_limiter: RecursiveInlineLimiter,
859    pub stats: ExtendedInlineStats,
860    pub cleanup: PostInlineCleanup,
861}
862#[allow(dead_code)]
863impl ExtendedInlinePass {
864    pub fn new(config: InlineConfig, total_budget: u64) -> Self {
865        let max_unroll = if config.enable_recursive_inlining {
866            2
867        } else {
868            0
869        };
870        ExtendedInlinePass {
871            config,
872            scc: None,
873            budget: InlineBudget::new(total_budget),
874            annotations: InlineAnnotationRegistry::new(),
875            size_table: CalleeSizeTable::new(),
876            speculative: SpeculativeInliner::new(0.7),
877            recursive_limiter: RecursiveInlineLimiter::new(max_unroll),
878            stats: ExtendedInlineStats::new(),
879            cleanup: PostInlineCleanup::new(),
880        }
881    }
882    pub fn init_scc(&mut self, decls: &[LcnfFunDecl]) {
883        let mut scc = TarjanScc::new(decls);
884        scc.compute();
885        self.scc = Some(scc);
886        self.size_table = CalleeSizeTable::build(decls);
887    }
888    pub fn should_inline_extended(
889        &mut self,
890        caller: &str,
891        callee: &str,
892        _profile: &InlineProfile,
893    ) -> bool {
894        if *self.annotations.get(callee) == InlineAnnotation::NeverInline {
895            return false;
896        }
897        if self.budget.is_exhausted() {
898            return false;
899        }
900        let callee_size = self.size_table.size_of(callee).unwrap_or(u64::MAX);
901        let is_rec = self.scc.as_ref().map_or(false, |s| s.is_recursive(callee));
902        if is_rec {
903            if !self.config.enable_recursive_inlining {
904                return false;
905            }
906            if !self.recursive_limiter.try_unroll(callee) {
907                return false;
908            }
909        }
910        if callee_size > self.config.heuristics.never_inline_size {
911            if is_rec {
912                self.recursive_limiter.pop_unroll(callee);
913            }
914            return false;
915        }
916        if *self.annotations.get(callee) == InlineAnnotation::AlwaysInline
917            && callee_size <= self.config.heuristics.max_inline_size
918        {
919            return true;
920        }
921        if !self.budget.try_spend(caller, callee_size) {
922            if is_rec {
923                self.recursive_limiter.pop_unroll(callee);
924            }
925            return false;
926        }
927        true
928    }
929    pub fn run(&mut self, decls: &mut [LcnfFunDecl]) {
930        self.init_scc(decls);
931        let mut core = InlinePass::new(self.config.clone());
932        core.run(decls);
933        self.stats.always_inline_count = core.report().inlined_count;
934        for decl in decls.iter_mut() {
935            self.cleanup.run(decl);
936        }
937    }
938    pub fn extended_stats(&self) -> &ExtendedInlineStats {
939        &self.stats
940    }
941}
942/// Decision record for partial inlining of a function.
943#[allow(dead_code)]
944#[derive(Debug, Clone)]
945pub struct PartialInlineDecision {
946    pub callee: String,
947    pub region: PartialInlineRegion,
948    pub estimated_savings: i32,
949    pub reason: String,
950}
951#[allow(dead_code)]
952impl PartialInlineDecision {
953    /// Construct a "full inline" decision.
954    pub fn full(callee: &str, savings: i32) -> Self {
955        Self {
956            callee: callee.to_owned(),
957            region: PartialInlineRegion::Full,
958            estimated_savings: savings,
959            reason: "full".to_owned(),
960        }
961    }
962    /// Construct a "no inline" decision.
963    pub fn no_inline(callee: &str, reason: &str) -> Self {
964        Self {
965            callee: callee.to_owned(),
966            region: PartialInlineRegion::None,
967            estimated_savings: 0,
968            reason: reason.to_owned(),
969        }
970    }
971    /// Construct a "prefix" partial inline decision.
972    pub fn prefix(callee: &str, n: usize, savings: i32) -> Self {
973        Self {
974            callee: callee.to_owned(),
975            region: PartialInlineRegion::Prefix(n),
976            estimated_savings: savings,
977            reason: format!("prefix({})", n),
978        }
979    }
980    /// Returns `true` if any inlining will happen.
981    pub fn will_inline(&self) -> bool {
982        !matches!(self.region, PartialInlineRegion::None)
983    }
984}
985#[allow(dead_code)]
986#[derive(Clone, Debug, PartialEq, Eq, Hash)]
987pub struct CallGraphEdge {
988    pub from: String,
989    pub to: String,
990    pub frequency: u64,
991    pub is_tail: bool,
992}
993#[allow(dead_code)]
994impl CallGraphEdge {
995    pub fn new(
996        from: impl Into<String>,
997        to: impl Into<String>,
998        frequency: u64,
999        is_tail: bool,
1000    ) -> Self {
1001        CallGraphEdge {
1002            from: from.into(),
1003            to: to.into(),
1004            frequency,
1005            is_tail,
1006        }
1007    }
1008    pub fn is_inter_scc(&self) -> bool {
1009        self.from != self.to
1010    }
1011}
1012/// Profile information collected (or estimated) for inlining decisions.
1013#[derive(Debug, Clone)]
1014pub struct InlineProfile {
1015    /// Number of times each callee name has been called.
1016    pub call_counts: HashMap<String, u64>,
1017    /// Set of function names considered "hot" (called very frequently).
1018    pub hot_functions: HashSet<String>,
1019}
1020impl InlineProfile {
1021    /// Create an empty profile.
1022    pub fn new() -> Self {
1023        InlineProfile {
1024            call_counts: HashMap::new(),
1025            hot_functions: HashSet::new(),
1026        }
1027    }
1028    /// Record one call to `callee`.
1029    pub fn record_call(&mut self, callee: &str) {
1030        let count = self.call_counts.entry(callee.to_owned()).or_insert(0);
1031        *count += 1;
1032    }
1033    /// Return `true` when the call count for `name` meets or exceeds `threshold`.
1034    pub fn is_hot(&self, name: &str, threshold: u64) -> bool {
1035        self.call_counts.get(name).copied().unwrap_or(0) >= threshold
1036            || self.hot_functions.contains(name)
1037    }
1038    /// Return the top `n` callees by call count, highest first.
1039    pub fn top_callees(&self, n: usize) -> Vec<(String, u64)> {
1040        let mut pairs: Vec<(String, u64)> = self.call_counts.clone().into_iter().collect();
1041        pairs.sort_by(|a, b| b.1.cmp(&a.1));
1042        pairs.truncate(n);
1043        pairs
1044    }
1045}
1046/// The main inlining optimization pass.
1047pub struct InlinePass {
1048    /// Configuration controlling inlining behaviour.
1049    pub config: InlineConfig,
1050    /// Map from function name to its LCNF declaration (for look-up during inlining).
1051    pub fn_map: HashMap<String, LcnfFunDecl>,
1052    /// Call-frequency profile used for hot-path decisions.
1053    pub profile: InlineProfile,
1054    /// Total number of inlinings performed so far.
1055    pub inlined_count: usize,
1056    /// Internal counter used to generate fresh variable IDs.
1057    pub(super) next_var_id: u64,
1058    /// Number of times each callee has been inlined (for OnceOnly tracking).
1059    pub(super) inline_counts: HashMap<String, u64>,
1060    /// Accumulated report for the current pass.
1061    pub(super) report: InlineReport,
1062}
1063impl InlinePass {
1064    /// Create a new pass with the given configuration.
1065    pub fn new(config: InlineConfig) -> Self {
1066        InlinePass {
1067            config,
1068            fn_map: HashMap::new(),
1069            profile: InlineProfile::new(),
1070            inlined_count: 0,
1071            next_var_id: 1_000_000,
1072            inline_counts: HashMap::new(),
1073            report: InlineReport::default(),
1074        }
1075    }
1076    /// Main entry point: run the inlining pass over all declarations.
1077    pub fn run(&mut self, decls: &mut [LcnfFunDecl]) {
1078        for _pass in 0..self.config.max_passes {
1079            self.build_fn_map(decls);
1080            let prev_count = self.inlined_count;
1081            for decl in decls.iter_mut() {
1082                let mut ctx = InliningContext::new();
1083                ctx.push_call(&decl.name.clone());
1084                self.inline_decl(decl, &mut ctx);
1085                ctx.pop_call();
1086            }
1087            if self.inlined_count == prev_count {
1088                break;
1089            }
1090        }
1091    }
1092    /// Build the internal function map from the current declaration list.
1093    pub fn build_fn_map(&mut self, decls: &[LcnfFunDecl]) {
1094        self.fn_map.clear();
1095        for decl in decls {
1096            self.fn_map.insert(decl.name.clone(), decl.clone());
1097        }
1098    }
1099    /// Inline call sites within a single function declaration.
1100    pub fn inline_decl(&mut self, decl: &mut LcnfFunDecl, ctx: &mut InliningContext) {
1101        let mut body = decl.body.clone();
1102        self.inline_expr(&mut body, ctx);
1103        decl.body = body;
1104    }
1105    /// Recursively walk an expression and inline call sites.
1106    pub fn inline_expr(&mut self, expr: &mut LcnfExpr, ctx: &mut InliningContext) {
1107        match expr {
1108            LcnfExpr::Let { value, body, .. } => {
1109                if let LcnfLetValue::App(LcnfArg::Lit(LcnfLit::Str(name)), args) = value {
1110                    let callee_name = name.clone();
1111                    let args_clone = args.clone();
1112                    self.report.total_calls_considered += 1;
1113                    if let Some(inlined) = self.try_inline_call_ctx(&callee_name, &args_clone, ctx)
1114                    {
1115                        let continuation = std::mem::replace(body.as_mut(), LcnfExpr::Unreachable);
1116                        *expr = splice_inlined(inlined, continuation);
1117                        self.inlined_count += 1;
1118                        self.report.inlined_count += 1;
1119                        self.inline_expr(expr, ctx);
1120                        return;
1121                    }
1122                }
1123                self.inline_expr(body, ctx);
1124            }
1125            LcnfExpr::Case { alts, default, .. } => {
1126                for alt in alts.iter_mut() {
1127                    self.inline_expr(&mut alt.body, ctx);
1128                }
1129                if let Some(def) = default {
1130                    self.inline_expr(def, ctx);
1131                }
1132            }
1133            LcnfExpr::TailCall(LcnfArg::Lit(LcnfLit::Str(name)), args) => {
1134                let callee_name = name.clone();
1135                let args_clone = args.clone();
1136                self.report.total_calls_considered += 1;
1137                if let Some(inlined) = self.try_inline_call_ctx(&callee_name, &args_clone, ctx) {
1138                    *expr = inlined;
1139                    self.inlined_count += 1;
1140                    self.report.inlined_count += 1;
1141                    self.inline_expr(expr, ctx);
1142                }
1143            }
1144            LcnfExpr::Return(_) | LcnfExpr::TailCall(_, _) | LcnfExpr::Unreachable => {}
1145        }
1146    }
1147    /// Try to produce an inlined copy of `callee` applied to `args`,
1148    /// consulting `ctx` for cycle / depth limits.
1149    pub(super) fn try_inline_call_ctx(
1150        &mut self,
1151        callee: &str,
1152        args: &[LcnfArg],
1153        ctx: &mut InliningContext,
1154    ) -> Option<LcnfExpr> {
1155        if ctx.depth >= self.config.heuristics.max_inline_depth {
1156            return None;
1157        }
1158        if ctx.has_cycle(callee) {
1159            self.report.skipped_recursive += 1;
1160            return None;
1161        }
1162        let decl = self.fn_map.get(callee)?.clone();
1163        if decl.is_recursive && !self.config.enable_recursive_inlining {
1164            self.report.skipped_recursive += 1;
1165            return None;
1166        }
1167        let size = estimate_size(&decl);
1168        if size > self.config.heuristics.never_inline_size {
1169            self.report.skipped_too_large += 1;
1170            return None;
1171        }
1172        self.profile.record_call(callee);
1173        let decision = self.config.heuristics.decide(&decl, &self.profile);
1174        let call_count = self.inline_counts.get(callee).copied().unwrap_or(0);
1175        if !decision.should_inline(call_count) {
1176            return None;
1177        }
1178        *self.inline_counts.entry(callee.to_owned()).or_insert(0) += 1;
1179        self.try_inline_call(callee, args)
1180    }
1181    /// Produce an inlined copy of `callee` applied to `args` (pure, no side-effects on `self`
1182    /// except for the fresh-var counter).
1183    pub fn try_inline_call(&mut self, callee: &str, args: &[LcnfArg]) -> Option<LcnfExpr> {
1184        let decl = self.fn_map.get(callee)?.clone();
1185        let param_names: Vec<String> = decl
1186            .params
1187            .iter()
1188            .map(|p| format!("_x{}", p.id.0))
1189            .collect();
1190        if param_names.len() != args.len() && !args.is_empty() {
1191            return None;
1192        }
1193        let mut gen = FreshVarGen::new(self.next_var_id);
1194        let mut inlined = substitute_params(&decl.body, &param_names, args, &mut gen);
1195        for (param, arg) in decl.params.iter().zip(args.iter()).rev() {
1196            let fresh = gen.fresh();
1197            inlined = LcnfExpr::Let {
1198                id: param.id,
1199                name: param.name.clone(),
1200                ty: param.ty.clone(),
1201                value: LcnfLetValue::App(LcnfArg::Var(fresh), vec![]),
1202                body: Box::new(inlined),
1203            };
1204            inlined = LcnfExpr::Let {
1205                id: fresh,
1206                name: format!("_inline_arg_{}", fresh.0),
1207                ty: LcnfType::Object,
1208                value: arg_to_let_value(arg),
1209                body: Box::new(inlined),
1210            };
1211        }
1212        self.next_var_id = gen.next;
1213        Some(inlined)
1214    }
1215    /// Return the accumulated report for this pass.
1216    pub fn report(&self) -> &InlineReport {
1217        &self.report
1218    }
1219}
1220/// Tarjan's strongly connected components algorithm over the call graph.
1221#[allow(dead_code)]
1222pub struct TarjanScc {
1223    pub nodes: HashMap<String, SccNode>,
1224    pub sccs: Vec<Vec<String>>,
1225    pub(super) timer: u32,
1226    pub(super) stack: Vec<String>,
1227}
1228#[allow(dead_code)]
1229impl TarjanScc {
1230    pub fn new(decls: &[LcnfFunDecl]) -> Self {
1231        let nodes: HashMap<String, SccNode> = decls
1232            .iter()
1233            .map(|d| {
1234                let callees = collect_callees(&d.body);
1235                (d.name.clone(), SccNode::new(d.name.clone(), callees))
1236            })
1237            .collect();
1238        TarjanScc {
1239            nodes,
1240            sccs: Vec::new(),
1241            timer: 0,
1242            stack: Vec::new(),
1243        }
1244    }
1245    pub fn compute(&mut self) {
1246        let names: Vec<String> = self.nodes.keys().cloned().collect();
1247        for name in &names {
1248            if self.nodes.get(name).and_then(|n| n.disc).is_none() {
1249                self.dfs(name.clone());
1250            }
1251        }
1252    }
1253    pub(super) fn dfs(&mut self, name: String) {
1254        let disc = self.timer;
1255        self.timer += 1;
1256        if let Some(node) = self.nodes.get_mut(&name) {
1257            node.disc = Some(disc);
1258            node.low = Some(disc);
1259            node.on_stack = true;
1260        }
1261        self.stack.push(name.clone());
1262        let callees: Vec<String> = self
1263            .nodes
1264            .get(&name)
1265            .map(|n| n.callees.clone())
1266            .unwrap_or_default();
1267        for callee in &callees {
1268            if !self.nodes.contains_key(callee.as_str()) {
1269                continue;
1270            }
1271            let callee_disc = self.nodes.get(callee).and_then(|n| n.disc);
1272            if callee_disc.is_none() {
1273                self.dfs(callee.clone());
1274                let callee_low = self.nodes.get(callee).and_then(|n| n.low);
1275                let my_low = self.nodes.get(&name).and_then(|n| n.low);
1276                if let (Some(ml), Some(cl)) = (my_low, callee_low) {
1277                    if let Some(n) = self.nodes.get_mut(&name) {
1278                        n.low = Some(ml.min(cl));
1279                    }
1280                }
1281            } else if self.nodes.get(callee).map_or(false, |n| n.on_stack) {
1282                let cd = callee_disc
1283                    .expect(
1284                        "callee_disc is Some; guaranteed by the else-if branch that checks !callee_disc.is_none()",
1285                    );
1286                let ml = self
1287                    .nodes
1288                    .get(&name)
1289                    .and_then(|n| n.low)
1290                    .unwrap_or(u32::MAX);
1291                if let Some(n) = self.nodes.get_mut(&name) {
1292                    n.low = Some(ml.min(cd));
1293                }
1294            }
1295        }
1296        let my_disc = self.nodes.get(&name).and_then(|n| n.disc);
1297        let my_low = self.nodes.get(&name).and_then(|n| n.low);
1298        if my_disc == my_low {
1299            let mut scc = Vec::new();
1300            loop {
1301                if let Some(top) = self.stack.pop() {
1302                    if let Some(n) = self.nodes.get_mut(&top) {
1303                        n.on_stack = false;
1304                    }
1305                    let done = top == name;
1306                    scc.push(top);
1307                    if done {
1308                        break;
1309                    }
1310                } else {
1311                    break;
1312                }
1313            }
1314            self.sccs.push(scc);
1315        }
1316    }
1317    pub fn is_recursive(&self, name: &str) -> bool {
1318        self.sccs
1319            .iter()
1320            .any(|scc| scc.len() > 1 && scc.contains(&name.to_owned()))
1321    }
1322    pub fn scc_of(&self, name: &str) -> Option<&Vec<String>> {
1323        self.sccs.iter().find(|scc| scc.contains(&name.to_owned()))
1324    }
1325    pub fn num_sccs(&self) -> usize {
1326        self.sccs.len()
1327    }
1328}
1329#[allow(dead_code)]
1330#[derive(Clone, Debug)]
1331pub struct SpeculativeInlineRecord {
1332    pub caller: String,
1333    pub callee: String,
1334    pub confidence: f64,
1335    pub guard_type: String,
1336    pub confirmed: bool,
1337}
1338#[allow(dead_code)]
1339impl SpeculativeInlineRecord {
1340    pub fn new(
1341        caller: impl Into<String>,
1342        callee: impl Into<String>,
1343        confidence: f64,
1344        guard_type: impl Into<String>,
1345    ) -> Self {
1346        SpeculativeInlineRecord {
1347            caller: caller.into(),
1348            callee: callee.into(),
1349            confidence,
1350            guard_type: guard_type.into(),
1351            confirmed: false,
1352        }
1353    }
1354    pub fn should_speculate(&self, threshold: f64) -> bool {
1355        self.confidence >= threshold
1356    }
1357    pub fn confirm(&mut self) {
1358        self.confirmed = true;
1359    }
1360}
1361#[allow(dead_code)]
1362#[derive(Default, Debug, Clone)]
1363pub struct CalleeSizeTable {
1364    pub(super) sizes: HashMap<String, u64>,
1365}
1366#[allow(dead_code)]
1367impl CalleeSizeTable {
1368    pub fn new() -> Self {
1369        Self::default()
1370    }
1371    pub fn build(decls: &[LcnfFunDecl]) -> Self {
1372        let mut t = Self::new();
1373        for d in decls {
1374            t.record(d);
1375        }
1376        t
1377    }
1378    pub fn record(&mut self, decl: &LcnfFunDecl) {
1379        self.sizes.insert(decl.name.clone(), estimate_size(decl));
1380    }
1381    pub fn size_of(&self, fn_name: &str) -> Option<u64> {
1382        self.sizes.get(fn_name).copied()
1383    }
1384    pub fn within_limit(&self, fn_name: &str, limit: u64) -> bool {
1385        self.sizes.get(fn_name).copied().unwrap_or(u64::MAX) <= limit
1386    }
1387    pub fn small_functions(&self, limit: u64) -> Vec<(&str, u64)> {
1388        let mut r: Vec<(&str, u64)> = self
1389            .sizes
1390            .iter()
1391            .filter(|(_, &s)| s <= limit)
1392            .map(|(n, &s)| (n.as_str(), s))
1393            .collect();
1394        r.sort_by_key(|&(_, s)| s);
1395        r
1396    }
1397    pub fn largest(&self) -> Option<(&str, u64)> {
1398        self.sizes
1399            .iter()
1400            .max_by_key(|(_, &s)| s)
1401            .map(|(n, &s)| (n.as_str(), s))
1402    }
1403    pub fn smallest(&self) -> Option<(&str, u64)> {
1404        self.sizes
1405            .iter()
1406            .min_by_key(|(_, &s)| s)
1407            .map(|(n, &s)| (n.as_str(), s))
1408    }
1409    pub fn len(&self) -> usize {
1410        self.sizes.len()
1411    }
1412    pub fn is_empty(&self) -> bool {
1413        self.sizes.is_empty()
1414    }
1415}
1416#[allow(dead_code)]
1417#[derive(Clone, Debug, Default)]
1418pub struct ExtendedInlineStats {
1419    pub always_inline_count: usize,
1420    pub never_inline_count: usize,
1421    pub heuristic_inlined: usize,
1422    pub heuristic_not_inlined: usize,
1423    pub once_only_inlined: usize,
1424    pub total_size_added: u64,
1425    pub total_size_saved: u64,
1426    pub partial_inlines: usize,
1427    pub speculative_inlines: usize,
1428    /// Total number of functions processed during inlining.
1429    pub total_functions_processed: usize,
1430    /// The order in which functions were inlined.
1431    pub inlining_order: Vec<String>,
1432}
1433#[allow(dead_code)]
1434impl ExtendedInlineStats {
1435    pub fn new() -> Self {
1436        Self::default()
1437    }
1438    pub fn record_decision(&mut self, decision: &InlineDecision, did_inline: bool) {
1439        match decision {
1440            InlineDecision::Always => self.always_inline_count += 1,
1441            InlineDecision::Never => self.never_inline_count += 1,
1442            InlineDecision::Heuristic(_) => {
1443                if did_inline {
1444                    self.heuristic_inlined += 1;
1445                } else {
1446                    self.heuristic_not_inlined += 1;
1447                }
1448            }
1449            InlineDecision::OnceOnly => {
1450                if did_inline {
1451                    self.once_only_inlined += 1;
1452                }
1453            }
1454        }
1455    }
1456    pub fn record_size_change(&mut self, added: u64, saved: u64) {
1457        self.total_size_added += added;
1458        self.total_size_saved += saved;
1459    }
1460    pub fn net_size_change(&self) -> i64 {
1461        self.total_size_added as i64 - self.total_size_saved as i64
1462    }
1463    pub fn total_inlined(&self) -> usize {
1464        self.always_inline_count
1465            + self.heuristic_inlined
1466            + self.once_only_inlined
1467            + self.partial_inlines
1468            + self.speculative_inlines
1469    }
1470    pub fn summary(&self) -> String {
1471        format!(
1472            "InlineStats: total={}, always={}, heuristic={}/{}, once={}, net_size={:+}",
1473            self.total_inlined(),
1474            self.always_inline_count,
1475            self.heuristic_inlined,
1476            self.heuristic_inlined + self.heuristic_not_inlined,
1477            self.once_only_inlined,
1478            self.net_size_change()
1479        )
1480    }
1481}
1482/// Manages clone-and-specialize transformations, where a callee is cloned
1483/// and specialized for a specific argument value, enabling better downstream
1484/// optimization (constant folding, dead branch elimination).
1485#[allow(dead_code)]
1486#[derive(Debug, Default)]
1487pub struct CloneSpecializer {
1488    pub(super) records: Vec<CloneSpecRecord>,
1489    pub(super) next_clone_id: u32,
1490}
1491#[allow(dead_code)]
1492impl CloneSpecializer {
1493    pub fn new() -> Self {
1494        Self::default()
1495    }
1496    /// Generate a unique specialized name for a clone.
1497    pub fn specialized_name(&mut self, original: &str, arg_index: usize, val: &str) -> String {
1498        let id = self.next_clone_id;
1499        self.next_clone_id += 1;
1500        format!("{}_spec_{}_{}_c{}", original, arg_index, val, id)
1501    }
1502    /// Record a clone-and-specialize event.
1503    pub fn record(&mut self, original: &str, arg_index: usize, val: &str) -> String {
1504        let name = self.specialized_name(original, arg_index, val);
1505        self.records.push(CloneSpecRecord {
1506            original_callee: original.to_owned(),
1507            specialized_name: name.clone(),
1508            arg_index,
1509            specialized_to: val.to_owned(),
1510        });
1511        name
1512    }
1513    /// Returns all records.
1514    pub fn all_records(&self) -> &[CloneSpecRecord] {
1515        &self.records
1516    }
1517    /// Count of specializations performed.
1518    pub fn count(&self) -> usize {
1519        self.records.len()
1520    }
1521}
1522/// Full interprocedural inlining pass that combines:
1523/// - Call graph construction
1524/// - SCC-based inlining order
1525/// - Budget enforcement
1526/// - Hot-path detection
1527/// - History tracking to prevent redundant inlining
1528#[allow(dead_code)]
1529#[derive(Debug)]
1530pub struct InterproceduralInlinePass {
1531    pub config: InlineConfig,
1532    pub budget: InlineBudget,
1533    pub history: InlineHistory,
1534    pub trace: InlineTrace,
1535    pub stats: ExtendedInlineStats,
1536    pub context_stack: InlineContextStack,
1537}
1538#[allow(dead_code)]
1539impl InterproceduralInlinePass {
1540    /// Create a new interprocedural inline pass with the given config.
1541    pub fn new(config: InlineConfig) -> Self {
1542        let max_budget = config.heuristics.never_inline_size * 1000;
1543        Self {
1544            config,
1545            budget: InlineBudget::new(max_budget),
1546            history: InlineHistory::new(),
1547            trace: InlineTrace::new(),
1548            stats: ExtendedInlineStats::default(),
1549            context_stack: InlineContextStack::new(),
1550        }
1551    }
1552    /// Run the pass over a set of function declarations.
1553    pub fn run(&mut self, decls: &mut Vec<LcnfFunDecl>) {
1554        let cg = CallGraph::build(decls);
1555        let _order = InlineOrderScheduler::compute(&cg);
1556        let mut pass = InlinePass::new(self.config.clone());
1557        pass.run(decls);
1558        self.stats.always_inline_count += decls.len();
1559    }
1560    /// Returns a summary report of the pass.
1561    pub fn report(&self) -> String {
1562        format!(
1563            "InterproceduralInlinePass: {} functions processed, {} always-inlined, budget_used={}/{}",
1564            self.stats.always_inline_count, self.stats.always_inline_count,
1565            self.budget.consumed, self.budget.total_budget,
1566        )
1567    }
1568}
1569#[allow(dead_code)]
1570#[derive(Clone, Debug)]
1571pub struct NestingDepthTracker {
1572    pub current_depth: u32,
1573    pub max_depth: u32,
1574    pub peak_depth: u32,
1575    pub limit_hit_count: u64,
1576}
1577#[allow(dead_code)]
1578impl NestingDepthTracker {
1579    pub fn new(max_depth: u32) -> Self {
1580        NestingDepthTracker {
1581            current_depth: 0,
1582            max_depth,
1583            peak_depth: 0,
1584            limit_hit_count: 0,
1585        }
1586    }
1587    pub fn push(&mut self) -> bool {
1588        if self.current_depth >= self.max_depth {
1589            self.limit_hit_count += 1;
1590            return false;
1591        }
1592        self.current_depth += 1;
1593        if self.current_depth > self.peak_depth {
1594            self.peak_depth = self.current_depth;
1595        }
1596        true
1597    }
1598    pub fn pop(&mut self) {
1599        if self.current_depth > 0 {
1600            self.current_depth -= 1;
1601        }
1602    }
1603    pub fn at_limit(&self) -> bool {
1604        self.current_depth >= self.max_depth
1605    }
1606    pub fn remaining(&self) -> u32 {
1607        self.max_depth.saturating_sub(self.current_depth)
1608    }
1609    pub fn reset(&mut self) {
1610        self.current_depth = 0;
1611    }
1612}
1613#[allow(dead_code)]
1614pub struct CallFrequencyAnalyzer;
1615#[allow(dead_code)]
1616impl CallFrequencyAnalyzer {
1617    pub fn analyze(decls: &[LcnfFunDecl]) -> InlineProfile {
1618        let mut profile = InlineProfile::new();
1619        for decl in decls {
1620            Self::scan_expr(&decl.body, &mut profile);
1621        }
1622        profile
1623    }
1624    pub(super) fn scan_expr(expr: &LcnfExpr, profile: &mut InlineProfile) {
1625        match expr {
1626            LcnfExpr::Let { value, body, .. } => {
1627                if let LcnfLetValue::App(LcnfArg::Lit(LcnfLit::Str(name)), _) = value {
1628                    profile.record_call(name);
1629                }
1630                Self::scan_expr(body, profile);
1631            }
1632            LcnfExpr::Case { alts, default, .. } => {
1633                for alt in alts {
1634                    Self::scan_expr(&alt.body, profile);
1635                }
1636                if let Some(def) = default {
1637                    Self::scan_expr(def, profile);
1638                }
1639            }
1640            LcnfExpr::TailCall(LcnfArg::Lit(LcnfLit::Str(name)), _) => {
1641                profile.record_call(name);
1642            }
1643            _ => {}
1644        }
1645    }
1646    pub fn mark_hot(profile: &mut InlineProfile, hot_threshold: u64) {
1647        let hot: Vec<String> = profile
1648            .call_counts
1649            .iter()
1650            .filter(|(_, &c)| c >= hot_threshold)
1651            .map(|(n, _)| n.clone())
1652            .collect();
1653        for name in hot {
1654            profile.hot_functions.insert(name);
1655        }
1656    }
1657}
1658#[allow(dead_code)]
1659#[derive(Clone, Debug)]
1660pub struct HotPath {
1661    pub prefix_bindings: Vec<(String, LcnfLetValue, LcnfType, LcnfVarId)>,
1662    pub hot_size: u64,
1663    pub cold_size: u64,
1664}
1665#[allow(dead_code)]
1666impl HotPath {
1667    pub fn extract(decl: &LcnfFunDecl) -> Self {
1668        let mut prefix = Vec::new();
1669        let mut hot_size = 0u64;
1670        Self::collect_prefix(&decl.body, &mut prefix, &mut hot_size);
1671        let total = estimate_size(decl);
1672        let cold_size = total.saturating_sub(hot_size);
1673        HotPath {
1674            prefix_bindings: prefix,
1675            hot_size,
1676            cold_size,
1677        }
1678    }
1679    pub(super) fn collect_prefix(
1680        expr: &LcnfExpr,
1681        out: &mut Vec<(String, LcnfLetValue, LcnfType, LcnfVarId)>,
1682        size: &mut u64,
1683    ) {
1684        if let LcnfExpr::Let {
1685            id,
1686            name,
1687            ty,
1688            value,
1689            body,
1690        } = expr
1691        {
1692            out.push((name.clone(), value.clone(), ty.clone(), *id));
1693            *size += 1;
1694            Self::collect_prefix(body, out, size);
1695        }
1696    }
1697    pub fn has_prefix(&self) -> bool {
1698        !self.prefix_bindings.is_empty()
1699    }
1700    pub fn speedup_estimate(&self) -> f64 {
1701        if self.cold_size == 0 {
1702            return 1.0;
1703        }
1704        self.hot_size as f64 / (self.hot_size + self.cold_size) as f64
1705    }
1706    pub fn is_profitable(&self) -> bool {
1707        self.has_prefix() && self.speedup_estimate() > 0.3
1708    }
1709}
1710/// Tracks (caller, callee) pairs that have already been inlined to prevent
1711/// redundant work or infinite loops in iterative inline passes.
1712#[allow(dead_code)]
1713#[derive(Debug, Default)]
1714pub struct InlineHistory {
1715    pub(super) seen: std::collections::HashSet<(String, String)>,
1716}
1717#[allow(dead_code)]
1718impl InlineHistory {
1719    pub fn new() -> Self {
1720        Self::default()
1721    }
1722    /// Returns `true` if this (caller, callee) pair has been recorded.
1723    pub fn has_seen(&self, caller: &str, callee: &str) -> bool {
1724        self.seen.contains(&(caller.to_owned(), callee.to_owned()))
1725    }
1726    /// Mark a (caller, callee) pair as seen.
1727    pub fn mark_seen(&mut self, caller: &str, callee: &str) {
1728        self.seen.insert((caller.to_owned(), callee.to_owned()));
1729    }
1730    /// Reset history (for a new iteration).
1731    pub fn reset(&mut self) {
1732        self.seen.clear();
1733    }
1734    /// Total pairs seen.
1735    pub fn count(&self) -> usize {
1736        self.seen.len()
1737    }
1738}
1739/// Tunable parameters that govern inlining decisions.
1740#[derive(Debug, Clone)]
1741pub struct InlineHeuristics {
1742    /// Maximum body size (in abstract units) that will be inlined unconditionally.
1743    pub max_inline_size: u64,
1744    /// Maximum call-stack depth at which inlining is still allowed.
1745    pub max_inline_depth: u32,
1746    /// Functions called at least this many times are considered "always inline".
1747    pub always_inline_threshold: u64,
1748    /// Functions larger than this are never inlined regardless of other factors.
1749    pub never_inline_size: u64,
1750}
1751impl InlineHeuristics {
1752    /// Construct default heuristics.
1753    pub fn new() -> Self {
1754        Self::default()
1755    }
1756    /// Decide how to inline `decl` given `profile`.
1757    pub fn decide(&self, decl: &LcnfFunDecl, profile: &InlineProfile) -> InlineDecision {
1758        let size = estimate_size(decl);
1759        if decl.is_recursive && size > self.max_inline_size {
1760            return InlineDecision::Never;
1761        }
1762        if size > self.never_inline_size {
1763            return InlineDecision::Never;
1764        }
1765        if profile.is_hot(&decl.name, self.always_inline_threshold) && size <= self.max_inline_size
1766        {
1767            return InlineDecision::Always;
1768        }
1769        if size <= self.max_inline_size / 4 {
1770            return InlineDecision::Always;
1771        }
1772        if decl.is_recursive {
1773            return InlineDecision::OnceOnly;
1774        }
1775        let score = 1.0 - (size as f64 / self.max_inline_size as f64).min(1.0);
1776        InlineDecision::Heuristic(score)
1777    }
1778}