Skip to main content

hara_native/vm/
opcode.rs

1//! Typed instruction set for the experimental bytecode VM.
2//!
3//! Instructions are a typed enum, not packed bytes: the milestone
4//! prioritises exact validation and disassembly over encoding density.
5//! Jump operands are absolute instruction indexes.
6
7/// One VM instruction.
8///
9/// Stack effects (validated before execution):
10///
11/// - `Constant`, `Nil`, `True`, `False`, `LoadLocal`: push 1.
12/// - `StoreLocal`, `Pop`, `JumpIfFalse`: pop 1.
13/// - `IntrinsicCall`, `ProtocolCall`: pop `argc`, push 1 (net `1 - argc`).
14/// - `Closure`: pops `captures`, pushes 1 (net `1 - captures`).
15/// - `Call`: pops `argc` arguments plus the callee, pushes 1 (net `-argc`).
16/// - `CallStatic`: pops `argc`, pushes 1 (net `1 - argc`).
17/// - `Jump`: no change.
18/// - `Throw`, `Rethrow`: pop 1; terminal (unwind).
19/// - `GetGlobal`, `VarGlobal`, `DeclareGlobal`: push 1.
20/// - `DefGlobal`, `SetGlobal`, `MutableFieldGet`: pop 1, push 1 (net 0).
21/// - `MutableFieldSet`: pops receiver and replacement, pushes replacement (net -1).
22/// - `InstanceOf`: pops 2, pushes 1 (net -1).
23/// - `MakeMultiArity`: pops `count`, pushes 1 (net `1 - count`).
24/// - `Return`: terminal; requires stack height exactly 1.
25#[derive(Debug, Clone, PartialEq)]
26pub enum Instruction {
27    /// Pushes `constants[index]` onto the operand stack.
28    Constant(u32),
29    /// Pushes `Value::Nil`.
30    Nil,
31    /// Pushes `Value::Bool(true)`.
32    True,
33    /// Pushes `Value::Bool(false)`.
34    False,
35    /// Pushes the value of local slot `slot`.
36    LoadLocal(u16),
37    /// Pops the top of the stack into local slot `slot`.
38    StoreLocal(u16),
39    /// Discards the top of the stack.
40    Pop,
41    /// Duplicates the top stack value.
42    Dup,
43    /// Pops `argc` arguments and invokes the named runtime intrinsic.
44    IntrinsicCall {
45        target: u32,
46        argc: u8,
47    },
48    /// Unconditional jump to an absolute instruction index.
49    Jump(u32),
50    /// Pops the condition and jumps when it is not truthy
51    /// (`Value::truthy`: only `nil` and `false` are false).
52    JumpIfFalse(u32),
53    /// Pops `captures` captured values and pushes a function value for
54    /// `prototype`.
55    Closure {
56        prototype: u16,
57        captures: u8,
58    },
59    /// Pops `argc` arguments and then the callee, invokes the function
60    /// value through the shared `call_function` boundary, and pushes the
61    /// result.
62    Call {
63        argc: u8,
64    },
65    /// Pops `argc` arguments and calls `prototype` directly, copying the
66    /// current frame's capture slots as the callee's captures (`defn`
67    /// self-recursion).
68    CallStatic {
69        prototype: u16,
70        argc: u8,
71    },
72    /// Pops one value and raises it as a guest exception through the
73    /// shared `core::thrown_error` boundary. Terminal: unwinds to the
74    /// innermost covering try entry or fails the run.
75    Throw,
76    /// Pops a string and raises that exact message without touching the
77    /// thrown-value side channel, preserving error identity across an
78    /// unmatched finally boundary. Terminal; only emitted in finally
79    /// resume sequences.
80    Rethrow,
81    /// Pushes the dereferenced value of the var named by the string
82    /// constant at `constants[index]`, resolved through the namespace
83    /// registry at execution time.
84    GetGlobal(u32),
85    /// Pops a value, interns it as a `Var` in the current namespace
86    /// (optional hara metadata from the program's var-metadata table),
87    /// and pushes the interned Var back (`def` returns the Var).
88    DefGlobal {
89        name: u32,
90        metadata: Option<u16>,
91    },
92    /// Pops a value, resets the root of the var named by the string
93    /// constant at `constants[index]`, and pushes the value.
94    SetGlobal(u32),
95    /// Pushes the `Value::Var` named by the string constant at
96    /// `constants[index]` (`var` / `#'`).
97    VarGlobal(u32),
98    /// Interns a nil var for the string constant at `constants[index]`
99    /// when the name is not already bound (`declare`), pushing nil.
100    /// Never resets an existing var.
101    DeclareGlobal(u32),
102    /// Pops a mutable instance and pushes the declared field value named by
103    /// the string constant at `constants[index]` (`field`).
104    MutableFieldGet(u32),
105    /// Pops the replacement and mutable receiver, performs one direct field
106    /// mutation, and pushes the replacement value (`set!` field place).
107    MutableFieldSet(u32),
108    /// Pops the value and then a named type, and pushes whether the
109    /// value is an instance (`instance?`).
110    InstanceOf,
111    /// Pops `count` function values and pushes the arity dispatcher
112    /// named by the string constant at `constants[name]`, built through
113    /// the shared `core::arity_dispatcher` boundary.
114    MakeMultiArity {
115        name: u32,
116        count: u8,
117    },
118    /// Pops `count` values and constructs a compact tuple, upgrading to a vector above arity 8.
119    BuildVector(u16),
120    /// Pops `pairs * 2` alternating keys and values and constructs an
121    /// persistent hash map.
122    BuildMap(u16),
123    /// Pops `count` values and constructs an insertion-ordered set.
124    BuildSet(u16),
125    BuildList(u16),
126    ConcatList(u16),
127    ToVector,
128    /// Pops a function value, interns it as a macro Var, registers it in the
129    /// active Runtime macro registry, and pushes the function back.
130    DefMacro {
131        name: u32,
132        metadata: Option<u16>,
133    },
134    /// Pushes a first-class callable for a runtime intrinsic.
135    IntrinsicValue(u32),
136    /// Pushes a first-class callable implemented by the structural runtime.
137    BuiltinValue(u32),
138    /// Pushes a namespace value resolved from the current namespace's alias
139    /// table (or from the registry by name), matching the evaluator's bare
140    /// namespace alias form.
141    NamespaceValue(u32),
142    /// Applies a validated `ns`, `ns+`, or `require` form retained in the
143    /// constant pool and pushes its result. This is the bytecode management
144    /// seam used for nested namespace operations; top-level declarations are
145    /// prepared by the runtime before ordinary code is compiled.
146    NamespaceOperation(u32),
147    /// Binds a dynamic Var to the value on top of the stack and leaves nil.
148    DynamicBind(u32),
149    /// Removes the most recent binding for a dynamic Var and leaves nil.
150    DynamicUnbind(u32),
151    /// Replaces a settled promise with its value, raises a rejection, or
152    /// suspends the current VM fiber while preserving the complete machine.
153    Await,
154    /// Suspends the current bytecode coroutine with the value on top of
155    /// the stack. Resumption replaces it with the caller-supplied value.
156    Yield,
157    /// Pops service, method, and argument-vector values and returns the
158    /// provider's ordinary Promise value.
159    HostCall,
160    /// Invokes a native collection method with an evaluated receiver and arguments.
161    DotCall {
162        method: u32,
163        argc: u8,
164    },
165    /// Pops `argc` protocol arguments, including the receiver, and dispatches
166    /// the canonical protocol method through the active protocol registry.
167    ProtocolCall {
168        target: u32,
169        argc: u8,
170    },
171    /// Returns the top of the stack as the function result.
172    Return,
173}
174
175impl Instruction {
176    /// The jump target, when the instruction transfers control.
177    pub fn jump_target(&self) -> Option<u32> {
178        match self {
179            Instruction::Jump(target) | Instruction::JumpIfFalse(target) => Some(*target),
180            _ => None,
181        }
182    }
183
184    /// Whether control falls through to the next instruction.
185    pub(crate) fn falls_through(&self) -> bool {
186        !matches!(
187            self,
188            Instruction::Jump(_) | Instruction::Return | Instruction::Throw | Instruction::Rethrow
189        )
190    }
191
192    /// Static stack effect of the instruction; `None` for the terminals
193    /// (`Return` requires height exactly 1, `Throw`/`Rethrow` pop 1), which
194    /// are validated separately.
195    pub(crate) fn stack_effect(&self) -> Option<i32> {
196        Some(match self {
197            Instruction::Constant(_)
198            | Instruction::Nil
199            | Instruction::True
200            | Instruction::False
201            | Instruction::LoadLocal(_) => 1,
202            Instruction::StoreLocal(_) | Instruction::Pop | Instruction::JumpIfFalse(_) => -1,
203            Instruction::Dup => 1,
204            Instruction::IntrinsicCall { argc, .. }
205            | Instruction::ProtocolCall { argc, .. }
206            | Instruction::CallStatic { argc, .. } => 1 - i32::from(*argc),
207            Instruction::Closure { captures, .. } => 1 - i32::from(*captures),
208            Instruction::Call { argc } => -i32::from(*argc),
209            Instruction::GetGlobal(_)
210            | Instruction::VarGlobal(_)
211            | Instruction::DeclareGlobal(_) => 1,
212            Instruction::IntrinsicValue(_) => 1,
213            Instruction::BuiltinValue(_) => 1,
214            Instruction::NamespaceValue(_) | Instruction::NamespaceOperation(_) => 1,
215            Instruction::DynamicBind(_) => 0,
216            Instruction::DynamicUnbind(_) => 1,
217            Instruction::Yield => 0,
218            Instruction::DefGlobal { .. }
219            | Instruction::SetGlobal(_)
220            | Instruction::MutableFieldGet(_) => 0,
221            Instruction::MutableFieldSet(_) => -1,
222            Instruction::InstanceOf => -1,
223            Instruction::MakeMultiArity { count, .. } => 1 - i32::from(*count),
224            Instruction::BuildVector(count)
225            | Instruction::BuildSet(count)
226            | Instruction::BuildList(count)
227            | Instruction::ConcatList(count) => 1 - i32::from(*count),
228            Instruction::BuildMap(pairs) => 1 - (2 * i32::from(*pairs)),
229            Instruction::ToVector => 0,
230            Instruction::DefMacro { .. } => 0,
231            Instruction::Await => 0,
232            Instruction::HostCall => -2,
233            Instruction::DotCall { argc, .. } => -i32::from(*argc),
234            Instruction::Jump(_) => 0,
235            Instruction::Return | Instruction::Throw | Instruction::Rethrow => return None,
236        })
237    }
238}
239
240impl std::fmt::Display for Instruction {
241    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        match self {
243            Instruction::Constant(index) => write!(formatter, "Constant {index}"),
244            Instruction::Nil => formatter.write_str("Nil"),
245            Instruction::True => formatter.write_str("True"),
246            Instruction::False => formatter.write_str("False"),
247            Instruction::LoadLocal(slot) => write!(formatter, "LoadLocal {slot}"),
248            Instruction::StoreLocal(slot) => write!(formatter, "StoreLocal {slot}"),
249            Instruction::Pop => formatter.write_str("Pop"),
250            Instruction::Dup => formatter.write_str("Dup"),
251            Instruction::IntrinsicCall { target, argc } => {
252                write!(formatter, "IntrinsicCall target {target} argc {argc}")
253            }
254            Instruction::Jump(target) => write!(formatter, "Jump {target:04}"),
255            Instruction::JumpIfFalse(target) => write!(formatter, "JumpIfFalse {target:04}"),
256            Instruction::Closure {
257                prototype,
258                captures,
259            } => {
260                write!(formatter, "Closure {prototype:04} captures {captures}")
261            }
262            Instruction::Call { argc } => write!(formatter, "Call {argc}"),
263            Instruction::CallStatic { prototype, argc } => {
264                write!(formatter, "CallStatic {prototype:04} {argc}")
265            }
266            Instruction::Throw => formatter.write_str("Throw"),
267            Instruction::Rethrow => formatter.write_str("Rethrow"),
268            Instruction::GetGlobal(index) => write!(formatter, "GetGlobal {index}"),
269            Instruction::DefGlobal { name, metadata } => match metadata {
270                Some(metadata) => write!(formatter, "DefGlobal {name} meta {metadata}"),
271                None => write!(formatter, "DefGlobal {name}"),
272            },
273            Instruction::SetGlobal(index) => write!(formatter, "SetGlobal {index}"),
274            Instruction::VarGlobal(index) => write!(formatter, "VarGlobal {index}"),
275            Instruction::DeclareGlobal(index) => write!(formatter, "DeclareGlobal {index}"),
276            Instruction::MutableFieldGet(index) => {
277                write!(formatter, "MutableFieldGet {index}")
278            }
279            Instruction::MutableFieldSet(index) => {
280                write!(formatter, "MutableFieldSet {index}")
281            }
282            Instruction::InstanceOf => formatter.write_str("InstanceOf"),
283            Instruction::MakeMultiArity { name, count } => {
284                write!(formatter, "MakeMultiArity {name} count {count}")
285            }
286            Instruction::BuildVector(count) => write!(formatter, "BuildVector {count}"),
287            Instruction::BuildMap(count) => write!(formatter, "BuildMap {count}"),
288            Instruction::BuildSet(count) => write!(formatter, "BuildSet {count}"),
289            Instruction::BuildList(count) => write!(formatter, "BuildList {count}"),
290            Instruction::ConcatList(count) => write!(formatter, "ConcatList {count}"),
291            Instruction::ToVector => formatter.write_str("ToVector"),
292            Instruction::DefMacro { name, metadata } => match metadata {
293                Some(metadata) => write!(formatter, "DefMacro {name} meta {metadata}"),
294                None => write!(formatter, "DefMacro {name}"),
295            },
296            Instruction::IntrinsicValue(target) => {
297                write!(formatter, "IntrinsicValue target {target}")
298            }
299            Instruction::BuiltinValue(index) => write!(formatter, "BuiltinValue {index}"),
300            Instruction::NamespaceValue(index) => write!(formatter, "NamespaceValue {index}"),
301            Instruction::NamespaceOperation(index) => {
302                write!(formatter, "NamespaceOperation {index}")
303            }
304            Instruction::DynamicBind(index) => write!(formatter, "DynamicBind {index}"),
305            Instruction::DynamicUnbind(index) => write!(formatter, "DynamicUnbind {index}"),
306            Instruction::Await => formatter.write_str("Await"),
307            Instruction::Yield => formatter.write_str("Yield"),
308            Instruction::HostCall => formatter.write_str("HostCall"),
309            Instruction::DotCall { method, argc } => {
310                write!(formatter, "DotCall {method} {argc}")
311            }
312            Instruction::ProtocolCall { target, argc } => {
313                write!(formatter, "ProtocolCall target {target} argc {argc}")
314            }
315            Instruction::Return => formatter.write_str("Return"),
316        }
317    }
318}