candela_vm/instr.rs
1// This file is derived from keel (https://github.com/horacehoff/keel),
2// Copyright 2026 Horace Hoff, licensed under the Apache License, Version 2.0.
3// It has been modified by the candela authors. See the NOTICE file.
4use serde::Deserialize;
5use serde::Serialize;
6
7#[derive(Debug, Clone, PartialEq, Eq, Copy, Serialize, Deserialize)]
8#[repr(u8)]
9pub enum Instr {
10 Print(u16),
11
12 // LOGIC
13 /// Jumps x instructions forwards
14 Jmp(u16),
15 /// Jumps x instructions backwards
16 JmpBack(u16),
17 IsFalseJmp(u16, u16),
18 IsTrueJmp(u16, u16),
19 SupEqFloatJmp(u16, u16, u16),
20 SupEqIntJmp(u16, u16, u16),
21 SupFloatJmp(u16, u16, u16),
22 SupIntJmp(u16, u16, u16),
23 InfEqFloatJmp(u16, u16, u16),
24 InfEqIntJmp(u16, u16, u16),
25 InfFloatJmp(u16, u16, u16),
26 InfIntJmp(u16, u16, u16),
27 InfIntJmpBack(u16, u16, u16),
28 /// StepIntJmpBack(counter_reg, end_reg, size)\
29 /// Adds 1 to the `int` in counter_reg, then jumps size instructions
30 /// backwards while it is below the `int` in end_reg. The step at the
31 /// bottom of every counted loop and every loop over a collection, which
32 /// would otherwise be an `IncInt` and an `InfIntJmpBack`.
33 StepIntJmpBack(u16, u16, u16),
34 NotEqJmp(u16, u16, u16),
35 EqJmp(u16, u16, u16),
36 ObjNotEqJmp(u16, u16, u16),
37 ObjEqJmp(u16, u16, u16),
38 StrNotEqJmp(u16, u16, u16),
39 StrEqJmp(u16, u16, u16),
40
41 Mov(u16, u16),
42 /// MovChain(dest, mid, src)\
43 /// dest = mid, then mid = src: two moves where the second refills the
44 /// register the first read, the way `a = b; b = c;` rotates values.
45 MovChain(u16, u16, u16),
46 /// SetInt(dest_reg_id, val)\
47 /// Writes val directly into dest_reg_id. The operand is narrower than an
48 /// `int`, so a wider constant is moved out of its register instead, which
49 /// keeps every instruction one word.
50 SetInt(u16, i32),
51 /// SetBool(dest_reg_id, val)\
52 /// Writes val directly into dest_reg_id
53 SetBool(bool, u16),
54
55 // OPS
56 AddFloat(u16, u16, u16),
57 AddInt(u16, u16, u16),
58 /// AddIntImm(src_reg, imm, dest_reg)\
59 /// dest = src + imm, for an `int` src. What adding or subtracting a
60 /// constant that fits the operand compiles to.
61 AddIntImm(u16, i16, u16),
62 AddArray(u16, u16, u16),
63 AddStr(u16, u16, u16),
64 MulFloat(u16, u16, u16),
65 MulInt(u16, u16, u16),
66 SubFloat(u16, u16, u16),
67 SubInt(u16, u16, u16),
68 DivFloat(u16, u16, u16),
69 DivInt(u16, u16, u16),
70 ModFloat(u16, u16, u16),
71 ModInt(u16, u16, u16),
72 PowFloat(u16, u16, u16),
73 PowInt(u16, u16, u16),
74 BitAndInt(u16, u16, u16),
75 BitOrInt(u16, u16, u16),
76 BitXorInt(u16, u16, u16),
77 /// BitNotInt(operand, dest)\
78 /// dest = every bit of operand flipped
79 BitNotInt(u16, u16),
80 /// ShlInt(value, count, dest)\
81 /// Raises `shift_count_out_of_range` for a count outside 0..64.
82 ShlInt(u16, u16, u16),
83 /// ShrInt(value, count, dest)\
84 /// Arithmetic: `int` is signed, so the sign bit is what the vacated places
85 /// are filled with. Raises `shift_count_out_of_range` for a count outside
86 /// 0..64.
87 ShrInt(u16, u16, u16),
88 /// Increments the integer in-place by 1
89 IncInt(u16),
90 /// Decrements the integer in-place by 1
91 DecInt(u16),
92 /// IncIntTo(src, dst)\
93 /// dst = src + 1
94 IncIntTo(u16, u16),
95 /// DecIntTo(src, dst)\
96 /// dst = src - 1
97 DecIntTo(u16, u16),
98 Eq(u16, u16, u16),
99 NotEq(u16, u16, u16),
100 ObjEq(u16, u16, u16),
101 ObjNotEq(u16, u16, u16),
102 StrEq(u16, u16, u16),
103 StrNotEq(u16, u16, u16),
104 SupFloat(u16, u16, u16),
105 SupInt(u16, u16, u16),
106 SupEqFloat(u16, u16, u16),
107 SupEqInt(u16, u16, u16),
108 InfFloat(u16, u16, u16),
109 InfInt(u16, u16, u16),
110 InfEqFloat(u16, u16, u16),
111 InfEqInt(u16, u16, u16),
112 /// SupStr(l_reg_id, r_reg_id, dest_reg_id)\
113 /// Orders two strings by their bytes, the order `sort` puts a list of
114 /// strings in. The four string comparisons are their own instructions
115 /// rather than one carrying an ordering operand, because a fourth operand
116 /// would take an instruction past a single word.
117 SupStr(u16, u16, u16),
118 /// SupEqStr(l_reg_id, r_reg_id, dest_reg_id)
119 SupEqStr(u16, u16, u16),
120 /// InfStr(l_reg_id, r_reg_id, dest_reg_id)
121 InfStr(u16, u16, u16),
122 /// InfEqStr(l_reg_id, r_reg_id, dest_reg_id)
123 InfEqStr(u16, u16, u16),
124 BoolAnd(u16, u16, u16),
125 BoolOr(u16, u16, u16),
126 NegBool(u16, u16),
127 NegFloat(u16, u16),
128 NegInt(u16, u16),
129
130 /// CallFunc(n,y)\
131 /// Jumps to the nth instruction, and adds y as a slot to be set by the Return instruction\
132 /// VoidReturn/Return will jump back to this location
133 CallFunc(u16, u16),
134 /// CallFuncRecursive(n, register_id)\
135 /// Jumps to the nth instruction, register_id is only used during parsing.
136 CallFuncRecursive(u16, u16),
137 /// Jumps to the instruction right after the last CallFunc encountered by the interpreter
138 VoidReturn,
139 /// Return(n)\
140 /// Returns the data located in register n
141 Return(u16),
142 /// RecursiveReturn(n,function_id)\
143 /// Returns the data located in register n, and restores the function's register state
144 RecursiveReturn(u16),
145 /// SaveFrame(function_location, return_register, function_id)
146 SaveFrame(u16, u16, u16),
147
148 /// CallIndirect(callee_register_id)\
149 /// Jumps to the instruction the function value in callee_register_id names
150 /// in its first slot. The frame is pushed by the `SaveFrame` that precedes
151 /// this instruction, which also carries the register the return lands in,
152 /// so a call whose callee is only known at run time keeps the caller's
153 /// registers across it.
154 CallIndirect(u16),
155
156 /// CallDynamicLibFunc(fn_id, dest_register_id)
157 CallDynamicLibFunc(u16, u16),
158
159 /// CallHostFunc(host_fn_id, dest_register_id)
160 /// Dispatches to a Rust closure registered on the embedding `Engine`,
161 /// marshalling `StoreFuncArg` operands into the closure and its result back
162 /// into `dest_register_id`.
163 CallHostFunc(u16, u16),
164
165 StoreFuncArg(u16),
166 /// CallLibFunc(function, src_register_id, dest_register_id)
167 CallLibFunc(LibFunc, u16, u16),
168 /// CallLibFuncVoid(function, src_register_1, src_register_2)
169 /// For single-source ops, src register 2 is unused
170 CallLibFuncVoid(LibFuncVoid, u16, u16),
171
172 /// StartErrorCatch(jump_size, error_register_id)
173 StartErrorCatch(u16, u16),
174 StopErrorCatch,
175
176 /// ThrowError(error_register_id)
177 /// Throws the error (a string) located in error_register_id
178 ThrowError(u16),
179
180 /// ArrayMov(new_elem_reg_id, array_id, idx)\
181 /// Replaces the idx-th element in the array (with the id array_id) with the element located in new_elem_reg_id
182 ObjElemMov(u16, u16, u16),
183
184 /// EmptyArray(array_reg_id)
185 /// Allocates a fresh empty array and stores its address in array_reg_id
186 EmptyArray(u16),
187
188 /// EmptyFnValue(value_reg_id)\
189 /// Allocates a fresh entry like `EmptyArray` and stores a function value
190 /// naming it in value_reg_id. The pushes that follow fill it with where
191 /// the body starts and the cells the function captured; the value is a
192 /// function rather than the list it is built as, which is what makes it
193 /// read as one wherever it becomes text.
194 EmptyFnValue(u16),
195
196 /// CloneArray(src_reg, dest_reg, len)
197 /// Allocates a fresh array with exact capacity len and clones the array in src_reg to dest_reg
198 CloneArray(u16, u16, u16),
199
200 CloneStruct(u16, u16),
201
202 /// CloneEnum(template_reg, dest_reg)
203 /// Clones the enum value in `template_reg` into a fresh object-pool entry,
204 /// preserving the enum type tag. Mirrors `CloneStruct` for enum values.
205 CloneEnum(u16, u16),
206
207 /// SetElementArray(array_reg_id, new_elem_reg_id, idx)\
208 /// Replaces the idx-th element in the array located in array_reg_id with the element located in new_elem_reg_id
209 SetElementObj(u16, u16, u16),
210
211 /// SetElementString(string_reg_id, new_str_reg_id, idx)\
212 /// Replaces the idx-th char in the string located in string_reg_id with the string located in new_str_reg_id
213 SetElementString(u16, u16, u16),
214
215 /// SetFieldStruct(struct_reg_id, new_elem_reg_id, idx)
216 SetFieldStruct(u16, u16, u16),
217 /// AddFieldFloat(struct_reg_id, value_reg_id, field_idx)\
218 /// Adds the float in value_reg_id to the float field field_idx of the
219 /// struct, the whole of `s.f += x`.
220 AddFieldFloat(u16, u16, u16),
221
222 /// GetIndexArray(array_reg_id, index_reg_id, output_reg_id)
223 GetIndexArray(u16, u16, u16),
224
225 /// GetIndexString(str_reg_id, index_reg_id, output_reg_id)
226 GetIndexString(u16, u16, u16),
227
228 /// GetFieldStruct(struct_reg_id, index, output_reg_id)
229 GetFieldStruct(u16, u16, u16),
230
231 /// GetSliceArray(array_reg_id, idx_start_reg_id, output_reg_id)\
232 /// idx_end_reg_id is pulled from a StoreFuncArg
233 GetSliceArray(u16, u16, u16),
234 /// GetSliceString(str_reg_id, idx_start_reg_id, output_reg_id)\
235 /// idx_end_reg_id is pulled from a StoreFuncArg
236 GetSliceString(u16, u16, u16),
237
238 /// Push(array_reg_id, elem_reg_id)
239 Push(u16, u16),
240
241 /// Remove(array_reg_id, elem_index_reg_id)
242 Remove(u16, u16),
243
244 /// MapGet(map_pool_id, key_reg_id, dest_reg_id)
245 MapGet(u16, u16, u16),
246 /// MapSet(map_pool_id, key_reg_id, val_reg_id)
247 MapInsert(u16, u16, u16),
248 /// MapSet(map_reg_id, key_reg_id, val_reg_id)
249 MapInsertReg(u16, u16, u16),
250 /// MapRemove(map_reg_id, key_reg_id)
251 MapRemove(u16, u16),
252 CloneMap(u16, u16),
253
254 /// NewCell(src_reg_id, dest_reg_id)\
255 /// Allocates a one-slot cell holding what src_reg_id holds and puts the
256 /// cell in dest_reg_id. A variable a closure captures lives in a cell, so
257 /// the function that declared it and every closure that took it read and
258 /// write one slot.
259 NewCell(u16, u16),
260
261 /// LoadCell(cell_reg_id, dest_reg_id)\
262 /// Reads the value out of the cell in cell_reg_id.
263 LoadCell(u16, u16),
264
265 /// StoreCell(cell_reg_id, src_reg_id)\
266 /// Writes what src_reg_id holds into the cell in cell_reg_id.
267 StoreCell(u16, u16),
268
269 /// Exits the program with the i32 code if it's != 0
270 Halt(u16),
271
272 // The instructions below exist only in a loaded program, never in an
273 // artifact: loading ahead-of-time machine code rewrites the calls it
274 // covers into them. They sit last so the ones an artifact carries keep
275 // their numbers.
276 /// CallNative(function, dest_register_id)\
277 /// A `CallFunc` whose callee runs as machine code: calls native function
278 /// `function` of the loaded program and writes what it returns into
279 /// dest_register_id.
280 #[serde(skip)]
281 CallNative(u16, u16),
282 /// CallNativeRec(function, dest_register_id)\
283 /// A `CallFuncRecursive` whose callee runs as machine code. The
284 /// `SaveFrame` before it has pushed the frame the call returns through.
285 #[serde(skip)]
286 CallNativeRec(u16, u16),
287 /// Where a call machine code makes into the interpreter returns to: the
288 /// run that call started ends here.
289 #[serde(skip)]
290 NativeExit,
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
294pub enum LibFunc {
295 Uppercase = 0,
296 Lowercase = 1,
297 Contains = 2,
298 Trim = 3,
299 TrimSequence = 4,
300 Find = 5,
301 IsFloat = 6,
302 IsInt = 7,
303 TrimLeft = 8,
304 TrimRight = 9,
305 TrimSequenceLeft = 10,
306 TrimSequenceRight = 11,
307 Repeat = 12,
308 Round = 13,
309 Abs = 14,
310 Argv = 15,
311 SqrtFloat = 16,
312 Float = 17,
313 Int = 18,
314 Str = 19,
315 Bool = 20,
316 Input = 21,
317 Floor = 22,
318 TheAnswer = 23,
319 Len = 24,
320 StartsWith = 25,
321 EndsWith = 26,
322 Replace = 27,
323 Split = 28,
324 Range = 29,
325 JoinStringArray = 30,
326 Reverse = 31,
327 FsRead = 32,
328 FsExists = 33,
329 /// Map key array. Reads the map in the source register and emits a fresh
330 /// array of its keys.
331 Keys = 34,
332 /// Map value array. Reads the map in the source register and emits a fresh
333 /// array of its values.
334 Values = 35,
335 /// json::parse. Parses the source string into a value graph (maps, arrays,
336 /// scalars); a malformed string raises a catchable error.
337 JsonParse = 36,
338 /// json::stringify. Serializes the source value to a json string.
339 JsonStringify = 37,
340 // Runtime type tests on an `any` value: read the source register's tag and
341 // emit a bool. The `Val` suffix separates these from the string-parsing
342 // `IsInt`/`IsFloat` above.
343 IsIntVal = 38,
344 IsFloatVal = 39,
345 IsStrVal = 40,
346 IsBoolVal = 41,
347 IsListVal = 42,
348 IsMapVal = 43,
349 IsNullVal = 44,
350 // Checked downcasts of an `any` value to a concrete type: pass the value
351 // through when the tag matches, otherwise raise a catchable error. The
352 // compiler types the result as the target type so it can be used directly.
353 AsIntVal = 45,
354 AsFloatVal = 46,
355 AsStrVal = 47,
356 AsBoolVal = 48,
357 AsListVal = 49,
358 AsMapVal = 50,
359 /// The checked downcast for a type the ones above do not name: a struct,
360 /// an enum, a function, a union, or a list or map whose contents are
361 /// checked. The argument register holds a list of [`type_code`]s, and the
362 /// value passes when it has any of those types.
363 AsTypeVal = 51,
364 /// The type test `is` compiles to for the types [`Self::AsTypeVal`]
365 /// checks: the same list of [`type_code`]s, answered as a bool.
366 IsTypeVal = 52,
367}
368
369/// What a value is checked for by [`LibFunc::AsTypeVal`] and
370/// [`LibFunc::IsTypeVal`]: a kind, and for a struct or an enum the type's id
371/// above it.
372///
373/// An entry of the list is one of these ints, or a list for a collection
374/// whose contents are checked too: `[LIST, elements]` for a list, and
375/// `[MAP, keys, values]` for a map, where each of `elements`, `keys` and
376/// `values` is a list of entries in the same form. An empty list there checks
377/// nothing, which is what `any` in that position means.
378pub mod type_code {
379 pub const INT: i64 = 1;
380 pub const FLOAT: i64 = 2;
381 pub const STRING: i64 = 3;
382 pub const BOOL: i64 = 4;
383 pub const NULL: i64 = 5;
384 pub const LIST: i64 = 6;
385 pub const MAP: i64 = 7;
386 pub const FUNCTION: i64 = 8;
387 pub const STRUCT: i64 = 9;
388 pub const ENUM: i64 = 10;
389 /// The bits below a type id.
390 pub const KIND_BITS: u32 = 8;
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
394pub enum LibFuncVoid {
395 Reverse = 0,
396 FsWrite = 1,
397 FsAppend = 2,
398 FsDelete = 3,
399 FsDeleteDir = 4,
400 Sort = 5,
401}
402
403impl Instr {
404 /// Returns the ID of the register that will be modified by the given instruction
405 #[must_use]
406 pub fn get_tgt_id(mut self) -> Option<u16> {
407 self.tgt_id_mut().map(|reg| *reg)
408 }
409
410 /// The operand naming the register this instruction writes, to look at or
411 /// to rewrite. See [`Self::get_tgt_id`].
412 ///
413 /// `MovChain` writes a second register, the one it refills, which this
414 /// does not name; [`Self::for_each_write_reg`] names both. The compiler
415 /// joins moves into it after every pass that rewrites registers.
416 pub fn tgt_id_mut(&mut self) -> Option<&mut u16> {
417 match self {
418 // Instructions that modify no register.
419 Self::Print(_)
420 | Self::Jmp(_)
421 | Self::JmpBack(_)
422 | Self::IsFalseJmp(_, _)
423 | Self::IsTrueJmp(_, _)
424 | Self::NotEqJmp(_, _, _)
425 | Self::ObjNotEqJmp(_, _, _)
426 | Self::StrNotEqJmp(_, _, _)
427 | Self::EqJmp(_, _, _)
428 | Self::ObjEqJmp(_, _, _)
429 | Self::StrEqJmp(_, _, _)
430 | Self::SupFloatJmp(_, _, _)
431 | Self::SupIntJmp(_, _, _)
432 | Self::SupEqFloatJmp(_, _, _)
433 | Self::SupEqIntJmp(_, _, _)
434 | Self::InfEqFloatJmp(_, _, _)
435 | Self::InfEqIntJmp(_, _, _)
436 | Self::InfFloatJmp(_, _, _)
437 | Self::InfIntJmp(_, _, _)
438 | Self::InfIntJmpBack(_, _, _)
439 | Self::StoreFuncArg(_)
440 | Self::SetElementObj(_, _, _)
441 | Self::SetFieldStruct(_, _, _)
442 | Self::AddFieldFloat(_, _, _)
443 | Self::MapInsert(_, _, _)
444 | Self::MapInsertReg(_, _, _)
445 | Self::MapRemove(_, _)
446 | Self::ObjElemMov(_, _, _)
447 | Self::Push(_, _)
448 | Self::Return(_) // Modifies a register, but this function doesn't know which one
449 | Self::RecursiveReturn(_) // Modifies a register, but this function doesn't know which one
450 | Self::VoidReturn
451 | Self::Remove(_, _)
452 | Self::CallLibFuncVoid(_, _, _)
453 | Self::Halt(_)
454 | Self::StopErrorCatch
455 | Self::ThrowError(_)
456 | Self::StoreCell(_, _)
457 | Self::CallIndirect(_) // The preceding SaveFrame carries the return register
458 | Self::NativeExit
459 => None,
460
461 Self::StartErrorCatch(_, y) if *y == u16::MAX => None,
462
463 Self::Mov(_, y)
464 | Self::MovChain(y, _, _)
465 | Self::SetInt(y, _)
466 | Self::SetBool(_, y)
467 | Self::CallFunc(_, y)
468 | Self::CallFuncRecursive(_, y)
469 | Self::CallNative(_, y)
470 | Self::CallNativeRec(_, y)
471 | Self::SaveFrame(_, y, _)
472 | Self::AddFloat(_, _, y)
473 | Self::AddInt(_, _, y)
474 | Self::AddIntImm(_, _, y)
475 | Self::StepIntJmpBack(y, _, _)
476 | Self::AddArray(_, _, y)
477 | Self::AddStr(_, _, y)
478 | Self::MulFloat(_, _, y)
479 | Self::MulInt(_, _, y)
480 | Self::SubFloat(_, _, y)
481 | Self::SubInt(_, _, y)
482 | Self::DivFloat(_, _, y)
483 | Self::DivInt(_, _, y)
484 | Self::ModFloat(_, _, y)
485 | Self::ModInt(_, _, y)
486 | Self::PowFloat(_, _, y)
487 | Self::PowInt(_, _, y)
488 | Self::BitAndInt(_, _, y)
489 | Self::BitOrInt(_, _, y)
490 | Self::BitXorInt(_, _, y)
491 | Self::BitNotInt(_, y)
492 | Self::ShlInt(_, _, y)
493 | Self::ShrInt(_, _, y)
494 | Self::Eq(_, _, y)
495 | Self::ObjEq(_, _, y)
496 | Self::StrEq(_, _, y)
497 | Self::NotEq(_, _, y)
498 | Self::ObjNotEq(_, _, y)
499 | Self::StrNotEq(_, _, y)
500 | Self::SupFloat(_, _, y)
501 | Self::SupInt(_, _, y)
502 | Self::SupEqFloat(_, _, y)
503 | Self::SupEqInt(_, _, y)
504 | Self::InfFloat(_, _, y)
505 | Self::InfInt(_, _, y)
506 | Self::InfEqFloat(_, _, y)
507 | Self::InfEqInt(_, _, y)
508 | Self::SupStr(_, _, y)
509 | Self::SupEqStr(_, _, y)
510 | Self::InfStr(_, _, y)
511 | Self::InfEqStr(_, _, y)
512 | Self::BoolAnd(_, _, y)
513 | Self::BoolOr(_, _, y)
514 | Self::NegBool(_, y)
515 | Self::EmptyArray(y)
516 | Self::EmptyFnValue(y)
517 | Self::NegFloat(_, y)
518 | Self::NegInt(_, y)
519 | Self::CallLibFunc(_, _, y)
520 | Self::GetIndexArray(_, _, y)
521 | Self::GetFieldStruct(_, _, y)
522 | Self::MapGet(_, _, y)
523 | Self::GetSliceArray(_, _, y)
524 | Self::GetIndexString(_, _, y)
525 | Self::GetSliceString(_, _, y)
526 | Self::SetElementString(y, _, _)
527 | Self::CallDynamicLibFunc(_, y)
528 | Self::CallHostFunc(_, y)
529 | Self::IncInt(y)
530 | Self::DecInt(y)
531 | Self::IncIntTo(_, y)
532 | Self::DecIntTo(_, y)
533 | Self::StartErrorCatch(_,y)
534 | Self::CloneStruct(_, y)
535 | Self::CloneEnum(_, y)
536 | Self::CloneMap(_, y)
537 | Self::NewCell(_, y)
538 | Self::LoadCell(_, y)
539 | Self::CloneArray(_, y, _) => Some(y),
540 }
541 }
542
543 /// Calls `f` with each register this instruction writes: the one
544 /// [`Self::get_tgt_id`] names, and for `MovChain` the register it refills
545 /// as well.
546 pub fn for_each_write_reg(self, mut f: impl FnMut(u16)) {
547 if let Self::MovChain(_, mid, _) = self {
548 f(mid);
549 }
550 if let Some(reg) = self.get_tgt_id() {
551 f(reg);
552 }
553 }
554
555 /// Calls `f` with each register this instruction reads.
556 pub fn for_each_read_reg(mut self, mut f: impl FnMut(u16)) {
557 self.read_regs_mut(|reg| f(*reg));
558 }
559
560 /// Hands `f` each operand of this instruction that names a register it
561 /// reads, to look at or to rewrite.
562 pub fn read_regs_mut(&mut self, mut f: impl FnMut(&mut u16)) {
563 match self {
564 Self::AddFloat(a, b, _)
565 | Self::AddInt(a, b, _)
566 | Self::AddArray(a, b, _)
567 | Self::AddStr(a, b, _)
568 | Self::MulFloat(a, b, _)
569 | Self::MulInt(a, b, _)
570 | Self::SubFloat(a, b, _)
571 | Self::SubInt(a, b, _)
572 | Self::DivFloat(a, b, _)
573 | Self::DivInt(a, b, _)
574 | Self::ModFloat(a, b, _)
575 | Self::ModInt(a, b, _)
576 | Self::PowFloat(a, b, _)
577 | Self::PowInt(a, b, _)
578 | Self::BitAndInt(a, b, _)
579 | Self::BitOrInt(a, b, _)
580 | Self::BitXorInt(a, b, _)
581 | Self::ShlInt(a, b, _)
582 | Self::ShrInt(a, b, _)
583 | Self::Eq(a, b, _)
584 | Self::NotEq(a, b, _)
585 | Self::ObjEq(a, b, _)
586 | Self::ObjNotEq(a, b, _)
587 | Self::StrEq(a, b, _)
588 | Self::StrNotEq(a, b, _)
589 | Self::SupFloat(a, b, _)
590 | Self::SupInt(a, b, _)
591 | Self::SupEqFloat(a, b, _)
592 | Self::SupEqInt(a, b, _)
593 | Self::InfFloat(a, b, _)
594 | Self::InfInt(a, b, _)
595 | Self::InfEqFloat(a, b, _)
596 | Self::InfEqInt(a, b, _)
597 | Self::SupStr(a, b, _)
598 | Self::SupEqStr(a, b, _)
599 | Self::InfStr(a, b, _)
600 | Self::InfEqStr(a, b, _)
601 | Self::BoolAnd(a, b, _)
602 | Self::BoolOr(a, b, _)
603 | Self::GetIndexArray(a, b, _)
604 | Self::GetSliceArray(a, b, _)
605 | Self::GetIndexString(a, b, _)
606 | Self::GetSliceString(a, b, _)
607 | Self::NotEqJmp(a, b, _)
608 | Self::EqJmp(a, b, _)
609 | Self::ObjNotEqJmp(a, b, _)
610 | Self::ObjEqJmp(a, b, _)
611 | Self::StrNotEqJmp(a, b, _)
612 | Self::StrEqJmp(a, b, _)
613 | Self::SupFloatJmp(a, b, _)
614 | Self::SupIntJmp(a, b, _)
615 | Self::SupEqFloatJmp(a, b, _)
616 | Self::SupEqIntJmp(a, b, _)
617 | Self::InfFloatJmp(a, b, _)
618 | Self::InfIntJmp(a, b, _)
619 | Self::InfEqFloatJmp(a, b, _)
620 | Self::InfEqIntJmp(a, b, _)
621 | Self::InfIntJmpBack(a, b, _)
622 | Self::StepIntJmpBack(a, b, _)
623 | Self::Push(a, b)
624 | Self::SetFieldStruct(a, b, _)
625 | Self::AddFieldFloat(a, b, _)
626 | Self::MovChain(_, a, b)
627 | Self::MapGet(a, b, _)
628 | Self::MapInsert(_, a, b)
629 | Self::MapRemove(a, b)
630 | Self::StoreCell(a, b)
631 | Self::Remove(a, b) => {
632 f(a);
633 f(b);
634 }
635
636 Self::SetElementObj(a, b, c)
637 | Self::SetElementString(a, b, c)
638 | Self::MapInsertReg(a, b, c) => {
639 f(a);
640 f(b);
641 f(c);
642 }
643
644 Self::Mov(a, _)
645 | Self::AddIntImm(a, _, _)
646 | Self::IncInt(a)
647 | Self::DecInt(a)
648 | Self::IncIntTo(a, _)
649 | Self::DecIntTo(a, _)
650 | Self::NegFloat(a, _)
651 | Self::NegInt(a, _)
652 | Self::BitNotInt(a, _)
653 | Self::CallLibFunc(_, a, _)
654 | Self::Print(a)
655 | Self::StoreFuncArg(a)
656 | Self::Return(a)
657 | Self::RecursiveReturn(a)
658 | Self::IsFalseJmp(a, _)
659 | Self::IsTrueJmp(a, _)
660 | Self::ThrowError(a)
661 | Self::GetFieldStruct(a, _, _)
662 | Self::NegBool(a, _)
663 | Self::NewCell(a, _)
664 | Self::LoadCell(a, _)
665 | Self::CallIndirect(a)
666 | Self::ObjElemMov(a, _, _) => f(a),
667
668 Self::CallLibFuncVoid(func, a, b) => {
669 f(a);
670 if matches!(*func, LibFuncVoid::FsWrite | LibFuncVoid::FsAppend) {
671 f(b);
672 }
673 }
674 Self::Halt(x) if *x != 0 => f(x),
675
676 Self::CloneArray(src, _, _)
677 | Self::CloneStruct(src, _)
678 | Self::CloneEnum(src, _)
679 | Self::CloneMap(src, _) => {
680 f(src);
681 }
682
683 Self::Halt(_)
684 | Self::Jmp(_)
685 | Self::JmpBack(_)
686 | Self::VoidReturn
687 | Self::CallFunc(_, _)
688 | Self::CallFuncRecursive(_, _)
689 | Self::CallNative(_, _)
690 | Self::CallNativeRec(_, _)
691 | Self::NativeExit
692 | Self::SaveFrame(_, _, _)
693 | Self::CallDynamicLibFunc(_, _)
694 | Self::CallHostFunc(_, _)
695 | Self::EmptyArray(_)
696 | Self::EmptyFnValue(_)
697 | Self::SetInt(_, _)
698 | Self::StartErrorCatch(_, _)
699 | Self::StopErrorCatch
700 | Self::SetBool(_, _) => {}
701 }
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 use super::Instr;
708
709 /// One instruction is one word. A fourth operand would make it two, which
710 /// doubles the bytecode a program carries and the bytes each dispatch
711 /// reads.
712 #[test]
713 fn instruction_is_one_word() {
714 assert_eq!(size_of::<Instr>(), 8);
715 }
716
717 /// A value is two words: the NaN box and the word an `int` needs. A third
718 /// would widen every register and every list slot in a program.
719 #[test]
720 fn a_value_is_two_words() {
721 assert_eq!(size_of::<crate::data::Data>(), 16);
722 }
723}