Skip to main content

oxilean_runtime/tco/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::trampoline;
6use std::collections::{HashMap, HashSet};
7
8/// A labeled trampoline step: tag identifies which "function" to run next.
9#[allow(dead_code)]
10pub enum MultiStep<State, Output> {
11    /// Run the "even" branch.
12    Even(State),
13    /// Run the "odd" branch.
14    Odd(State),
15    /// Done with output.
16    Done(Output),
17}
18/// Detects potential infinite loops by tracking state hashes.
19#[allow(dead_code)]
20pub struct LoopDetector {
21    /// Set of observed state fingerprints.
22    seen: std::collections::HashSet<u64>,
23    /// Number of states checked.
24    pub checked: u64,
25}
26impl LoopDetector {
27    /// Create a new detector.
28    #[allow(dead_code)]
29    pub fn new() -> Self {
30        Self {
31            seen: std::collections::HashSet::new(),
32            checked: 0,
33        }
34    }
35    /// Record a state with the given fingerprint.
36    /// Returns `true` if this state was seen before (loop detected).
37    #[allow(dead_code)]
38    pub fn check(&mut self, fingerprint: u64) -> bool {
39        self.checked += 1;
40        !self.seen.insert(fingerprint)
41    }
42    /// Clear history.
43    #[allow(dead_code)]
44    pub fn reset(&mut self) {
45        self.seen.clear();
46        self.checked = 0;
47    }
48    /// Number of unique states seen.
49    #[allow(dead_code)]
50    pub fn unique_states(&self) -> usize {
51        self.seen.len()
52    }
53}
54/// Aggregated TCO metrics across many functions.
55#[allow(dead_code)]
56#[derive(Default)]
57pub struct TrampolineMetricsRegistry {
58    /// Map from function name to metrics.
59    pub functions: std::collections::HashMap<String, FunctionTcoMetrics>,
60}
61impl TrampolineMetricsRegistry {
62    /// Create an empty registry.
63    #[allow(dead_code)]
64    pub fn new() -> Self {
65        Self::default()
66    }
67    /// Record a TCO call for `function` at `depth`.
68    #[allow(dead_code)]
69    pub fn record(&mut self, function: &str, depth: u64) {
70        self.functions
71            .entry(function.to_string())
72            .or_insert_with(|| FunctionTcoMetrics::new(function))
73            .record(depth);
74    }
75    /// Return the top `n` functions by total steps.
76    #[allow(dead_code)]
77    pub fn top_by_steps(&self, n: usize) -> Vec<&FunctionTcoMetrics> {
78        let mut sorted: Vec<&FunctionTcoMetrics> = self.functions.values().collect();
79        sorted.sort_by_key(|b| std::cmp::Reverse(b.total_steps));
80        sorted.truncate(n);
81        sorted
82    }
83    /// Total calls across all functions.
84    #[allow(dead_code)]
85    pub fn total_calls(&self) -> u64 {
86        self.functions.values().map(|m| m.call_count).sum()
87    }
88}
89/// A trampoline driver with a configurable step limit.
90#[allow(dead_code)]
91pub struct BoundedTrampoline {
92    /// The maximum number of steps allowed.
93    pub step_limit: u64,
94}
95impl BoundedTrampoline {
96    /// Create a bounded trampoline with the given step limit.
97    #[allow(dead_code)]
98    pub fn new(step_limit: u64) -> Self {
99        Self { step_limit }
100    }
101    /// Run `step` to completion, returning `Ok(value)` or an error if the limit
102    /// was exceeded.
103    #[allow(dead_code)]
104    pub fn run<T>(&self, mut step: TailCall<T>) -> Result<T, String> {
105        let mut count = 0u64;
106        loop {
107            match step {
108                TailCall::Done(v) => return Ok(v),
109                TailCall::Call(f) => {
110                    count += 1;
111                    if count > self.step_limit {
112                        return Err(format!(
113                            "trampoline step limit {} exceeded at step {}",
114                            self.step_limit, count
115                        ));
116                    }
117                    step = f();
118                }
119            }
120        }
121    }
122}
123/// A scheduler that drives a `TailCall<T>` in bounded batches.
124#[allow(dead_code)]
125pub struct TailCallScheduler<T> {
126    /// Current pending step (None if already finished or not started).
127    pending: Option<TailCall<T>>,
128    /// Configuration for this scheduler.
129    pub config: TailCallSchedulerConfig,
130    /// Total steps taken so far.
131    pub total_steps: u64,
132}
133impl<T> TailCallScheduler<T> {
134    /// Create a new scheduler with the given step and the default config.
135    #[allow(dead_code)]
136    pub fn new(step: TailCall<T>) -> Self {
137        Self {
138            pending: Some(step),
139            config: TailCallSchedulerConfig::default(),
140            total_steps: 0,
141        }
142    }
143    /// Create a scheduler with a custom config.
144    #[allow(dead_code)]
145    pub fn with_config(step: TailCall<T>, config: TailCallSchedulerConfig) -> Self {
146        Self {
147            pending: Some(step),
148            config,
149            total_steps: 0,
150        }
151    }
152    /// Run up to `max_steps_per_batch` steps.
153    #[allow(dead_code)]
154    pub fn tick(&mut self) -> SchedulerTickResult<T> {
155        let batch = self.config.max_steps_per_batch;
156        for _ in 0..batch {
157            match self.pending.take() {
158                None => return SchedulerTickResult::Pending,
159                Some(TailCall::Done(v)) => return SchedulerTickResult::Finished(v),
160                Some(TailCall::Call(f)) => {
161                    self.total_steps += 1;
162                    if self.total_steps > self.config.step_limit {
163                        return SchedulerTickResult::StepLimitExceeded;
164                    }
165                    self.pending = Some(f());
166                }
167            }
168        }
169        SchedulerTickResult::Pending
170    }
171    /// Run to completion (ignoring the batch limit).
172    #[allow(dead_code)]
173    pub fn run_to_completion(mut self) -> Result<T, String> {
174        loop {
175            match self.tick() {
176                SchedulerTickResult::Finished(v) => return Ok(v),
177                SchedulerTickResult::StepLimitExceeded => {
178                    return Err(format!("step limit {} exceeded", self.config.step_limit));
179                }
180                SchedulerTickResult::Pending => {}
181            }
182        }
183    }
184}
185/// Configuration for loop unrolling.
186#[allow(dead_code)]
187#[derive(Clone, Debug)]
188pub struct UnrollConfig {
189    /// Factor by which to unroll the loop (e.g., 4 = 4 copies of the body).
190    pub factor: usize,
191    /// Maximum loop count to consider for full unrolling.
192    pub full_unroll_limit: usize,
193}
194/// Result of loop unrolling analysis.
195#[allow(dead_code)]
196#[derive(Clone, Debug)]
197pub struct UnrollResult {
198    /// Whether the loop was fully unrolled.
199    pub fully_unrolled: bool,
200    /// Factor used.
201    pub factor: usize,
202    /// Estimated savings (iterations avoided).
203    pub iterations_saved: usize,
204}
205impl UnrollResult {
206    /// Compute unroll result for a loop with `n` iterations.
207    #[allow(dead_code)]
208    pub fn compute(n: usize, cfg: &UnrollConfig) -> Self {
209        if n <= cfg.full_unroll_limit {
210            UnrollResult {
211                fully_unrolled: true,
212                factor: n,
213                iterations_saved: n,
214            }
215        } else {
216            let factor = cfg.factor.min(n);
217            let remainder = n % factor;
218            let main_iters = n / factor;
219            let saved = n - main_iters - remainder;
220            UnrollResult {
221                fully_unrolled: false,
222                factor,
223                iterations_saved: saved,
224            }
225        }
226    }
227}
228/// A simple evaluation context that maps names to values.
229#[allow(dead_code)]
230#[derive(Clone, Debug, Default)]
231pub struct EvaluationContext {
232    /// Variable bindings.
233    pub bindings: std::collections::HashMap<String, u64>,
234    /// Stack depth.
235    pub depth: u32,
236}
237impl EvaluationContext {
238    /// Create an empty context.
239    #[allow(dead_code)]
240    pub fn new() -> Self {
241        Self::default()
242    }
243    /// Bind a variable.
244    #[allow(dead_code)]
245    pub fn bind(&mut self, name: &str, value: u64) {
246        self.bindings.insert(name.to_string(), value);
247    }
248    /// Look up a variable.
249    #[allow(dead_code)]
250    pub fn lookup(&self, name: &str) -> Option<u64> {
251        self.bindings.get(name).copied()
252    }
253    /// Return a child context (incremented depth).
254    #[allow(dead_code)]
255    pub fn child(&self) -> Self {
256        Self {
257            bindings: self.bindings.clone(),
258            depth: self.depth + 1,
259        }
260    }
261    /// Number of bindings.
262    #[allow(dead_code)]
263    pub fn size(&self) -> usize {
264        self.bindings.len()
265    }
266}
267/// A simple peephole optimizer that applies rules to an opcode list.
268#[allow(dead_code)]
269pub struct PeepholeOptimizer {
270    rules: Vec<PeepholeRule>,
271    /// Number of rewrites applied in the last `optimize` call.
272    pub rewrites: usize,
273}
274impl PeepholeOptimizer {
275    /// Create an optimizer with no rules.
276    #[allow(dead_code)]
277    pub fn new() -> Self {
278        Self {
279            rules: Vec::new(),
280            rewrites: 0,
281        }
282    }
283    /// Add a rule.
284    #[allow(dead_code)]
285    pub fn add_rule(&mut self, rule: PeepholeRule) {
286        self.rules.push(rule);
287    }
288    /// Apply all rules to `opcodes`, returning the optimized sequence.
289    #[allow(dead_code)]
290    pub fn optimize<'a>(&mut self, opcodes: &[&'a str]) -> Vec<&'a str> {
291        let mut result: Vec<&'a str> = opcodes.to_vec();
292        let mut changed = true;
293        self.rewrites = 0;
294        while changed {
295            changed = false;
296            'outer: for rule in &self.rules {
297                let plen = rule.pattern.len();
298                if plen == 0 {
299                    continue;
300                }
301                let mut i = 0;
302                while i + plen <= result.len() {
303                    if result[i..i + plen] == rule.pattern[..] {
304                        let mut new_result = result[..i].to_vec();
305                        new_result.extend_from_slice(&rule.replacement);
306                        new_result.extend_from_slice(&result[i + plen..]);
307                        result = new_result;
308                        self.rewrites += 1;
309                        changed = true;
310                        continue 'outer;
311                    }
312                    i += 1;
313                }
314            }
315        }
316        result
317    }
318}
319/// A simple heuristic for identifying tail-position `Call` instructions
320/// in a flat bytecode sequence.
321///
322/// The rule is: a `Call` instruction is in tail position if it is
323/// immediately followed by a `Return` instruction (with no intervening
324/// instructions that produce stack values consumed after the return).
325pub struct TailCallDetector {
326    /// Positions (0-based) of instructions that are in tail position.
327    pub tail_positions: Vec<usize>,
328}
329impl TailCallDetector {
330    /// Create a new detector (no positions identified yet).
331    pub fn new() -> Self {
332        TailCallDetector {
333            tail_positions: Vec::new(),
334        }
335    }
336    /// Analyse a sequence of opcode names (strings) and populate
337    /// [`tail_positions`](Self::tail_positions).
338    ///
339    /// The opcode names are strings like `"Call"`, `"Return"`, `"Halt"`,
340    /// matching what [`crate::bytecode_interp::Opcode`] would produce via
341    /// `Debug`. This keeps the detector decoupled from the specific
342    /// `Opcode` enum variant structure.
343    pub fn analyse(&mut self, opcode_names: &[&str]) {
344        self.tail_positions.clear();
345        for (i, name) in opcode_names.iter().enumerate() {
346            if *name == "Call" {
347                let next = opcode_names.get(i + 1).copied().unwrap_or("");
348                if next == "Return" || next == "Halt" {
349                    self.tail_positions.push(i);
350                }
351            }
352        }
353    }
354    /// Returns `true` if position `idx` was identified as a tail call.
355    pub fn is_tail(&self, idx: usize) -> bool {
356        self.tail_positions.contains(&idx)
357    }
358    /// Number of tail-call positions found.
359    pub fn count(&self) -> usize {
360        self.tail_positions.len()
361    }
362}
363/// Aggregate statistics for a batch of TCO-enabled computations.
364#[allow(dead_code)]
365#[derive(Clone, Debug, Default)]
366pub struct TcoStatistics {
367    /// Total number of computations run.
368    pub total_runs: u64,
369    /// Total trampoline steps across all runs.
370    pub total_steps: u64,
371    /// Maximum depth seen in any single run.
372    pub global_max_depth: u64,
373    /// Number of runs that hit the step limit.
374    pub step_limit_hits: u64,
375}
376impl TcoStatistics {
377    /// Create zeroed statistics.
378    #[allow(dead_code)]
379    pub fn new() -> Self {
380        Self::default()
381    }
382    /// Record the result of a single run.
383    #[allow(dead_code)]
384    pub fn record_run(&mut self, counter: &TailCallCounter, hit_limit: bool) {
385        self.total_runs += 1;
386        self.total_steps += counter.optimized;
387        if counter.max_depth > self.global_max_depth {
388            self.global_max_depth = counter.max_depth;
389        }
390        if hit_limit {
391            self.step_limit_hits += 1;
392        }
393    }
394    /// Average steps per run.
395    #[allow(dead_code)]
396    pub fn avg_steps(&self) -> f64 {
397        if self.total_runs == 0 {
398            0.0
399        } else {
400            self.total_steps as f64 / self.total_runs as f64
401        }
402    }
403    /// Format as a summary string.
404    #[allow(dead_code)]
405    pub fn summary(&self) -> String {
406        format!(
407            "TcoStats: runs={}, total_steps={}, max_depth={}, limit_hits={}, avg_steps={:.2}",
408            self.total_runs,
409            self.total_steps,
410            self.global_max_depth,
411            self.step_limit_hits,
412            self.avg_steps()
413        )
414    }
415}
416/// A simulated stack frame for explicit-stack recursion.
417#[allow(dead_code)]
418#[derive(Clone, Debug)]
419pub struct StackFrame {
420    /// The function name at this frame.
421    pub function: String,
422    /// Local variable bindings as `(name, value)` pairs.
423    pub locals: Vec<(String, u64)>,
424    /// The return address (index into a bytecode chunk).
425    pub return_address: usize,
426}
427impl StackFrame {
428    /// Create a new stack frame.
429    #[allow(dead_code)]
430    pub fn new(function: &str, return_address: usize) -> Self {
431        Self {
432            function: function.to_string(),
433            locals: Vec::new(),
434            return_address,
435        }
436    }
437    /// Add a local variable binding.
438    #[allow(dead_code)]
439    pub fn bind(&mut self, name: &str, value: u64) {
440        self.locals.push((name.to_string(), value));
441    }
442    /// Look up a local variable by name.
443    #[allow(dead_code)]
444    pub fn lookup(&self, name: &str) -> Option<u64> {
445        self.locals
446            .iter()
447            .rev()
448            .find(|(n, _)| n == name)
449            .map(|(_, v)| *v)
450    }
451}
452/// Threshold configuration for inlining decisions.
453#[allow(dead_code)]
454#[derive(Clone, Debug)]
455pub struct InliningThreshold {
456    /// Maximum size (in opcodes) of a function that may be inlined.
457    pub max_size: usize,
458    /// Maximum call depth after which inlining is suppressed.
459    pub max_depth: usize,
460    /// Minimum call count before inlining is triggered.
461    pub min_call_count: u64,
462}
463/// A builder that converts a two-argument accumulator-style recursion
464/// into a trampolined computation.
465///
466/// ```
467/// # use oxilean_runtime::tco::{RecursiveStep, trampoline};
468/// // factorial via recursive step
469/// let result = RecursiveStep::run(10u64, 1u64, |n, acc| {
470///     if n == 0 { None } else { Some((n - 1, n * acc)) }
471/// });
472/// assert_eq!(result, 3628800);
473/// ```
474pub struct RecursiveStep;
475impl RecursiveStep {
476    /// Run the accumulator loop.
477    ///
478    /// - `initial`: the initial input value.
479    /// - `acc`: the initial accumulator.
480    /// - `step`: given `(input, acc)`, returns `Some((next_input, next_acc))`
481    ///   to continue, or `None` to stop and return `acc`.
482    pub fn run<I, A, F>(initial: I, acc: A, step: F) -> A
483    where
484        I: Clone + 'static,
485        A: Clone + 'static,
486        F: Fn(I, A) -> Option<(I, A)> + Clone + 'static,
487    {
488        fn go<
489            I: Clone + 'static,
490            A: Clone + 'static,
491            F: Fn(I, A) -> Option<(I, A)> + Clone + 'static,
492        >(
493            i: I,
494            a: A,
495            f: F,
496        ) -> TailCall<A> {
497            match f(i, a.clone()) {
498                None => TailCall::Done(a),
499                Some((next_i, next_a)) => {
500                    let f2 = f.clone();
501                    TailCall::Call(Box::new(move || go(next_i, next_a, f2)))
502                }
503            }
504        }
505        trampoline(go(initial, acc, step))
506    }
507}
508/// Kind of binary operation.
509#[allow(dead_code)]
510#[derive(Clone, Copy, Debug, PartialEq, Eq)]
511pub enum BinopKind {
512    Add,
513    Sub,
514    Mul,
515    Div,
516}
517impl BinopKind {
518    /// Evaluate this binary operation.
519    #[allow(dead_code)]
520    pub fn eval(self, lhs: u64, rhs: u64) -> Option<u64> {
521        match self {
522            BinopKind::Add => Some(lhs.wrapping_add(rhs)),
523            BinopKind::Sub => Some(lhs.wrapping_sub(rhs)),
524            BinopKind::Mul => Some(lhs.wrapping_mul(rhs)),
525            BinopKind::Div => lhs.checked_div(rhs),
526        }
527    }
528}
529/// The result of a step in a trampoline loop.
530///
531/// A function that supports TCO returns `TailCall<T>` instead of `T`:
532/// - [`TailCall::Done`] means the final value is ready.
533/// - [`TailCall::Call`] means there is another step to execute.
534pub enum TailCall<T> {
535    /// Computation is finished; `T` is the final value.
536    Done(T),
537    /// Another step is needed; the boxed closure produces the next
538    /// `TailCall<T>` without growing the call stack.
539    Call(Box<dyn FnOnce() -> TailCall<T>>),
540}
541/// Result of a single interpreter step (for a TCO-aware interpreter loop).
542pub enum StepResult<State, Output> {
543    /// Continue with an updated state.
544    Continue(State),
545    /// Computation finished with this output.
546    Finished(Output),
547    /// An error occurred.
548    Error(String),
549}
550/// High-level optimizer that applies TCO analysis to bytecode chunks.
551#[allow(dead_code)]
552pub struct TailCallOptimizer {
553    detector: TailCallDetector,
554    /// Statistics accumulator.
555    pub stats: TailCallCounter,
556}
557impl TailCallOptimizer {
558    /// Create a new optimizer.
559    #[allow(dead_code)]
560    pub fn new() -> Self {
561        Self {
562            detector: TailCallDetector::new(),
563            stats: TailCallCounter::new(),
564        }
565    }
566    /// Analyse a chunk of opcodes and return the report.
567    #[allow(dead_code)]
568    pub fn analyse_chunk(&mut self, opcodes: &[&str]) -> TailCallAnalysisReport {
569        self.detector.analyse(opcodes);
570        let report = TailCallAnalysisReport::build(&self.detector, opcodes);
571        self.stats.optimized += report.tail_positions.len() as u64;
572        report
573    }
574    /// Returns `true` if the given index is a tail call.
575    #[allow(dead_code)]
576    pub fn is_tail_call(&self, idx: usize) -> bool {
577        self.detector.is_tail(idx)
578    }
579}
580/// Analysis result for a bytecode chunk, pairing detection results with stats.
581#[allow(dead_code)]
582#[derive(Clone, Debug)]
583pub struct TailCallAnalysisReport {
584    /// Indices of tail-position call instructions.
585    pub tail_positions: Vec<usize>,
586    /// Total number of call instructions found.
587    pub total_calls: usize,
588    /// Fraction of calls that are in tail position.
589    pub tail_ratio: f64,
590    /// Human-readable summary.
591    pub summary: String,
592}
593impl TailCallAnalysisReport {
594    /// Build a report from a detector result and the original opcodes.
595    #[allow(dead_code)]
596    pub fn build(detector: &TailCallDetector, opcodes: &[&str]) -> Self {
597        let total_calls = opcodes.iter().filter(|&&op| op == "Call").count();
598        let tail_count = detector.count();
599        let tail_ratio = if total_calls == 0 {
600            0.0
601        } else {
602            tail_count as f64 / total_calls as f64
603        };
604        let summary = format!(
605            "{}/{} calls ({:.0}%) are in tail position",
606            tail_count,
607            total_calls,
608            tail_ratio * 100.0
609        );
610        Self {
611            tail_positions: detector.tail_positions.clone(),
612            total_calls,
613            tail_ratio,
614            summary,
615        }
616    }
617}
618/// Extended detector that classifies tail positions more finely.
619#[allow(dead_code)]
620pub struct ExtendedTailCallDetector {
621    /// Current function being analyzed.
622    pub current_function: String,
623    /// Positions and their classifications.
624    pub classified: Vec<(usize, TailPositionKind)>,
625}
626impl ExtendedTailCallDetector {
627    /// Create a new detector for `function_name`.
628    #[allow(dead_code)]
629    pub fn new(function_name: &str) -> Self {
630        Self {
631            current_function: function_name.to_string(),
632            classified: Vec::new(),
633        }
634    }
635    /// Analyse a list of `(opcode_name, callee_name)` pairs.
636    #[allow(dead_code)]
637    pub fn analyse_with_callees(&mut self, ops: &[(&str, Option<&str>)]) {
638        self.classified.clear();
639        for (i, (op, callee)) in ops.iter().enumerate() {
640            if *op != "Call" {
641                continue;
642            }
643            let next = ops.get(i + 1).map(|(o, _)| *o).unwrap_or("");
644            if next != "Return" && next != "Halt" {
645                self.classified.push((i, TailPositionKind::NonTail));
646                continue;
647            }
648            let kind = match *callee {
649                Some(name) if name == self.current_function => TailPositionKind::SelfTailCall,
650                Some(_) => TailPositionKind::MutualTailCall,
651                None => TailPositionKind::ExternalTailCall,
652            };
653            self.classified.push((i, kind));
654        }
655    }
656    /// Count how many positions are of the given kind.
657    #[allow(dead_code)]
658    pub fn count_kind(&self, kind: TailPositionKind) -> usize {
659        self.classified.iter().filter(|(_, k)| *k == kind).count()
660    }
661}
662/// More fine-grained classification of a tail position.
663#[allow(dead_code)]
664#[derive(Clone, Copy, Debug, PartialEq, Eq)]
665pub enum TailPositionKind {
666    /// Direct self-tail-call (trivially TCO-able).
667    SelfTailCall,
668    /// Tail call to a sibling function (mutually recursive TCO).
669    MutualTailCall,
670    /// Tail call to an unknown/external function.
671    ExternalTailCall,
672    /// Not a tail call.
673    NonTail,
674}
675/// A thunk: either an unevaluated computation or a memoized value.
676#[allow(dead_code)]
677pub enum Thunk<T> {
678    /// Not yet evaluated.
679    Deferred(Box<dyn FnOnce() -> T>),
680    /// Already evaluated; value is cached.
681    Evaluated(T),
682}
683impl<T: Clone> Thunk<T> {
684    /// Create a deferred thunk.
685    #[allow(dead_code)]
686    pub fn defer(f: impl FnOnce() -> T + 'static) -> Self {
687        Thunk::Deferred(Box::new(f))
688    }
689    /// Force evaluation and memoize the result.
690    #[allow(dead_code)]
691    pub fn force(&mut self) -> &T {
692        match self {
693            Thunk::Evaluated(ref v) => v,
694            Thunk::Deferred(_) => {
695                let Thunk::Deferred(f) =
696                    std::mem::replace(self, Thunk::Evaluated(unsafe { std::mem::zeroed() }))
697                else {
698                    unreachable!()
699                };
700                let val = f();
701                *self = Thunk::Evaluated(val);
702                match self {
703                    Thunk::Evaluated(ref v) => v,
704                    _ => unreachable!(),
705                }
706            }
707        }
708    }
709}
710/// A simple symbolic rewrite rule.
711#[allow(dead_code)]
712#[derive(Clone, Debug)]
713pub struct RewriteRule {
714    /// Pattern string (LHS).
715    pub lhs: String,
716    /// Replacement string (RHS).
717    pub rhs: String,
718    /// Whether this rule fires unconditionally.
719    pub unconditional: bool,
720}
721impl RewriteRule {
722    /// Create a new unconditional rewrite rule.
723    #[allow(dead_code)]
724    pub fn new(lhs: &str, rhs: &str) -> Self {
725        Self {
726            lhs: lhs.to_string(),
727            rhs: rhs.to_string(),
728            unconditional: true,
729        }
730    }
731    /// Create a conditional rewrite rule.
732    #[allow(dead_code)]
733    pub fn conditional(lhs: &str, rhs: &str) -> Self {
734        Self {
735            lhs: lhs.to_string(),
736            rhs: rhs.to_string(),
737            unconditional: false,
738        }
739    }
740}
741/// Per-function trampoline metrics.
742#[allow(dead_code)]
743#[derive(Clone, Debug, Default)]
744pub struct FunctionTcoMetrics {
745    /// Name of the function.
746    pub name: String,
747    /// Number of times this function was called via trampoline.
748    pub call_count: u64,
749    /// Maximum recursion depth eliminated for this function.
750    pub max_depth_eliminated: u64,
751    /// Total steps this function contributed.
752    pub total_steps: u64,
753}
754impl FunctionTcoMetrics {
755    /// Create metrics for a named function.
756    #[allow(dead_code)]
757    pub fn new(name: &str) -> Self {
758        Self {
759            name: name.to_string(),
760            ..Default::default()
761        }
762    }
763    /// Record a call at the given depth.
764    #[allow(dead_code)]
765    pub fn record(&mut self, depth: u64) {
766        self.call_count += 1;
767        self.total_steps += depth;
768        if depth > self.max_depth_eliminated {
769            self.max_depth_eliminated = depth;
770        }
771    }
772    /// Average depth per call.
773    #[allow(dead_code)]
774    pub fn avg_depth(&self) -> f64 {
775        if self.call_count == 0 {
776            0.0
777        } else {
778            self.total_steps as f64 / self.call_count as f64
779        }
780    }
781}
782/// Represents a chain of tail calls that can potentially be fused.
783#[allow(dead_code)]
784#[derive(Clone, Debug)]
785pub struct TailCallChain {
786    /// Ordered list of function names in the chain.
787    pub functions: Vec<String>,
788    /// Whether the chain can be safely fused into a single jump.
789    pub can_fuse: bool,
790}
791impl TailCallChain {
792    /// Create a new chain.
793    #[allow(dead_code)]
794    pub fn new() -> Self {
795        Self {
796            functions: Vec::new(),
797            can_fuse: true,
798        }
799    }
800    /// Add a function to the chain.
801    #[allow(dead_code)]
802    pub fn push(&mut self, name: &str) {
803        self.functions.push(name.to_string());
804    }
805    /// Mark as non-fusable.
806    #[allow(dead_code)]
807    pub fn mark_non_fusable(&mut self) {
808        self.can_fuse = false;
809    }
810    /// Length of the chain.
811    #[allow(dead_code)]
812    pub fn len(&self) -> usize {
813        self.functions.len()
814    }
815    /// Whether the chain is empty.
816    #[allow(dead_code)]
817    pub fn is_empty(&self) -> bool {
818        self.functions.is_empty()
819    }
820}
821/// A full TCO optimization pass over a module's functions.
822#[allow(dead_code)]
823pub struct TailCallOptimizationPass {
824    /// Optimizer instance.
825    pub optimizer: TailCallOptimizer,
826    /// Functions processed.
827    pub processed: Vec<String>,
828    /// Functions skipped (already TCO-safe or inlined).
829    pub skipped: Vec<String>,
830}
831impl TailCallOptimizationPass {
832    /// Create a new pass.
833    #[allow(dead_code)]
834    pub fn new() -> Self {
835        Self {
836            optimizer: TailCallOptimizer::new(),
837            processed: Vec::new(),
838            skipped: Vec::new(),
839        }
840    }
841    /// Process a function with the given bytecode opcode names.
842    /// Returns the analysis report.
843    #[allow(dead_code)]
844    pub fn process_function(
845        &mut self,
846        name: &str,
847        opcodes: &[&str],
848        skip: bool,
849    ) -> Option<TailCallAnalysisReport> {
850        if skip {
851            self.skipped.push(name.to_string());
852            return None;
853        }
854        let report = self.optimizer.analyse_chunk(opcodes);
855        self.processed.push(name.to_string());
856        Some(report)
857    }
858    /// Summary of processing.
859    #[allow(dead_code)]
860    pub fn summary(&self) -> String {
861        format!(
862            "TCO pass: {} processed, {} skipped, {} tail calls found",
863            self.processed.len(),
864            self.skipped.len(),
865            self.optimizer.stats.optimized
866        )
867    }
868}
869/// Tracks how many tail calls were optimized during a run.
870#[derive(Clone, Debug, Default)]
871pub struct TailCallCounter {
872    /// Total tail calls optimized (turned into loop iterations).
873    pub optimized: u64,
874    /// Maximum trampoline depth seen in a single evaluation.
875    pub max_depth: u64,
876}
877impl TailCallCounter {
878    /// Create a zeroed counter.
879    pub fn new() -> Self {
880        Self::default()
881    }
882    /// Record one optimized tail call at the given depth.
883    pub fn record(&mut self, depth: u64) {
884        self.optimized += 1;
885        if depth > self.max_depth {
886            self.max_depth = depth;
887        }
888    }
889}
890/// Decision on whether to inline a call.
891#[allow(dead_code)]
892#[derive(Clone, Copy, Debug, PartialEq, Eq)]
893pub enum InliningDecision {
894    /// Inline the callee.
895    Inline,
896    /// Do not inline (call it normally).
897    DoNotInline,
898    /// Force inline regardless of heuristics (e.g., `#[inline(always)]`).
899    ForceInline,
900}
901/// Controls the behaviour of a batched tail-call scheduler.
902#[allow(dead_code)]
903#[derive(Clone, Debug)]
904pub struct TailCallSchedulerConfig {
905    /// Maximum number of trampoline steps per batch before yielding.
906    pub max_steps_per_batch: usize,
907    /// Maximum total steps allowed before returning an error.
908    pub step_limit: u64,
909}
910/// Result of a tail-call micro-benchmark.
911#[allow(dead_code)]
912#[derive(Clone, Debug)]
913pub struct TailCallBenchmarkResult {
914    /// Name of the benchmark.
915    pub name: String,
916    /// Number of iterations run.
917    pub iterations: u64,
918    /// Total wall-clock nanoseconds (stub: always 0 in tests).
919    pub total_ns: u64,
920    /// Final computed value.
921    pub value: u64,
922}
923impl TailCallBenchmarkResult {
924    /// Compute throughput in iterations per second (0 if total_ns == 0).
925    #[allow(dead_code)]
926    pub fn throughput(&self) -> f64 {
927        if self.total_ns == 0 {
928            0.0
929        } else {
930            self.iterations as f64 / (self.total_ns as f64 / 1_000_000_000.0)
931        }
932    }
933    /// Format as a human-readable report line.
934    #[allow(dead_code)]
935    pub fn report(&self) -> String {
936        format!(
937            "Benchmark[{}]: {} iters, {} ns, value={}",
938            self.name, self.iterations, self.total_ns, self.value
939        )
940    }
941}
942/// A peephole optimization rule: if the opcode pattern matches, replace it.
943#[allow(dead_code)]
944pub struct PeepholeRule {
945    /// The opcode sequence to match (by name).
946    pub pattern: Vec<&'static str>,
947    /// The replacement opcode sequence.
948    pub replacement: Vec<&'static str>,
949    /// Human-readable description of the optimization.
950    pub description: &'static str,
951}
952impl PeepholeRule {
953    /// Create a new peephole rule.
954    #[allow(dead_code)]
955    pub fn new(
956        pattern: Vec<&'static str>,
957        replacement: Vec<&'static str>,
958        description: &'static str,
959    ) -> Self {
960        Self {
961            pattern,
962            replacement,
963            description,
964        }
965    }
966}
967/// A proof certificate that a function is TCO-safe.
968#[allow(dead_code)]
969#[derive(Clone, Debug)]
970pub struct TailCallProof {
971    /// Name of the function proven TCO-safe.
972    pub function_name: String,
973    /// Which argument decreases on each call.
974    pub decreasing_argument: String,
975    /// Well-founded ordering used.
976    pub ordering: String,
977    /// Informal justification.
978    pub justification: String,
979}
980impl TailCallProof {
981    /// Create a proof certificate.
982    #[allow(dead_code)]
983    pub fn new(
984        function_name: &str,
985        decreasing_argument: &str,
986        ordering: &str,
987        justification: &str,
988    ) -> Self {
989        Self {
990            function_name: function_name.to_string(),
991            decreasing_argument: decreasing_argument.to_string(),
992            ordering: ordering.to_string(),
993            justification: justification.to_string(),
994        }
995    }
996    /// Format the proof as a human-readable string.
997    #[allow(dead_code)]
998    pub fn format(&self) -> String {
999        format!(
1000            "TCO Proof for `{}`:\n  Decreasing: {}\n  Ordering: {}\n  Justification: {}",
1001            self.function_name, self.decreasing_argument, self.ordering, self.justification
1002        )
1003    }
1004}
1005/// Analysis result for a single bytecode instruction position.
1006#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1007pub enum TailPosition {
1008    /// The instruction is in tail position (its result leaves the function).
1009    Tail,
1010    /// The instruction is not in tail position.
1011    NonTail,
1012}
1013/// An explicit call stack used when TCO cannot be applied.
1014#[allow(dead_code)]
1015pub struct ExplicitCallStack {
1016    frames: Vec<StackFrame>,
1017    /// Maximum depth ever reached.
1018    pub max_depth: usize,
1019}
1020impl ExplicitCallStack {
1021    /// Create an empty call stack.
1022    #[allow(dead_code)]
1023    pub fn new() -> Self {
1024        Self {
1025            frames: Vec::new(),
1026            max_depth: 0,
1027        }
1028    }
1029    /// Push a new frame.
1030    #[allow(dead_code)]
1031    pub fn push(&mut self, frame: StackFrame) {
1032        self.frames.push(frame);
1033        if self.frames.len() > self.max_depth {
1034            self.max_depth = self.frames.len();
1035        }
1036    }
1037    /// Pop the top frame.
1038    #[allow(dead_code)]
1039    pub fn pop(&mut self) -> Option<StackFrame> {
1040        self.frames.pop()
1041    }
1042    /// Return the current depth.
1043    #[allow(dead_code)]
1044    pub fn depth(&self) -> usize {
1045        self.frames.len()
1046    }
1047    /// Peek at the top frame without removing it.
1048    #[allow(dead_code)]
1049    pub fn top(&self) -> Option<&StackFrame> {
1050        self.frames.last()
1051    }
1052    /// Peek at the top frame mutably.
1053    #[allow(dead_code)]
1054    pub fn top_mut(&mut self) -> Option<&mut StackFrame> {
1055        self.frames.last_mut()
1056    }
1057    /// Return a backtrace as a list of function names.
1058    #[allow(dead_code)]
1059    pub fn backtrace(&self) -> Vec<&str> {
1060        self.frames.iter().map(|f| f.function.as_str()).collect()
1061    }
1062    /// Return a formatted backtrace string.
1063    #[allow(dead_code)]
1064    pub fn format_backtrace(&self) -> String {
1065        let mut out = String::from("Backtrace (most recent last):\n");
1066        for (i, frame) in self.frames.iter().enumerate() {
1067            out.push_str(&format!(
1068                "  {:3}: {} @ {}\n",
1069                i, frame.function, frame.return_address
1070            ));
1071        }
1072        out
1073    }
1074}
1075/// Result of a scheduler tick.
1076#[allow(dead_code)]
1077pub enum SchedulerTickResult<T> {
1078    /// The computation is not yet done.
1079    Pending,
1080    /// The computation finished with value `T`.
1081    Finished(T),
1082    /// The step limit was exceeded.
1083    StepLimitExceeded,
1084}
1085/// Represents a step of a mutually-recursive computation.
1086///
1087/// Two functions `even` and `odd` can be represented using a single
1088/// `MutualTailCall` enum, with `A` and `B` tagging which "side" we are on.
1089#[allow(dead_code)]
1090pub enum MutualTailCall<A, B, R> {
1091    /// Continue with the A-branch.
1092    GoA(A, Box<dyn FnOnce(A) -> MutualTailCall<A, B, R>>),
1093    /// Continue with the B-branch.
1094    GoB(B, Box<dyn FnOnce(B) -> MutualTailCall<A, B, R>>),
1095    /// Final result.
1096    Done(R),
1097}
1098/// An explicit continuation frame.
1099#[allow(dead_code)]
1100#[derive(Clone, Debug)]
1101pub enum ContinuationFrame {
1102    /// Apply a binary operation to the top of the value stack and this operand.
1103    ApplyBinop { op: BinopKind, operand: u64 },
1104    /// Store the result in the named variable.
1105    StoreResult { var: String },
1106    /// Print the result.
1107    PrintResult,
1108}
1109/// The result of partially evaluating an expression.
1110#[allow(dead_code)]
1111#[derive(Clone, Debug, PartialEq)]
1112pub enum PartialValue {
1113    /// Known concrete value.
1114    Known(u64),
1115    /// Unknown symbolic value.
1116    Unknown(String),
1117    /// Bottom (error / undefined).
1118    Bottom,
1119}
1120impl PartialValue {
1121    /// Return true if the value is concrete.
1122    #[allow(dead_code)]
1123    pub fn is_known(&self) -> bool {
1124        matches!(self, PartialValue::Known(_))
1125    }
1126    /// Add two partial values.
1127    #[allow(dead_code)]
1128    pub fn add(&self, other: &PartialValue) -> PartialValue {
1129        match (self, other) {
1130            (PartialValue::Known(a), PartialValue::Known(b)) => {
1131                PartialValue::Known(a.wrapping_add(*b))
1132            }
1133            (PartialValue::Bottom, _) | (_, PartialValue::Bottom) => PartialValue::Bottom,
1134            _ => PartialValue::Unknown(format!("{:?} + {:?}", self, other)),
1135        }
1136    }
1137    /// Multiply two partial values.
1138    #[allow(dead_code)]
1139    pub fn mul(&self, other: &PartialValue) -> PartialValue {
1140        match (self, other) {
1141            (PartialValue::Known(a), PartialValue::Known(b)) => {
1142                PartialValue::Known(a.wrapping_mul(*b))
1143            }
1144            (PartialValue::Known(0), _) | (_, PartialValue::Known(0)) => PartialValue::Known(0),
1145            (PartialValue::Bottom, _) | (_, PartialValue::Bottom) => PartialValue::Bottom,
1146            _ => PartialValue::Unknown(format!("{:?} * {:?}", self, other)),
1147        }
1148    }
1149}
1150/// A state in an explicit finite state machine.
1151#[allow(dead_code)]
1152#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1153pub struct StateMachineState {
1154    /// Numeric state identifier.
1155    pub id: u32,
1156    /// Accumulated output collected during transitions.
1157    pub output: Vec<String>,
1158}
1159impl StateMachineState {
1160    /// Create a state with the given id and empty output.
1161    #[allow(dead_code)]
1162    pub fn new(id: u32) -> Self {
1163        Self {
1164            id,
1165            output: Vec::new(),
1166        }
1167    }
1168    /// Append a string to the accumulated output.
1169    #[allow(dead_code)]
1170    pub fn emit(mut self, s: &str) -> Self {
1171        self.output.push(s.to_string());
1172        self
1173    }
1174}
1175/// A continuation-based evaluator with an explicit continuation stack.
1176#[allow(dead_code)]
1177pub struct ContinuationEvaluator {
1178    /// Value stack.
1179    pub values: Vec<u64>,
1180    /// Continuation stack.
1181    pub continuations: Vec<ContinuationFrame>,
1182    /// Named variable bindings.
1183    pub env: std::collections::HashMap<String, u64>,
1184}
1185impl ContinuationEvaluator {
1186    /// Create a new evaluator.
1187    #[allow(dead_code)]
1188    pub fn new() -> Self {
1189        Self {
1190            values: Vec::new(),
1191            continuations: Vec::new(),
1192            env: std::collections::HashMap::new(),
1193        }
1194    }
1195    /// Push a value onto the value stack.
1196    #[allow(dead_code)]
1197    pub fn push_value(&mut self, v: u64) {
1198        self.values.push(v);
1199    }
1200    /// Pop a value from the value stack.
1201    #[allow(dead_code)]
1202    pub fn pop_value(&mut self) -> Option<u64> {
1203        self.values.pop()
1204    }
1205    /// Push a continuation frame.
1206    #[allow(dead_code)]
1207    pub fn push_cont(&mut self, frame: ContinuationFrame) {
1208        self.continuations.push(frame);
1209    }
1210    /// Step: pop one continuation frame and apply it to the top value.
1211    #[allow(dead_code)]
1212    pub fn step(&mut self) -> bool {
1213        let frame = match self.continuations.pop() {
1214            Some(f) => f,
1215            None => return false,
1216        };
1217        match frame {
1218            ContinuationFrame::ApplyBinop { op, operand } => {
1219                if let Some(lhs) = self.values.pop() {
1220                    if let Some(result) = op.eval(lhs, operand) {
1221                        self.values.push(result);
1222                    }
1223                }
1224            }
1225            ContinuationFrame::StoreResult { var } => {
1226                if let Some(v) = self.values.last().copied() {
1227                    self.env.insert(var, v);
1228                }
1229            }
1230            ContinuationFrame::PrintResult => {}
1231        }
1232        true
1233    }
1234    /// Run to completion.
1235    #[allow(dead_code)]
1236    pub fn run(&mut self) {
1237        while self.step() {}
1238    }
1239}