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}