Skip to main content

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}