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}