Skip to main content

dazzle_core/scheme/
instruction.rs

1//! Bytecode instructions for the Scheme VM
2//!
3//! This module implements OpenJade's instruction-based execution model.
4//! Instead of interpreting the AST directly (tree-walking), we compile
5//! expressions to bytecode instructions once and execute them with a
6//! simple stack-based VM.
7//!
8//! ## OpenJade Correspondence
9//!
10//! | Dazzle              | OpenJade                    |
11//! |---------------------|-----------------------------|
12//! | `Instruction` enum  | `Insn` class hierarchy      |
13//! | `compile()`         | `Expression::compile()`     |
14//! | `VM::run()`         | `VM::eval()`                |
15//! | instruction index   | `InsnPtr` (cached pointer)  |
16//!
17//! ## Key Optimization
18//!
19//! OpenJade caches compiled instructions in Identifier::insn_:
20//! ```cpp
21//! class Identifier {
22//!     Owner<Expression> def_;   // Parsed AST
23//!     InsnPtr insn_;            // Compiled instructions (cached!)
24//! };
25//! ```
26//!
27//! We cache instruction start index in lambdas and construction rules.
28
29use crate::scheme::arena::ValueId;
30
31/// Bytecode instruction
32///
33/// Each instruction is a simple operation that manipulates the value stack.
34/// Instructions are executed sequentially in a tight loop (no recursion).
35#[derive(Debug, Clone)]
36pub enum Instruction {
37    /// Push a constant value onto the stack
38    Constant { value_id: ValueId },
39
40    /// Look up a variable in the environment and push it
41    Variable { depth: usize, offset: usize },
42
43    /// Look up a global variable by name (for dynamic lookups)
44    GlobalVariable { name: String },
45
46    /// Pop n values from stack, pop a procedure, apply it, push result
47    Apply { n_args: usize },
48
49    /// Pop a value, if false jump to else_ip, otherwise continue
50    Test { else_ip: usize },
51
52    /// Jump unconditionally
53    Jump { target_ip: usize },
54
55    /// Pop n values, create a closure with them as free variables
56    MakeClosure {
57        params: Vec<String>,
58        required_count: usize,
59        body_ip: usize,
60        n_free: usize,
61    },
62
63    /// Return the top of stack
64    Return,
65
66    /// Create a cons cell from top two stack values (car, cdr)
67    Cons,
68
69    /// Get car of top stack value
70    Car,
71
72    /// Get cdr of top stack value
73    Cdr,
74
75    /// Test if top stack value is null
76    IsNull,
77
78    /// Add two numbers
79    Add,
80
81    /// Subtract two numbers
82    Subtract,
83
84    /// Multiply two numbers
85    Multiply,
86
87    /// Divide two numbers
88    Divide,
89
90    /// Compare two values for equality
91    Equal,
92
93    /// Numeric less-than
94    NumLt,
95
96    /// Numeric greater-than
97    NumGt,
98
99    /// Create a list from top n stack values
100    MakeList { n: usize },
101
102    /// Pop top of stack (discard result)
103    Pop,
104
105    /// Duplicate top of stack
106    Dup,
107
108    /// Set lexical variable (pop value, set variable, push value back)
109    SetVariable { depth: usize, offset: usize },
110
111    /// Set global variable (pop value, set global, push value back)
112    SetGlobalVariable { name: String },
113
114    /// Define global variable (pop value, define global, push unspecified)
115    DefineGlobal { name: String },
116}
117
118/// Compiled bytecode program
119///
120/// Just a sequence of instructions. The Arena is managed separately
121/// and shared between compiler, VM, and program.
122pub struct Program {
123    /// Sequential instruction stream
124    pub instructions: Vec<Instruction>,
125}
126
127impl Program {
128    pub fn new() -> Self {
129        Program {
130            instructions: Vec::new(),
131        }
132    }
133
134    /// Emit an instruction and return its index
135    pub fn emit(&mut self, insn: Instruction) -> usize {
136        let ip = self.instructions.len();
137        self.instructions.push(insn);
138        ip
139    }
140
141    /// Patch a jump instruction at the given index
142    pub fn patch_jump(&mut self, jump_ip: usize, target_ip: usize) {
143        match &mut self.instructions[jump_ip] {
144            Instruction::Jump { target_ip: ref mut t } => *t = target_ip,
145            Instruction::Test { else_ip: ref mut e } => *e = target_ip,
146            _ => panic!("Expected jump instruction at {}", jump_ip),
147        }
148    }
149}
150
151impl Default for Program {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::scheme::arena::NIL_ID;
161
162    #[test]
163    fn test_emit_instruction() {
164        let mut program = Program::new();
165        let ip = program.emit(Instruction::Constant {
166            value_id: NIL_ID,
167        });
168        assert_eq!(ip, 0);
169        assert_eq!(program.instructions.len(), 1);
170    }
171
172    #[test]
173    fn test_patch_jump() {
174        let mut program = Program::new();
175        let jump_ip = program.emit(Instruction::Jump { target_ip: 0 });
176        let target_ip = program.emit(Instruction::Return);
177        program.patch_jump(jump_ip, target_ip);
178
179        match program.instructions[jump_ip] {
180            Instruction::Jump { target_ip: t } => assert_eq!(t, target_ip),
181            _ => panic!("Expected jump"),
182        }
183    }
184}