Skip to main content

oxilean_runtime/bytecode_interp/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::{BTreeSet, HashMap};
6
7use super::functions::{estimate_max_stack_depth, opcode_kind};
8
9/// Statistics about a bytecode chunk.
10#[derive(Clone, Debug, Default)]
11#[allow(dead_code)]
12pub struct ChunkStats {
13    /// Total number of instructions.
14    pub total_instructions: usize,
15    /// Number of branch instructions.
16    pub branch_count: usize,
17    /// Number of arithmetic instructions.
18    pub arith_count: usize,
19    /// Number of load/store instructions.
20    pub mem_count: usize,
21    /// Number of call/return instructions.
22    pub call_count: usize,
23    /// Estimated max stack depth.
24    pub max_stack_depth: usize,
25}
26impl ChunkStats {
27    /// Compute statistics for a chunk.
28    pub fn compute(chunk: &BytecodeChunk) -> Self {
29        let mut stats = ChunkStats::default();
30        stats.total_instructions = chunk.opcodes.len();
31        stats.max_stack_depth = estimate_max_stack_depth(chunk);
32        for op in &chunk.opcodes {
33            match op {
34                Opcode::Jump(_) | Opcode::JumpIf(_) | Opcode::JumpIfNot(_) => {
35                    stats.branch_count += 1;
36                }
37                Opcode::Add
38                | Opcode::Sub
39                | Opcode::Mul
40                | Opcode::Div
41                | Opcode::Mod
42                | Opcode::Eq
43                | Opcode::Lt
44                | Opcode::Le
45                | Opcode::Not
46                | Opcode::And
47                | Opcode::Or => {
48                    stats.arith_count += 1;
49                }
50                Opcode::Load(_) | Opcode::Store(_) | Opcode::LoadGlobal(_) => {
51                    stats.mem_count += 1;
52                }
53                Opcode::Call(_) | Opcode::Return => {
54                    stats.call_count += 1;
55                }
56                _ => {}
57            }
58        }
59        stats
60    }
61}
62/// A saved call frame for nested function calls.
63#[derive(Debug, Clone)]
64#[allow(dead_code)]
65pub struct CallFrame {
66    /// The return instruction pointer.
67    pub return_ip: usize,
68    /// The base of the local variable array for this frame.
69    pub locals_base: usize,
70    /// Name of the function being called (for diagnostics).
71    pub fn_name: String,
72}
73impl CallFrame {
74    /// Create a new call frame.
75    pub fn new(return_ip: usize, locals_base: usize, fn_name: impl Into<String>) -> Self {
76        CallFrame {
77            return_ip,
78            locals_base,
79            fn_name: fn_name.into(),
80        }
81    }
82}
83/// A simple inline cache entry for a call site.
84#[derive(Clone, Debug)]
85#[allow(dead_code)]
86pub struct InlineCacheEntry {
87    /// Opcode index of the call site.
88    pub call_ip: usize,
89    /// Number of times this call site has been observed.
90    pub hit_count: u64,
91    /// The most recently observed target function position.
92    pub last_target: Option<u32>,
93    /// Whether this entry is considered monomorphic.
94    pub is_monomorphic: bool,
95}
96impl InlineCacheEntry {
97    /// Create a new IC entry for a given call site.
98    pub fn new(call_ip: usize) -> Self {
99        InlineCacheEntry {
100            call_ip,
101            hit_count: 0,
102            last_target: None,
103            is_monomorphic: true,
104        }
105    }
106    /// Record a call to `target_fn`.
107    pub fn record_call(&mut self, target_fn: u32) {
108        self.hit_count += 1;
109        match self.last_target {
110            None => {
111                self.last_target = Some(target_fn);
112            }
113            Some(prev) if prev != target_fn => {
114                self.is_monomorphic = false;
115            }
116            _ => {}
117        }
118    }
119}
120/// A DSL-style builder for constructing bytecode chunks.
121///
122/// Allows fluent construction of bytecode sequences without manually
123/// managing jump offsets.
124pub struct ChunkBuilder {
125    chunk: BytecodeChunk,
126}
127impl ChunkBuilder {
128    /// Create a new builder for a chunk with the given name.
129    pub fn new(name: &str) -> Self {
130        ChunkBuilder {
131            chunk: BytecodeChunk::new(name),
132        }
133    }
134    /// Emit a `Push(n)` instruction.
135    pub fn push_nat(mut self, n: u64) -> Self {
136        self.chunk.push_op(Opcode::Push(n));
137        self
138    }
139    /// Emit a `PushBool(b)` instruction.
140    pub fn push_bool(mut self, b: bool) -> Self {
141        self.chunk.push_op(Opcode::PushBool(b));
142        self
143    }
144    /// Emit a `PushStr(s)` instruction.
145    pub fn push_str(mut self, s: &str) -> Self {
146        self.chunk.push_op(Opcode::PushStr(s.to_string()));
147        self
148    }
149    /// Emit `PushNil`.
150    pub fn push_nil(mut self) -> Self {
151        self.chunk.push_op(Opcode::PushNil);
152        self
153    }
154    /// Emit `Add`.
155    pub fn add(mut self) -> Self {
156        self.chunk.push_op(Opcode::Add);
157        self
158    }
159    /// Emit `Sub`.
160    pub fn sub(mut self) -> Self {
161        self.chunk.push_op(Opcode::Sub);
162        self
163    }
164    /// Emit `Mul`.
165    pub fn mul(mut self) -> Self {
166        self.chunk.push_op(Opcode::Mul);
167        self
168    }
169    /// Emit `Div`.
170    pub fn div(mut self) -> Self {
171        self.chunk.push_op(Opcode::Div);
172        self
173    }
174    /// Emit `Mod`.
175    pub fn modulo(mut self) -> Self {
176        self.chunk.push_op(Opcode::Mod);
177        self
178    }
179    /// Emit `Eq`.
180    pub fn eq(mut self) -> Self {
181        self.chunk.push_op(Opcode::Eq);
182        self
183    }
184    /// Emit `Lt`.
185    pub fn lt(mut self) -> Self {
186        self.chunk.push_op(Opcode::Lt);
187        self
188    }
189    /// Emit `Le`.
190    pub fn le(mut self) -> Self {
191        self.chunk.push_op(Opcode::Le);
192        self
193    }
194    /// Emit `Not`.
195    pub fn not(mut self) -> Self {
196        self.chunk.push_op(Opcode::Not);
197        self
198    }
199    /// Emit `And`.
200    pub fn and(mut self) -> Self {
201        self.chunk.push_op(Opcode::And);
202        self
203    }
204    /// Emit `Or`.
205    pub fn or(mut self) -> Self {
206        self.chunk.push_op(Opcode::Or);
207        self
208    }
209    /// Emit `Dup`.
210    pub fn dup(mut self) -> Self {
211        self.chunk.push_op(Opcode::Dup);
212        self
213    }
214    /// Emit `Pop`.
215    pub fn pop(mut self) -> Self {
216        self.chunk.push_op(Opcode::Pop);
217        self
218    }
219    /// Emit `Swap`.
220    pub fn swap(mut self) -> Self {
221        self.chunk.push_op(Opcode::Swap);
222        self
223    }
224    /// Emit `Load(idx)`.
225    pub fn load(mut self, idx: u32) -> Self {
226        self.chunk.push_op(Opcode::Load(idx));
227        self
228    }
229    /// Emit `Store(idx)`.
230    pub fn store(mut self, idx: u32) -> Self {
231        self.chunk.push_op(Opcode::Store(idx));
232        self
233    }
234    /// Emit `LoadGlobal(name)`.
235    pub fn load_global(mut self, name: &str) -> Self {
236        self.chunk.push_op(Opcode::LoadGlobal(name.to_string()));
237        self
238    }
239    /// Emit `Jump(offset)`.
240    pub fn jump(mut self, offset: i32) -> Self {
241        self.chunk.push_op(Opcode::Jump(offset));
242        self
243    }
244    /// Emit `JumpIf(offset)`.
245    pub fn jump_if(mut self, offset: i32) -> Self {
246        self.chunk.push_op(Opcode::JumpIf(offset));
247        self
248    }
249    /// Emit `JumpIfNot(offset)`.
250    pub fn jump_if_not(mut self, offset: i32) -> Self {
251        self.chunk.push_op(Opcode::JumpIfNot(offset));
252        self
253    }
254    /// Emit `Call(pos)`.
255    pub fn call(mut self, pos: u32) -> Self {
256        self.chunk.push_op(Opcode::Call(pos));
257        self
258    }
259    /// Emit `Return`.
260    pub fn ret(mut self) -> Self {
261        self.chunk.push_op(Opcode::Return);
262        self
263    }
264    /// Emit `MakeClosure(n)`.
265    pub fn make_closure(mut self, n: u32) -> Self {
266        self.chunk.push_op(Opcode::MakeClosure(n));
267        self
268    }
269    /// Emit `Apply`.
270    pub fn apply(mut self) -> Self {
271        self.chunk.push_op(Opcode::Apply);
272        self
273    }
274    /// Emit `Halt`.
275    pub fn halt(mut self) -> Self {
276        self.chunk.push_op(Opcode::Halt);
277        self
278    }
279    /// Finalize and return the built chunk.
280    pub fn build(self) -> BytecodeChunk {
281        self.chunk
282    }
283    /// Current number of opcodes in the builder.
284    pub fn current_ip(&self) -> usize {
285        self.chunk.opcodes.len()
286    }
287}
288/// Result of applying a call convention.
289#[derive(Debug, Clone, PartialEq)]
290#[allow(dead_code)]
291pub enum CallResult {
292    /// Exact application: `n == arity`.
293    Exact { fn_pos: u32, args: Vec<StackValue> },
294    /// Under-application: returns a new PAP-like closure.
295    Partial {
296        captured: Vec<StackValue>,
297        remaining_arity: u32,
298    },
299    /// Over-application: apply to arity args, then apply result to rest.
300    Over {
301        fn_pos: u32,
302        first_args: Vec<StackValue>,
303        rest_args: Vec<StackValue>,
304    },
305}
306/// Remove instructions that can never be executed.
307///
308/// This simple pass detects instructions immediately following an unconditional
309/// `Halt` or `Jump` that are not targeted by any branch.
310pub struct DeadCodeEliminator;
311impl DeadCodeEliminator {
312    /// Remove dead instructions from a chunk.
313    pub fn eliminate(chunk: &BytecodeChunk) -> BytecodeChunk {
314        let n = chunk.opcodes.len();
315        let mut reachable = vec![false; n];
316        let mut worklist = vec![0usize];
317        while let Some(ip) = worklist.pop() {
318            if ip >= n || reachable[ip] {
319                continue;
320            }
321            reachable[ip] = true;
322            let op = &chunk.opcodes[ip];
323            match op {
324                Opcode::Halt | Opcode::Return => {}
325                Opcode::Jump(off) => {
326                    let target = (ip as i64 + 1 + *off as i64) as usize;
327                    if target < n {
328                        worklist.push(target);
329                    }
330                }
331                Opcode::JumpIf(off) | Opcode::JumpIfNot(off) => {
332                    let target = (ip as i64 + 1 + *off as i64) as usize;
333                    if target < n {
334                        worklist.push(target);
335                    }
336                    worklist.push(ip + 1);
337                }
338                _ => {
339                    worklist.push(ip + 1);
340                }
341            }
342        }
343        let mut out = BytecodeChunk::new(&chunk.name);
344        for (i, op) in chunk.opcodes.iter().enumerate() {
345            if reachable[i] {
346                out.push_op(op.clone());
347            }
348        }
349        out
350    }
351}
352/// A per-chunk inline cache.
353#[derive(Default, Debug)]
354#[allow(dead_code)]
355pub struct InlineCache {
356    entries: std::collections::HashMap<usize, InlineCacheEntry>,
357}
358impl InlineCache {
359    /// Create a new inline cache.
360    pub fn new() -> Self {
361        InlineCache::default()
362    }
363    /// Record a call at `call_ip` to `target_fn`.
364    pub fn record(&mut self, call_ip: usize, target_fn: u32) {
365        self.entries
366            .entry(call_ip)
367            .or_insert_with(|| InlineCacheEntry::new(call_ip))
368            .record_call(target_fn);
369    }
370    /// Get the IC entry for `call_ip`, if any.
371    pub fn entry(&self, call_ip: usize) -> Option<&InlineCacheEntry> {
372        self.entries.get(&call_ip)
373    }
374    /// Number of recorded call sites.
375    pub fn len(&self) -> usize {
376        self.entries.len()
377    }
378    /// Whether the cache is empty.
379    pub fn is_empty(&self) -> bool {
380        self.entries.is_empty()
381    }
382    /// Return all monomorphic call sites.
383    pub fn monomorphic_sites(&self) -> Vec<usize> {
384        self.entries
385            .values()
386            .filter(|e| e.is_monomorphic && e.last_target.is_some())
387            .map(|e| e.call_ip)
388            .collect()
389    }
390}
391/// Counts how many times each opcode type was executed.
392#[derive(Default, Clone, Debug)]
393#[allow(dead_code)]
394pub struct OpcodeProfile {
395    /// Raw count per opcode variant (indexed by discriminant).
396    counts: std::collections::HashMap<String, u64>,
397    /// Total instructions executed.
398    pub total_executed: u64,
399}
400impl OpcodeProfile {
401    /// Create a new empty profile.
402    pub fn new() -> Self {
403        OpcodeProfile::default()
404    }
405    /// Record execution of an opcode.
406    pub fn record(&mut self, op: &Opcode) {
407        let key = opcode_kind(op);
408        *self.counts.entry(key).or_insert(0) += 1;
409        self.total_executed += 1;
410    }
411    /// Get the count for a given opcode kind string (e.g., `"Add"`).
412    pub fn count(&self, kind: &str) -> u64 {
413        self.counts.get(kind).copied().unwrap_or(0)
414    }
415    /// Return a sorted (descending by count) list of `(kind, count)` pairs.
416    pub fn top_opcodes(&self) -> Vec<(String, u64)> {
417        let mut v: Vec<_> = self.counts.iter().map(|(k, v)| (k.clone(), *v)).collect();
418        v.sort_by(|a, b| b.1.cmp(&a.1));
419        v
420    }
421    /// Reset all counts.
422    pub fn reset(&mut self) {
423        self.counts.clear();
424        self.total_executed = 0;
425    }
426}
427/// An interpreter that manages a proper call frame stack.
428pub struct FramedInterpreter {
429    /// The operand stack.
430    pub stack: Vec<StackValue>,
431    /// All local variables across all frames (flat layout).
432    pub locals: Vec<StackValue>,
433    /// Frame stack (innermost at the back).
434    pub frames: Vec<CallFrame>,
435    /// Current instruction pointer.
436    pub ip: usize,
437    /// Maximum frame depth (0 = unlimited).
438    pub max_depth: usize,
439}
440impl FramedInterpreter {
441    /// Create a new framed interpreter.
442    pub fn new() -> Self {
443        FramedInterpreter {
444            stack: Vec::new(),
445            locals: Vec::new(),
446            frames: Vec::new(),
447            ip: 0,
448            max_depth: 256,
449        }
450    }
451    /// Set the maximum call depth.
452    pub fn with_max_depth(mut self, d: usize) -> Self {
453        self.max_depth = d;
454        self
455    }
456    /// Push a new call frame, saving `return_ip` and recording `locals_base`.
457    pub fn push_frame(&mut self, return_ip: usize, fn_name: &str) -> Result<(), String> {
458        if self.max_depth > 0 && self.frames.len() >= self.max_depth {
459            return Err(format!(
460                "call stack overflow (max depth {})",
461                self.max_depth
462            ));
463        }
464        let base = self.locals.len();
465        self.frames.push(CallFrame::new(return_ip, base, fn_name));
466        Ok(())
467    }
468    /// Pop the top call frame and restore the instruction pointer.
469    pub fn pop_frame(&mut self) -> Option<usize> {
470        let frame = self.frames.pop()?;
471        self.locals.truncate(frame.locals_base);
472        Some(frame.return_ip)
473    }
474    /// Current call depth.
475    pub fn depth(&self) -> usize {
476        self.frames.len()
477    }
478    /// Get a frame by depth (0 = innermost).
479    pub fn frame(&self, depth: usize) -> Option<&CallFrame> {
480        self.frames.get(self.frames.len().saturating_sub(1 + depth))
481    }
482    /// Pretty-print the current call stack trace.
483    pub fn stack_trace(&self) -> String {
484        let mut out = String::new();
485        for (i, frame) in self.frames.iter().rev().enumerate() {
486            out.push_str(&format!(
487                "  #{}: {} (return @{})\n",
488                i, frame.fn_name, frame.return_ip
489            ));
490        }
491        out
492    }
493    /// Reset the interpreter to a clean state.
494    pub fn reset(&mut self) {
495        self.stack.clear();
496        self.locals.clear();
497        self.frames.clear();
498        self.ip = 0;
499    }
500}
501/// A named sequence of opcodes.
502pub struct BytecodeChunk {
503    pub opcodes: Vec<Opcode>,
504    pub name: String,
505}
506impl BytecodeChunk {
507    /// Create a new empty chunk with the given name.
508    pub fn new(name: &str) -> Self {
509        BytecodeChunk {
510            opcodes: Vec::new(),
511            name: name.to_string(),
512        }
513    }
514    /// Append an opcode to the chunk.
515    pub fn push_op(&mut self, op: Opcode) {
516        self.opcodes.push(op);
517    }
518    /// Number of opcodes in the chunk.
519    pub fn len(&self) -> usize {
520        self.opcodes.len()
521    }
522    /// Whether the chunk is empty.
523    pub fn is_empty(&self) -> bool {
524        self.opcodes.is_empty()
525    }
526}
527/// A dispatch table mapping opcode tags to handler descriptions.
528///
529/// Used for debugging, introspection, and code generation tooling.
530#[derive(Clone, Debug)]
531#[allow(dead_code)]
532pub struct OpcodeInfo {
533    /// Opcode tag byte (as used in binary encoding).
534    pub tag: u8,
535    /// Human-readable mnemonic.
536    pub mnemonic: &'static str,
537    /// Number of bytes used by the instruction (including the tag byte).
538    pub byte_size: usize,
539    /// Whether the instruction affects control flow.
540    pub is_branch: bool,
541    /// Whether the instruction terminates a basic block.
542    pub is_terminator: bool,
543}
544/// A basic block: a maximal sequence of non-branching instructions.
545#[derive(Clone, Debug)]
546#[allow(dead_code)]
547pub struct BasicBlock {
548    /// Start index in the chunk's opcode array.
549    pub start: usize,
550    /// End index (exclusive).
551    pub end: usize,
552    /// Successor block indices (for control flow graph).
553    pub successors: Vec<usize>,
554}
555impl BasicBlock {
556    /// Number of instructions in this block.
557    pub fn len(&self) -> usize {
558        self.end - self.start
559    }
560    /// Whether this block is empty.
561    pub fn is_empty(&self) -> bool {
562        self.len() == 0
563    }
564}
565/// A more comprehensive constant folding pass that folds sequences of constant
566/// push + arithmetic/logic instructions into a single Push.
567pub struct ConstantFolder;
568impl ConstantFolder {
569    /// Fold all constant expressions in a chunk.
570    pub fn fold(chunk: &BytecodeChunk) -> BytecodeChunk {
571        let folded = Self::fold_ops(&chunk.opcodes);
572        let mut out = BytecodeChunk::new(&chunk.name);
573        for op in folded {
574            out.push_op(op);
575        }
576        out
577    }
578    fn fold_ops(ops: &[Opcode]) -> Vec<Opcode> {
579        let mut result: Vec<Opcode> = Vec::new();
580        let mut const_stack: Vec<Option<StackValue>> = Vec::new();
581        for op in ops {
582            match op {
583                Opcode::Push(n) => {
584                    result.push(op.clone());
585                    const_stack.push(Some(StackValue::Nat(*n)));
586                }
587                Opcode::PushBool(b) => {
588                    result.push(op.clone());
589                    const_stack.push(Some(StackValue::Bool(*b)));
590                }
591                Opcode::PushStr(s) => {
592                    result.push(op.clone());
593                    const_stack.push(Some(StackValue::Str(s.clone())));
594                }
595                Opcode::PushNil => {
596                    result.push(op.clone());
597                    const_stack.push(Some(StackValue::Nil));
598                }
599                Opcode::Add => {
600                    let b = const_stack.pop().flatten();
601                    let a = const_stack.pop().flatten();
602                    if let (Some(StackValue::Nat(av)), Some(StackValue::Nat(bv))) = (a, b) {
603                        let len = result.len();
604                        if len >= 2 {
605                            result.truncate(len - 2);
606                        }
607                        let folded = av.wrapping_add(bv);
608                        result.push(Opcode::Push(folded));
609                        const_stack.push(Some(StackValue::Nat(folded)));
610                    } else {
611                        result.push(op.clone());
612                        const_stack.push(None);
613                    }
614                }
615                Opcode::Mul => {
616                    let b = const_stack.pop().flatten();
617                    let a = const_stack.pop().flatten();
618                    if let (Some(StackValue::Nat(av)), Some(StackValue::Nat(bv))) = (a, b) {
619                        let len = result.len();
620                        if len >= 2 {
621                            result.truncate(len - 2);
622                        }
623                        let folded = av.wrapping_mul(bv);
624                        result.push(Opcode::Push(folded));
625                        const_stack.push(Some(StackValue::Nat(folded)));
626                    } else {
627                        result.push(op.clone());
628                        const_stack.push(None);
629                    }
630                }
631                Opcode::Sub => {
632                    let b = const_stack.pop().flatten();
633                    let a = const_stack.pop().flatten();
634                    if let (Some(StackValue::Nat(av)), Some(StackValue::Nat(bv))) = (a, b) {
635                        let len = result.len();
636                        if len >= 2 {
637                            result.truncate(len - 2);
638                        }
639                        let folded = av.wrapping_sub(bv);
640                        result.push(Opcode::Push(folded));
641                        const_stack.push(Some(StackValue::Nat(folded)));
642                    } else {
643                        result.push(op.clone());
644                        const_stack.push(None);
645                    }
646                }
647                Opcode::Not => {
648                    let a = const_stack.pop().flatten();
649                    if let Some(StackValue::Bool(bv)) = a {
650                        let len = result.len();
651                        if len >= 1 {
652                            result.truncate(len - 1);
653                        }
654                        result.push(Opcode::PushBool(!bv));
655                        const_stack.push(Some(StackValue::Bool(!bv)));
656                    } else {
657                        result.push(op.clone());
658                        const_stack.push(None);
659                    }
660                }
661                Opcode::Pop => {
662                    const_stack.pop();
663                    result.push(op.clone());
664                }
665                Opcode::Dup => {
666                    let top = const_stack.last().cloned().flatten();
667                    result.push(op.clone());
668                    const_stack.push(top.map(Some).unwrap_or(None));
669                }
670                Opcode::Swap => {
671                    let len = const_stack.len();
672                    if len >= 2 {
673                        const_stack.swap(len - 1, len - 2);
674                    }
675                    result.push(op.clone());
676                }
677                _ => {
678                    result.push(op.clone());
679                    const_stack.push(None);
680                }
681            }
682        }
683        result
684    }
685}
686/// Result of liveness analysis for a single chunk.
687#[derive(Clone, Debug, Default)]
688#[allow(dead_code)]
689pub struct LivenessInfo {
690    /// For each instruction index, the set of local variable indices that are
691    /// live (potentially used later) before that instruction executes.
692    pub live_before: Vec<std::collections::BTreeSet<u32>>,
693}
694/// An interpreter variant that tracks opcode profiling information.
695pub struct ProfilingInterpreter {
696    /// Underlying interpreter.
697    pub interp: Interpreter,
698    /// Opcode execution profile.
699    pub profile: OpcodeProfile,
700}
701impl ProfilingInterpreter {
702    /// Create a new profiling interpreter.
703    pub fn new() -> Self {
704        ProfilingInterpreter {
705            interp: Interpreter::new(),
706            profile: OpcodeProfile::new(),
707        }
708    }
709    /// Execute a chunk, collecting profiling data.
710    pub fn execute_chunk(&mut self, chunk: &BytecodeChunk) -> Result<StackValue, String> {
711        self.interp.ip = 0;
712        loop {
713            if self.interp.ip >= chunk.opcodes.len() {
714                break;
715            }
716            let op = chunk.opcodes[self.interp.ip].clone();
717            self.interp.ip += 1;
718            self.profile.record(&op);
719            let cont = self.interp.execute_op(&op, chunk)?;
720            if !cont {
721                break;
722            }
723        }
724        self.interp
725            .pop()
726            .ok_or_else(|| "stack underflow at end of chunk".to_string())
727    }
728}
729/// A simple peephole optimizer for bytecode chunks.
730///
731/// Applies a fixed set of local rewriting rules to reduce unnecessary
732/// instructions.
733pub struct PeepholeOptimizer {
734    /// Number of optimization passes to make.
735    pub passes: usize,
736}
737impl PeepholeOptimizer {
738    /// Create a new peephole optimizer with `passes` iterations.
739    pub fn new(passes: usize) -> Self {
740        PeepholeOptimizer {
741            passes: passes.max(1),
742        }
743    }
744    /// Optimize the given chunk, returning a new optimized chunk.
745    pub fn optimize(&self, chunk: &BytecodeChunk) -> BytecodeChunk {
746        let mut ops = chunk.opcodes.clone();
747        for _ in 0..self.passes {
748            ops = Self::run_pass(&ops);
749        }
750        let mut out = BytecodeChunk::new(&chunk.name);
751        for op in ops {
752            out.push_op(op);
753        }
754        out
755    }
756    fn run_pass(ops: &[Opcode]) -> Vec<Opcode> {
757        let mut result = Vec::with_capacity(ops.len());
758        let mut i = 0;
759        while i < ops.len() {
760            if i + 1 < ops.len() {
761                if let (Opcode::Push(_), Opcode::Pop) = (&ops[i], &ops[i + 1]) {
762                    i += 2;
763                    continue;
764                }
765            }
766            if i + 2 < ops.len() {
767                if let (Opcode::PushBool(a), Opcode::PushBool(b), Opcode::And) =
768                    (&ops[i], &ops[i + 1], &ops[i + 2])
769                {
770                    result.push(Opcode::PushBool(*a && *b));
771                    i += 3;
772                    continue;
773                }
774            }
775            if i + 2 < ops.len() {
776                if let (Opcode::PushBool(a), Opcode::PushBool(b), Opcode::Or) =
777                    (&ops[i], &ops[i + 1], &ops[i + 2])
778                {
779                    result.push(Opcode::PushBool(*a || *b));
780                    i += 3;
781                    continue;
782                }
783            }
784            if i + 1 < ops.len() {
785                if let (Opcode::PushBool(b), Opcode::Not) = (&ops[i], &ops[i + 1]) {
786                    result.push(Opcode::PushBool(!*b));
787                    i += 2;
788                    continue;
789                }
790            }
791            if i + 2 < ops.len() {
792                if let (Opcode::Push(a), Opcode::Push(b), Opcode::Add) =
793                    (&ops[i], &ops[i + 1], &ops[i + 2])
794                {
795                    result.push(Opcode::Push(a.wrapping_add(*b)));
796                    i += 3;
797                    continue;
798                }
799            }
800            if i + 2 < ops.len() {
801                if let (Opcode::Push(a), Opcode::Push(b), Opcode::Mul) =
802                    (&ops[i], &ops[i + 1], &ops[i + 2])
803                {
804                    result.push(Opcode::Push(a.wrapping_mul(*b)));
805                    i += 3;
806                    continue;
807                }
808            }
809            if i + 1 < ops.len() {
810                if let (Opcode::Dup, Opcode::Pop) = (&ops[i], &ops[i + 1]) {
811                    i += 2;
812                    continue;
813                }
814            }
815            result.push(ops[i].clone());
816            i += 1;
817        }
818        result
819    }
820}
821/// A simple compiler from common expression patterns to bytecode chunks.
822pub struct BytecodeCompiler;
823impl BytecodeCompiler {
824    /// Create a new BytecodeCompiler.
825    pub fn new() -> Self {
826        BytecodeCompiler
827    }
828    /// Compile a chunk that simply pushes a natural number and halts.
829    pub fn compile_nat(n: u64) -> BytecodeChunk {
830        let mut chunk = BytecodeChunk::new("nat");
831        chunk.push_op(Opcode::Push(n));
832        chunk.push_op(Opcode::Halt);
833        chunk
834    }
835    /// Compile a chunk that adds two naturals and halts.
836    pub fn compile_add(a: u64, b: u64) -> BytecodeChunk {
837        let mut chunk = BytecodeChunk::new("add");
838        chunk.push_op(Opcode::Push(a));
839        chunk.push_op(Opcode::Push(b));
840        chunk.push_op(Opcode::Add);
841        chunk.push_op(Opcode::Halt);
842        chunk
843    }
844    /// Compile a conditional: if `cond` then `then_val` else `else_val`.
845    pub fn compile_if(cond: bool, then_val: u64, else_val: u64) -> BytecodeChunk {
846        let mut chunk = BytecodeChunk::new("if");
847        chunk.push_op(Opcode::PushBool(cond));
848        chunk.push_op(Opcode::JumpIfNot(2));
849        chunk.push_op(Opcode::Push(then_val));
850        chunk.push_op(Opcode::Jump(1));
851        chunk.push_op(Opcode::Push(else_val));
852        chunk.push_op(Opcode::Halt);
853        chunk
854    }
855}
856/// A value on the interpreter stack.
857#[derive(Debug, Clone, PartialEq)]
858pub enum StackValue {
859    /// Natural number.
860    Nat(u64),
861    /// Signed integer.
862    Int(i64),
863    /// Boolean.
864    Bool(bool),
865    /// String.
866    Str(String),
867    /// Closure value with code and captured environment.
868    Closure {
869        code: Vec<Opcode>,
870        env: Vec<StackValue>,
871    },
872    /// Nil / unit.
873    Nil,
874}
875/// A single bytecode instruction.
876#[derive(Debug, Clone, PartialEq)]
877pub enum Opcode {
878    /// Push a natural number onto the stack.
879    Push(u64),
880    /// Push a boolean onto the stack.
881    PushBool(bool),
882    /// Push a string onto the stack.
883    PushStr(String),
884    /// Push nil (unit) onto the stack.
885    PushNil,
886    /// Discard the top of the stack.
887    Pop,
888    /// Duplicate the top of the stack.
889    Dup,
890    /// Swap the top two stack elements.
891    Swap,
892    /// Add the top two naturals.
893    Add,
894    /// Subtract (second - top).
895    Sub,
896    /// Multiply the top two naturals.
897    Mul,
898    /// Divide (second / top). Errors on division by zero.
899    Div,
900    /// Modulo (second % top). Errors on division by zero.
901    Mod,
902    /// Equality comparison; pushes Bool.
903    Eq,
904    /// Less-than comparison; pushes Bool.
905    Lt,
906    /// Less-than-or-equal comparison; pushes Bool.
907    Le,
908    /// Boolean NOT.
909    Not,
910    /// Boolean AND.
911    And,
912    /// Boolean OR.
913    Or,
914    /// Unconditional jump by a signed offset from the *next* instruction.
915    Jump(i32),
916    /// Jump if the top of the stack is Bool(true) (pops the condition).
917    JumpIf(i32),
918    /// Jump if the top of the stack is Bool(false) (pops the condition).
919    JumpIfNot(i32),
920    /// Call a function at position `u32` in the chunk (simplified model).
921    Call(u32),
922    /// Return from the current function.
923    Return,
924    /// Load local variable at index.
925    Load(u32),
926    /// Store top-of-stack into local variable at index (does not pop).
927    Store(u32),
928    /// Load a named global value.
929    LoadGlobal(String),
930    /// Create a closure over `u32` captured values from the stack.
931    MakeClosure(u32),
932    /// Apply a closure on the stack to the argument below it.
933    Apply,
934    /// Stop execution.
935    Halt,
936}
937/// A simple stack-based bytecode interpreter.
938pub struct Interpreter {
939    pub stack: Vec<StackValue>,
940    pub locals: Vec<StackValue>,
941    pub ip: usize,
942    pub call_stack: Vec<usize>,
943}
944impl Interpreter {
945    /// Create a new empty interpreter.
946    pub fn new() -> Self {
947        Interpreter {
948            stack: Vec::new(),
949            locals: Vec::with_capacity(64),
950            ip: 0,
951            call_stack: Vec::new(),
952        }
953    }
954    /// Push a value onto the operand stack.
955    pub fn push(&mut self, v: StackValue) {
956        self.stack.push(v);
957    }
958    /// Pop a value from the operand stack.
959    pub fn pop(&mut self) -> Option<StackValue> {
960        self.stack.pop()
961    }
962    /// Peek at the top of the operand stack without removing it.
963    pub fn peek(&self) -> Option<&StackValue> {
964        self.stack.last()
965    }
966    /// Execute all opcodes in `chunk` and return the top-of-stack value.
967    pub fn execute_chunk(&mut self, chunk: &BytecodeChunk) -> Result<StackValue, String> {
968        self.ip = 0;
969        loop {
970            if self.ip >= chunk.opcodes.len() {
971                break;
972            }
973            let op = chunk.opcodes[self.ip].clone();
974            self.ip += 1;
975            let cont = self.execute_op(&op, chunk)?;
976            if !cont {
977                break;
978            }
979        }
980        self.pop()
981            .ok_or_else(|| "stack underflow at end of chunk".to_string())
982    }
983    /// Execute a single opcode.
984    ///
985    /// Returns `Ok(true)` to continue, `Ok(false)` to halt.
986    pub fn execute_op(&mut self, op: &Opcode, _chunk: &BytecodeChunk) -> Result<bool, String> {
987        match op {
988            Opcode::Push(n) => {
989                self.stack.push(StackValue::Nat(*n));
990            }
991            Opcode::PushBool(b) => {
992                self.stack.push(StackValue::Bool(*b));
993            }
994            Opcode::PushStr(s) => {
995                self.stack.push(StackValue::Str(s.clone()));
996            }
997            Opcode::PushNil => {
998                self.stack.push(StackValue::Nil);
999            }
1000            Opcode::Pop => {
1001                self.stack.pop();
1002            }
1003            Opcode::Dup => {
1004                let top = self.stack.last().ok_or("Dup: stack underflow")?.clone();
1005                self.stack.push(top);
1006            }
1007            Opcode::Swap => {
1008                let len = self.stack.len();
1009                if len < 2 {
1010                    return Err("Swap: stack underflow".to_string());
1011                }
1012                self.stack.swap(len - 1, len - 2);
1013            }
1014            Opcode::Add => {
1015                let b = self.pop_nat("Add")?;
1016                let a = self.pop_nat("Add")?;
1017                self.stack.push(StackValue::Nat(a.wrapping_add(b)));
1018            }
1019            Opcode::Sub => {
1020                let b = self.pop_nat("Sub")?;
1021                let a = self.pop_nat("Sub")?;
1022                self.stack.push(StackValue::Nat(a.wrapping_sub(b)));
1023            }
1024            Opcode::Mul => {
1025                let b = self.pop_nat("Mul")?;
1026                let a = self.pop_nat("Mul")?;
1027                self.stack.push(StackValue::Nat(a.wrapping_mul(b)));
1028            }
1029            Opcode::Div => {
1030                let b = self.pop_nat("Div")?;
1031                let a = self.pop_nat("Div")?;
1032                if b == 0 {
1033                    return Err("division by zero".to_string());
1034                }
1035                self.stack.push(StackValue::Nat(a / b));
1036            }
1037            Opcode::Mod => {
1038                let b = self.pop_nat("Mod")?;
1039                let a = self.pop_nat("Mod")?;
1040                if b == 0 {
1041                    return Err("modulo by zero".to_string());
1042                }
1043                self.stack.push(StackValue::Nat(a % b));
1044            }
1045            Opcode::Eq => {
1046                let b = self.pop().ok_or("Eq: stack underflow")?;
1047                let a = self.pop().ok_or("Eq: stack underflow")?;
1048                self.stack.push(StackValue::Bool(a == b));
1049            }
1050            Opcode::Lt => {
1051                let b = self.pop_nat("Lt")?;
1052                let a = self.pop_nat("Lt")?;
1053                self.stack.push(StackValue::Bool(a < b));
1054            }
1055            Opcode::Le => {
1056                let b = self.pop_nat("Le")?;
1057                let a = self.pop_nat("Le")?;
1058                self.stack.push(StackValue::Bool(a <= b));
1059            }
1060            Opcode::Not => {
1061                let v = self.pop_bool("Not")?;
1062                self.stack.push(StackValue::Bool(!v));
1063            }
1064            Opcode::And => {
1065                let b = self.pop_bool("And")?;
1066                let a = self.pop_bool("And")?;
1067                self.stack.push(StackValue::Bool(a && b));
1068            }
1069            Opcode::Or => {
1070                let b = self.pop_bool("Or")?;
1071                let a = self.pop_bool("Or")?;
1072                self.stack.push(StackValue::Bool(a || b));
1073            }
1074            Opcode::Jump(offset) => {
1075                let new_ip = self.ip as i64 + *offset as i64;
1076                if new_ip < 0 {
1077                    return Err(format!("Jump: negative IP {}", new_ip));
1078                }
1079                self.ip = new_ip as usize;
1080            }
1081            Opcode::JumpIf(offset) => {
1082                let cond = self.pop_bool("JumpIf")?;
1083                if cond {
1084                    let new_ip = self.ip as i64 + *offset as i64;
1085                    if new_ip < 0 {
1086                        return Err(format!("JumpIf: negative IP {}", new_ip));
1087                    }
1088                    self.ip = new_ip as usize;
1089                }
1090            }
1091            Opcode::JumpIfNot(offset) => {
1092                let cond = self.pop_bool("JumpIfNot")?;
1093                if !cond {
1094                    let new_ip = self.ip as i64 + *offset as i64;
1095                    if new_ip < 0 {
1096                        return Err(format!("JumpIfNot: negative IP {}", new_ip));
1097                    }
1098                    self.ip = new_ip as usize;
1099                }
1100            }
1101            Opcode::Call(pos) => {
1102                self.call_stack.push(self.ip);
1103                self.ip = *pos as usize;
1104            }
1105            Opcode::Return => {
1106                if let Some(ret_addr) = self.call_stack.pop() {
1107                    self.ip = ret_addr;
1108                } else {
1109                    return Ok(false);
1110                }
1111            }
1112            Opcode::Load(idx) => {
1113                let idx = *idx as usize;
1114                let v = self
1115                    .locals
1116                    .get(idx)
1117                    .ok_or_else(|| format!("Load: local {} not found", idx))?
1118                    .clone();
1119                self.stack.push(v);
1120            }
1121            Opcode::Store(idx) => {
1122                let idx = *idx as usize;
1123                let v = self.stack.last().ok_or("Store: stack underflow")?.clone();
1124                while self.locals.len() <= idx {
1125                    self.locals.push(StackValue::Nil);
1126                }
1127                self.locals[idx] = v;
1128            }
1129            Opcode::LoadGlobal(name) => {
1130                self.stack
1131                    .push(StackValue::Str(format!("<global:{}>", name)));
1132            }
1133            Opcode::MakeClosure(n_captured) => {
1134                let n = *n_captured as usize;
1135                if self.stack.len() < n {
1136                    return Err(format!(
1137                        "MakeClosure: need {} captures, have {}",
1138                        n,
1139                        self.stack.len()
1140                    ));
1141                }
1142                let mut env = Vec::with_capacity(n);
1143                for _ in 0..n {
1144                    env.push(self.pop().expect(
1145                        "stack has at least n elements as verified by the length check above",
1146                    ));
1147                }
1148                env.reverse();
1149                self.stack.push(StackValue::Closure {
1150                    code: Vec::new(),
1151                    env,
1152                });
1153            }
1154            Opcode::Apply => {
1155                let _arg = self.pop().ok_or("Apply: stack underflow (arg)")?;
1156                let _closure = self.pop().ok_or("Apply: stack underflow (closure)")?;
1157                self.stack.push(StackValue::Nil);
1158            }
1159            Opcode::Halt => {
1160                return Ok(false);
1161            }
1162        }
1163        Ok(true)
1164    }
1165    fn pop_nat(&mut self, ctx: &str) -> Result<u64, String> {
1166        match self.pop() {
1167            Some(StackValue::Nat(n)) => Ok(n),
1168            Some(other) => Err(format!("{}: expected Nat, got {:?}", ctx, other)),
1169            None => Err(format!("{}: stack underflow", ctx)),
1170        }
1171    }
1172    fn pop_bool(&mut self, ctx: &str) -> Result<bool, String> {
1173        match self.pop() {
1174            Some(StackValue::Bool(b)) => Ok(b),
1175            Some(other) => Err(format!("{}: expected Bool, got {:?}", ctx, other)),
1176            None => Err(format!("{}: stack underflow", ctx)),
1177        }
1178    }
1179}
1180/// Compact binary encoding of a single opcode.
1181///
1182/// Format (variable-width):
1183/// - 1 byte: opcode tag
1184/// - optional operand bytes depending on the opcode
1185#[derive(Clone, Debug, PartialEq, Eq)]
1186pub struct EncodedInstruction {
1187    /// Raw bytes of the encoded instruction.
1188    pub bytes: Vec<u8>,
1189}
1190impl EncodedInstruction {
1191    /// Encode a single [`Opcode`] to bytes.
1192    pub fn encode(op: &Opcode) -> Self {
1193        let mut bytes = Vec::new();
1194        match op {
1195            Opcode::Push(n) => {
1196                bytes.push(0x01);
1197                bytes.extend_from_slice(&n.to_le_bytes());
1198            }
1199            Opcode::PushBool(b) => {
1200                bytes.push(0x02);
1201                bytes.push(*b as u8);
1202            }
1203            Opcode::PushStr(s) => {
1204                bytes.push(0x03);
1205                let len = s.len() as u32;
1206                bytes.extend_from_slice(&len.to_le_bytes());
1207                bytes.extend_from_slice(s.as_bytes());
1208            }
1209            Opcode::PushNil => bytes.push(0x04),
1210            Opcode::Pop => bytes.push(0x10),
1211            Opcode::Dup => bytes.push(0x11),
1212            Opcode::Swap => bytes.push(0x12),
1213            Opcode::Add => bytes.push(0x20),
1214            Opcode::Sub => bytes.push(0x21),
1215            Opcode::Mul => bytes.push(0x22),
1216            Opcode::Div => bytes.push(0x23),
1217            Opcode::Mod => bytes.push(0x24),
1218            Opcode::Eq => bytes.push(0x30),
1219            Opcode::Lt => bytes.push(0x31),
1220            Opcode::Le => bytes.push(0x32),
1221            Opcode::Not => bytes.push(0x40),
1222            Opcode::And => bytes.push(0x41),
1223            Opcode::Or => bytes.push(0x42),
1224            Opcode::Jump(off) => {
1225                bytes.push(0x50);
1226                bytes.extend_from_slice(&off.to_le_bytes());
1227            }
1228            Opcode::JumpIf(off) => {
1229                bytes.push(0x51);
1230                bytes.extend_from_slice(&off.to_le_bytes());
1231            }
1232            Opcode::JumpIfNot(off) => {
1233                bytes.push(0x52);
1234                bytes.extend_from_slice(&off.to_le_bytes());
1235            }
1236            Opcode::Call(pos) => {
1237                bytes.push(0x60);
1238                bytes.extend_from_slice(&pos.to_le_bytes());
1239            }
1240            Opcode::Return => bytes.push(0x61),
1241            Opcode::Load(idx) => {
1242                bytes.push(0x70);
1243                bytes.extend_from_slice(&idx.to_le_bytes());
1244            }
1245            Opcode::Store(idx) => {
1246                bytes.push(0x71);
1247                bytes.extend_from_slice(&idx.to_le_bytes());
1248            }
1249            Opcode::LoadGlobal(name) => {
1250                bytes.push(0x72);
1251                let len = name.len() as u32;
1252                bytes.extend_from_slice(&len.to_le_bytes());
1253                bytes.extend_from_slice(name.as_bytes());
1254            }
1255            Opcode::MakeClosure(n) => {
1256                bytes.push(0x80);
1257                bytes.extend_from_slice(&n.to_le_bytes());
1258            }
1259            Opcode::Apply => bytes.push(0x81),
1260            Opcode::Halt => bytes.push(0xFF),
1261        }
1262        EncodedInstruction { bytes }
1263    }
1264    /// Attempt to decode the first instruction from `data`.
1265    /// Returns `(opcode, bytes_consumed)` or `None` if data is malformed.
1266    pub fn decode(data: &[u8]) -> Option<(Opcode, usize)> {
1267        if data.is_empty() {
1268            return None;
1269        }
1270        let tag = data[0];
1271        match tag {
1272            0x01 => {
1273                if data.len() < 9 {
1274                    return None;
1275                }
1276                let n = u64::from_le_bytes(data[1..9].try_into().ok()?);
1277                Some((Opcode::Push(n), 9))
1278            }
1279            0x02 => {
1280                if data.len() < 2 {
1281                    return None;
1282                }
1283                Some((Opcode::PushBool(data[1] != 0), 2))
1284            }
1285            0x03 => {
1286                if data.len() < 5 {
1287                    return None;
1288                }
1289                let len = u32::from_le_bytes(data[1..5].try_into().ok()?) as usize;
1290                if data.len() < 5 + len {
1291                    return None;
1292                }
1293                let s = std::str::from_utf8(&data[5..5 + len]).ok()?.to_string();
1294                Some((Opcode::PushStr(s), 5 + len))
1295            }
1296            0x04 => Some((Opcode::PushNil, 1)),
1297            0x10 => Some((Opcode::Pop, 1)),
1298            0x11 => Some((Opcode::Dup, 1)),
1299            0x12 => Some((Opcode::Swap, 1)),
1300            0x20 => Some((Opcode::Add, 1)),
1301            0x21 => Some((Opcode::Sub, 1)),
1302            0x22 => Some((Opcode::Mul, 1)),
1303            0x23 => Some((Opcode::Div, 1)),
1304            0x24 => Some((Opcode::Mod, 1)),
1305            0x30 => Some((Opcode::Eq, 1)),
1306            0x31 => Some((Opcode::Lt, 1)),
1307            0x32 => Some((Opcode::Le, 1)),
1308            0x40 => Some((Opcode::Not, 1)),
1309            0x41 => Some((Opcode::And, 1)),
1310            0x42 => Some((Opcode::Or, 1)),
1311            0x50 => {
1312                if data.len() < 5 {
1313                    return None;
1314                }
1315                let off = i32::from_le_bytes(data[1..5].try_into().ok()?);
1316                Some((Opcode::Jump(off), 5))
1317            }
1318            0x51 => {
1319                if data.len() < 5 {
1320                    return None;
1321                }
1322                let off = i32::from_le_bytes(data[1..5].try_into().ok()?);
1323                Some((Opcode::JumpIf(off), 5))
1324            }
1325            0x52 => {
1326                if data.len() < 5 {
1327                    return None;
1328                }
1329                let off = i32::from_le_bytes(data[1..5].try_into().ok()?);
1330                Some((Opcode::JumpIfNot(off), 5))
1331            }
1332            0x60 => {
1333                if data.len() < 5 {
1334                    return None;
1335                }
1336                let pos = u32::from_le_bytes(data[1..5].try_into().ok()?);
1337                Some((Opcode::Call(pos), 5))
1338            }
1339            0x61 => Some((Opcode::Return, 1)),
1340            0x70 => {
1341                if data.len() < 5 {
1342                    return None;
1343                }
1344                let idx = u32::from_le_bytes(data[1..5].try_into().ok()?);
1345                Some((Opcode::Load(idx), 5))
1346            }
1347            0x71 => {
1348                if data.len() < 5 {
1349                    return None;
1350                }
1351                let idx = u32::from_le_bytes(data[1..5].try_into().ok()?);
1352                Some((Opcode::Store(idx), 5))
1353            }
1354            0x72 => {
1355                if data.len() < 5 {
1356                    return None;
1357                }
1358                let len = u32::from_le_bytes(data[1..5].try_into().ok()?) as usize;
1359                if data.len() < 5 + len {
1360                    return None;
1361                }
1362                let s = std::str::from_utf8(&data[5..5 + len]).ok()?.to_string();
1363                Some((Opcode::LoadGlobal(s), 5 + len))
1364            }
1365            0x80 => {
1366                if data.len() < 5 {
1367                    return None;
1368                }
1369                let n = u32::from_le_bytes(data[1..5].try_into().ok()?);
1370                Some((Opcode::MakeClosure(n), 5))
1371            }
1372            0x81 => Some((Opcode::Apply, 1)),
1373            0xFF => Some((Opcode::Halt, 1)),
1374            _ => None,
1375        }
1376    }
1377}