Skip to main content

cove_ir/bytecode/
op.rs

1//! The hundred and one opcodes, and what each one makes of the four fields.
2//!
3//! # One opcode per concrete operation
4//!
5//! [`Inst`](crate::Inst)'s own doc argues that *"the instruction set describes
6//! families, not cases"* — one `Arith`, not one per numeric type — and that
7//! argument is about the *language* growing a concept. The bytecode grows
8//! none by enumerating members that already exist, and it removes a nested
9//! dispatch by doing so. ADR 0041 decides the enumeration:
10//!
11//! - [`Inst::Arith`](crate::Inst::Arith) becomes ten, `Num` × `ArithOp`;
12//! - [`Inst::Cmp`](crate::Inst::Cmp) becomes thirty-six, `Compare` × `CmpOp`;
13//! - [`Inst::ArithImm`](crate::Inst::ArithImm) five and
14//!   [`Inst::CmpImm`](crate::Inst::CmpImm) six, the operator alone;
15//! - [`Inst::Neg`](crate::Inst::Neg) two, [`Convert`] two;
16//! - [`Inst::Alloc`](crate::Inst::Alloc) three, one per [`Len`](crate::Len)
17//!   form, so no discriminant is stored anywhere.
18//!
19//! # The cross products are generated, not hand-picked
20//!
21//! [`Op::all`] is the enumeration and an opcode *number is a position in it*.
22//! Nothing here lists a hundred numbers, and nothing lists which operator
23//! pairs with which comparison: `crate::verify` already constrains which
24//! `Repr`s a `Compare` admits and does not constrain the pairing, so a
25//! hand-picked table would be a second and weaker copy of the type rules,
26//! living in the encoder. An opcode the lowering never emits costs one number
27//! out of 256 and one row of a generated table; a rule about which pairs are
28//! legal costs a place for two copies to disagree.
29//!
30//! [`Op::number`] computes the same number by arithmetic, so that it is not a
31//! search, and [`Op::from_number`] inverts it through a table built from
32//! [`Op::all`] — one direction derived from the other rather than two lists
33//! to keep in step. The tests below pin all three against each other.
34
35use std::sync::LazyLock;
36
37use crate::inst::{ArithOp, CmpOp, Compare, Convert, Num};
38use crate::repr::Repr;
39
40/// Every [`Num`], in opcode order.
41const NUMS: [Num; 2] = [Num::Int, Num::Float];
42/// Every [`ArithOp`], in opcode order.
43const ARITH_OPS: [ArithOp; 5] = [
44    ArithOp::Add,
45    ArithOp::Sub,
46    ArithOp::Mul,
47    ArithOp::Div,
48    ArithOp::Rem,
49];
50/// Every [`CmpOp`], in opcode order.
51const CMP_OPS: [CmpOp; 6] = [
52    CmpOp::Eq,
53    CmpOp::Ne,
54    CmpOp::Lt,
55    CmpOp::Le,
56    CmpOp::Gt,
57    CmpOp::Ge,
58];
59/// Every [`Compare`], in opcode order.
60const COMPARES: [Compare; 6] = [
61    Compare::Int,
62    Compare::Float,
63    Compare::Bool,
64    Compare::Str,
65    Compare::Identity,
66    Compare::Tag,
67];
68/// Every [`Convert`], in opcode order.
69const CONVERTS: [Convert; 2] = [Convert::IntToFloat, Convert::FloatToInt];
70
71/// Where each family's opcodes begin.
72///
73/// Each base is the one before it plus that family's size, so no number is
74/// written down twice and inserting a family renumbers the ones after it —
75/// which ADR 0041 permits, because opcode numbers are explicitly not stable.
76mod base {
77    use super::{ARITH_OPS, CMP_OPS, COMPARES, CONVERTS, NUMS};
78
79    pub const CONST_UNIT: u8 = 0;
80    pub const CONST_BOOL: u8 = CONST_UNIT + 1;
81    pub const CONST_INT: u8 = CONST_BOOL + 1;
82    pub const FUNC_REF: u8 = CONST_INT + 1;
83    pub const CONST_TAG: u8 = FUNC_REF + 1;
84    pub const CONST_FLOAT: u8 = CONST_TAG + 1;
85    pub const STR: u8 = CONST_FLOAT + 1;
86    pub const COPY: u8 = STR + 1;
87    pub const CLEAR: u8 = COPY + 1;
88    pub const NEG: u8 = CLEAR + 1;
89    pub const ARITH: u8 = NEG + NUMS.len() as u8;
90    pub const CMP: u8 = ARITH + (NUMS.len() * ARITH_OPS.len()) as u8;
91    pub const ARITH_IMM: u8 = CMP + (COMPARES.len() * CMP_OPS.len()) as u8;
92    pub const CMP_IMM: u8 = ARITH_IMM + ARITH_OPS.len() as u8;
93    pub const NOT: u8 = CMP_IMM + CMP_OPS.len() as u8;
94    pub const CONVERT: u8 = NOT + 1;
95    pub const JUMP: u8 = CONVERT + CONVERTS.len() as u8;
96    pub const BRANCH_FALSE: u8 = JUMP + 1;
97    pub const SWITCH: u8 = BRANCH_FALSE + 1;
98    pub const RETURN: u8 = SWITCH + 1;
99    pub const CALL: u8 = RETURN + 1;
100    pub const CALL_CLOSURE: u8 = CALL + 1;
101    pub const CALL_HOST: u8 = CALL_CLOSURE + 1;
102    pub const CALL_RESOURCE: u8 = CALL_HOST + 1;
103    pub const CALL_BUILTIN: u8 = CALL_RESOURCE + 1;
104    pub const ALLOC_FIXED: u8 = CALL_BUILTIN + 1;
105    pub const ALLOC_IMM: u8 = ALLOC_FIXED + 1;
106    pub const ALLOC_SLOT: u8 = ALLOC_IMM + 1;
107    pub const LOAD_FIELD: u8 = ALLOC_SLOT + 1;
108    pub const STORE_FIELD: u8 = LOAD_FIELD + 1;
109    pub const LOAD_ELEM: u8 = STORE_FIELD + 1;
110    pub const STORE_ELEM: u8 = LOAD_ELEM + 1;
111    pub const BYTE_AT: u8 = STORE_ELEM + 1;
112    /// [ADR 0051](../../../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)'s
113    /// four byte-run instructions, in the order [`crate::Inst`] declares
114    /// them.
115    pub const ALLOC_BYTES: u8 = BYTE_AT + 1;
116    pub const WRITE_BYTE: u8 = ALLOC_BYTES + 1;
117    pub const COPY_BYTES: u8 = WRITE_BYTE + 1;
118    pub const FINISH_STRING: u8 = COPY_BYTES + 1;
119    /// [ADR 0052](../../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)'s
120    /// four byte-buffer instructions, in the order [`crate::Inst`] declares
121    /// them and directly after the four fixed-run ones they grow.
122    pub const ALLOC_BUFFER: u8 = FINISH_STRING + 1;
123    pub const APPEND_BYTE: u8 = ALLOC_BUFFER + 1;
124    pub const APPEND_BYTES: u8 = APPEND_BYTE + 1;
125    pub const FINISH_BUFFER: u8 = APPEND_BYTES + 1;
126    pub const LEN: u8 = FINISH_BUFFER + 1;
127    pub const LAYOUT_OF: u8 = LEN + 1;
128    pub const ADDR_OF_SLOT: u8 = LAYOUT_OF + 1;
129    pub const ADDR_OF_FIELD: u8 = ADDR_OF_SLOT + 1;
130    pub const ADDR_OF_ELEM: u8 = ADDR_OF_FIELD + 1;
131    pub const ADDR_OF_PART: u8 = ADDR_OF_ELEM + 1;
132    pub const LOAD: u8 = ADDR_OF_PART + 1;
133    pub const STORE: u8 = LOAD + 1;
134    pub const BOX: u8 = STORE + 1;
135    pub const UNBOX: u8 = BOX + 1;
136    pub const SCOPE_ENTER: u8 = UNBOX + 1;
137    pub const SCOPE_LEAVE: u8 = SCOPE_ENTER + 1;
138    pub const SCOPE_CANCEL: u8 = SCOPE_LEAVE + 1;
139    pub const SPAWN: u8 = SCOPE_CANCEL + 1;
140    pub const AWAIT: u8 = SPAWN + 1;
141    pub const CANCEL: u8 = AWAIT + 1;
142    pub const SETTLED: u8 = CANCEL + 1;
143    pub const SHARED_LOCK: u8 = SETTLED + 1;
144    pub const SHARED_UNLOCK: u8 = SHARED_LOCK + 1;
145    pub const TRAP: u8 = SHARED_UNLOCK + 1;
146    pub const ASSERT_FAILED: u8 = TRAP + 1;
147    /// One past the last, which is how many opcodes there are.
148    pub const END: u8 = ASSERT_FAILED + 1;
149}
150
151/// How many opcodes are defined, out of the 256 an opcode byte can name.
152pub const OPCODES: usize = base::END as usize;
153
154/// One concrete operation.
155///
156/// The parameterised variants are the cross products ADR 0041 generates: an
157/// `Op::Arith(Num::Int, ArithOp::Add)` *is* `add.int`, and there is no second
158/// name for it.
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub enum Op {
161    ConstUnit,
162    ConstBool,
163    ConstInt,
164    FuncRef,
165    ConstTag,
166    ConstFloat,
167    Str,
168    Copy,
169    Clear,
170    Neg(Num),
171    Arith(Num, ArithOp),
172    Cmp(Compare, CmpOp),
173    ArithImm(ArithOp),
174    CmpImm(CmpOp),
175    Not,
176    Convert(Convert),
177    Jump,
178    BranchFalse,
179    Switch,
180    Return,
181    Call,
182    CallClosure,
183    CallHost,
184    CallResource,
185    CallBuiltin,
186    AllocFixed,
187    AllocImm,
188    AllocSlot,
189    LoadField,
190    StoreField,
191    LoadElem,
192    StoreElem,
193    ByteAt,
194    AllocBytes,
195    WriteByte,
196    CopyBytes,
197    FinishString,
198    AllocBuffer,
199    AppendByte,
200    AppendBytes,
201    FinishBuffer,
202    Len,
203    LayoutOf,
204    AddrOfSlot,
205    AddrOfField,
206    AddrOfElem,
207    AddrOfPart,
208    Load,
209    Store,
210    Box,
211    Unbox,
212    ScopeEnter,
213    ScopeLeave,
214    ScopeCancel,
215    Spawn,
216    Await,
217    Cancel,
218    Settled,
219    SharedLock,
220    SharedUnlock,
221    Trap,
222    AssertFailed,
223}
224
225/// Which of `a`, `b` and `c` an opcode uses, and for what.
226///
227/// This is the table ADR 0041 calls the format's central saving: *"every slot
228/// is inside the function frame"* is not forty-nine rules but one rule over
229/// three fields, driven by which of the three an opcode declares live.
230#[derive(Clone, Copy, Debug, PartialEq, Eq)]
231pub enum Operand {
232    /// The opcode does not use this field, and it must be zero.
233    ///
234    /// Requiring the zero is what makes the encoding *canonical*: two
235    /// encodings of one program are byte-identical, and `encode(decode(b))`
236    /// is `b`.
237    Unused,
238    /// One word of the frame, holding one of these [`Repr`]s.
239    ///
240    /// [`ANY`] is the empty list and means the opcode constrains nothing —
241    /// the destination of a call, whose `Repr` is the head word of whatever
242    /// the callee answers, and the operand of an `addr.slot`, which is any
243    /// location at all.
244    Word(&'static [Repr]),
245    /// The first slot of a value location whose width comes from the layout
246    /// in the payload.
247    ///
248    /// The check is the `fits` one `crate::verify` makes: `slot + width` must
249    /// be inside the frame, because a run of words copied off the top of a
250    /// frame reads or writes the frame above it.
251    Value,
252}
253
254/// An operand whose `Repr` the opcode does not constrain. See
255/// [`Operand::Word`].
256pub const ANY: &[Repr] = &[];
257
258/// A numeric operand: a `Duration` is nanoseconds and adds like an integer,
259/// which is `crate::verify`'s rule kept word for word.
260const INT: &[Repr] = &[Repr::Int, Repr::Duration];
261const FLOAT: &[Repr] = &[Repr::Float];
262const BOOL: &[Repr] = &[Repr::Bool];
263const UNIT: &[Repr] = &[Repr::Unit];
264const REF: &[Repr] = &[Repr::Ref];
265const ADDR: &[Repr] = &[Repr::Addr];
266const HOST: &[Repr] = &[Repr::Host];
267const TASK: &[Repr] = &[Repr::Task];
268const SCOPE: &[Repr] = &[Repr::Scope];
269/// An enum's case index. Physically an integer word and semantically not
270/// one, so it is its own set and appears in exactly two opcodes.
271const TAG: &[Repr] = &[Repr::Tag];
272/// What a switch dispatches on: an enum's case, or the layout id a `dyn`
273/// dispatch reads out of a box. The second is still an `Int` — a layout id
274/// is the other metadata-like integer in this IR and giving it a `Repr` of
275/// its own is a separate change to a separate consumer.
276const SWITCHED: &[Repr] = &[Repr::Tag, Repr::Int];
277
278/// What the payload's eight bytes are.
279#[derive(Clone, Copy, Debug, PartialEq, Eq)]
280pub enum Payload {
281    /// Nothing, and all eight bytes must be zero.
282    Empty,
283    /// `0` or `1`, and nothing else.
284    Bool,
285    /// All sixty-four bits, as the instruction's own immediate: an `i64`
286    /// value, or the bits of an `f64`.
287    Imm,
288    /// `to - (pc + 1)`, two's complement, so a branch is relative.
289    Displacement,
290    /// Two 32-bit halves, low first.
291    Halves(Half, Half),
292}
293
294/// What one 32-bit half of a payload holds.
295///
296/// Every id keeps its full 32 bits: ADR 0041 narrows slot operands and
297/// nothing else.
298#[derive(Clone, Copy, Debug, PartialEq, Eq)]
299pub enum Half {
300    /// Unused, and must be zero.
301    Unused,
302    Function,
303    Str,
304    Layout,
305    Table,
306    Args,
307    Builtin,
308    HostOp,
309    /// An element count: `Len::Count`'s `n`.
310    Count,
311    /// An enum's case index: [`crate::CaseId`]'s number.
312    ///
313    /// It is not bounds-checked here, for `Half::Count`'s reason: which
314    /// numbers are cases is a fact about the layout the same instruction
315    /// names, so [`mod@crate::bytecode::verify`]'s semantic pass checks it
316    /// against that layout rather than against a table of its own.
317    Case,
318    /// A word offset into an object or into the value an address names.
319    Offset,
320}
321
322impl Half {
323    /// What a fault calls this, and what a table it indexes is called.
324    pub fn name(self) -> &'static str {
325        match self {
326            Half::Unused => "unused",
327            Half::Function => "function",
328            Half::Str => "string",
329            Half::Layout => "layout",
330            Half::Table => "table",
331            Half::Args => "argument list",
332            Half::Builtin => "builtin",
333            Half::HostOp => "host op",
334            Half::Count => "count",
335            Half::Case => "case",
336            Half::Offset => "offset",
337        }
338    }
339}
340
341/// What an opcode makes of the four fields.
342#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343pub struct Fields {
344    pub a: Operand,
345    pub b: Operand,
346    pub c: Operand,
347    pub payload: Payload,
348}
349
350impl Fields {
351    /// `a`, `b` and `c` in order, so that one loop is the whole slot check.
352    pub fn operands(&self) -> [Operand; 3] {
353        [self.a, self.b, self.c]
354    }
355}
356
357/// Builds a [`Fields`], filling in the fields an opcode leaves alone.
358fn fields(a: Operand, b: Operand, c: Operand, payload: Payload) -> Fields {
359    Fields { a, b, c, payload }
360}
361
362/// Nothing at all: the field is unused and must be zero.
363const NONE: Operand = Operand::Unused;
364
365/// The position of `wanted` in the table `held`, as an opcode offset.
366///
367/// A macro rather than a generic function because [`Op::number`] is a
368/// `const fn`, and an opcode number has to be a *constant* to be a `match`
369/// pattern: `crate::bytecode`'s numbers are what the runtime's encoded
370/// dispatch branches on, and a loop that had to call a function to learn
371/// which opcode it was holding would be doing the work the numbering exists
372/// to remove. `Iterator::position` is not const and neither is `PartialEq`,
373/// so this walks the array instead — and it walks *the array*, which keeps
374/// the tables above the only place any of these orders is written down. A
375/// `const fn` per enum would have been the second copy this module's doc
376/// refuses.
377///
378/// The comparison is on the discriminant, which is what `as u8` reads off a
379/// fieldless enum. Every member of an enum is in the table it is enumerated
380/// by, so the walk always stops on one; [`Op::all`] and the tests below are
381/// what hold that true.
382macro_rules! index_of {
383    ($held:ident, $wanted:expr) => {{
384        let wanted = $wanted as u8;
385        let mut at = 0;
386        while at < $held.len() && $held[at] as u8 != wanted {
387            at += 1;
388        }
389        at as u8
390    }};
391}
392
393impl Op {
394    /// Every opcode, in the order that gives them their numbers.
395    ///
396    /// This is the generated table ADR 0041 asks for. A family with members
397    /// contributes its cross product, in the order of the arrays above.
398    pub fn all() -> Vec<Op> {
399        let mut all = vec![
400            Op::ConstUnit,
401            Op::ConstBool,
402            Op::ConstInt,
403            Op::FuncRef,
404            Op::ConstTag,
405            Op::ConstFloat,
406            Op::Str,
407            Op::Copy,
408            Op::Clear,
409        ];
410        all.extend(NUMS.map(Op::Neg));
411        for num in NUMS {
412            for op in ARITH_OPS {
413                all.push(Op::Arith(num, op));
414            }
415        }
416        for on in COMPARES {
417            for op in CMP_OPS {
418                all.push(Op::Cmp(on, op));
419            }
420        }
421        all.extend(ARITH_OPS.map(Op::ArithImm));
422        all.extend(CMP_OPS.map(Op::CmpImm));
423        all.push(Op::Not);
424        all.extend(CONVERTS.map(Op::Convert));
425        all.extend([
426            Op::Jump,
427            Op::BranchFalse,
428            Op::Switch,
429            Op::Return,
430            Op::Call,
431            Op::CallClosure,
432            Op::CallHost,
433            Op::CallResource,
434            Op::CallBuiltin,
435            Op::AllocFixed,
436            Op::AllocImm,
437            Op::AllocSlot,
438            Op::LoadField,
439            Op::StoreField,
440            Op::LoadElem,
441            Op::StoreElem,
442            Op::ByteAt,
443            Op::AllocBytes,
444            Op::WriteByte,
445            Op::CopyBytes,
446            Op::FinishString,
447            Op::AllocBuffer,
448            Op::AppendByte,
449            Op::AppendBytes,
450            Op::FinishBuffer,
451            Op::Len,
452            Op::LayoutOf,
453            Op::AddrOfSlot,
454            Op::AddrOfField,
455            Op::AddrOfElem,
456            Op::AddrOfPart,
457            Op::Load,
458            Op::Store,
459            Op::Box,
460            Op::Unbox,
461            Op::ScopeEnter,
462            Op::ScopeLeave,
463            Op::ScopeCancel,
464            Op::Spawn,
465            Op::Await,
466            Op::Cancel,
467            Op::Settled,
468            Op::SharedLock,
469            Op::SharedUnlock,
470            Op::Trap,
471            Op::AssertFailed,
472        ]);
473        all
474    }
475
476    /// The byte this opcode is written as.
477    ///
478    /// `const`, so that an opcode is usable as a `match` pattern. That is
479    /// what lets the runtime's encoded dispatch name `add.int.imm` by
480    /// writing `Op::ArithImm(ArithOp::Add).number()` instead of a literal,
481    /// and it is why ADR 0041's *"opcode numbers are positions in a
482    /// generated table and move when the table does"* costs nothing outside
483    /// this file.
484    pub const fn number(self) -> u8 {
485        match self {
486            Op::ConstUnit => base::CONST_UNIT,
487            Op::ConstBool => base::CONST_BOOL,
488            Op::ConstInt => base::CONST_INT,
489            Op::FuncRef => base::FUNC_REF,
490            Op::ConstTag => base::CONST_TAG,
491            Op::ConstFloat => base::CONST_FLOAT,
492            Op::Str => base::STR,
493            Op::Copy => base::COPY,
494            Op::Clear => base::CLEAR,
495            Op::Neg(num) => base::NEG + index_of!(NUMS, num),
496            Op::Arith(num, op) => {
497                base::ARITH
498                    + index_of!(NUMS, num) * ARITH_OPS.len() as u8
499                    + index_of!(ARITH_OPS, op)
500            }
501            Op::Cmp(on, op) => {
502                base::CMP + index_of!(COMPARES, on) * CMP_OPS.len() as u8 + index_of!(CMP_OPS, op)
503            }
504            Op::ArithImm(op) => base::ARITH_IMM + index_of!(ARITH_OPS, op),
505            Op::CmpImm(op) => base::CMP_IMM + index_of!(CMP_OPS, op),
506            Op::Not => base::NOT,
507            Op::Convert(to) => base::CONVERT + index_of!(CONVERTS, to),
508            Op::Jump => base::JUMP,
509            Op::BranchFalse => base::BRANCH_FALSE,
510            Op::Switch => base::SWITCH,
511            Op::Return => base::RETURN,
512            Op::Call => base::CALL,
513            Op::CallClosure => base::CALL_CLOSURE,
514            Op::CallHost => base::CALL_HOST,
515            Op::CallResource => base::CALL_RESOURCE,
516            Op::CallBuiltin => base::CALL_BUILTIN,
517            Op::AllocFixed => base::ALLOC_FIXED,
518            Op::AllocImm => base::ALLOC_IMM,
519            Op::AllocSlot => base::ALLOC_SLOT,
520            Op::LoadField => base::LOAD_FIELD,
521            Op::StoreField => base::STORE_FIELD,
522            Op::LoadElem => base::LOAD_ELEM,
523            Op::StoreElem => base::STORE_ELEM,
524            Op::ByteAt => base::BYTE_AT,
525            Op::AllocBytes => base::ALLOC_BYTES,
526            Op::WriteByte => base::WRITE_BYTE,
527            Op::CopyBytes => base::COPY_BYTES,
528            Op::FinishString => base::FINISH_STRING,
529            Op::AllocBuffer => base::ALLOC_BUFFER,
530            Op::AppendByte => base::APPEND_BYTE,
531            Op::AppendBytes => base::APPEND_BYTES,
532            Op::FinishBuffer => base::FINISH_BUFFER,
533            Op::Len => base::LEN,
534            Op::LayoutOf => base::LAYOUT_OF,
535            Op::AddrOfSlot => base::ADDR_OF_SLOT,
536            Op::AddrOfField => base::ADDR_OF_FIELD,
537            Op::AddrOfElem => base::ADDR_OF_ELEM,
538            Op::AddrOfPart => base::ADDR_OF_PART,
539            Op::Load => base::LOAD,
540            Op::Store => base::STORE,
541            Op::Box => base::BOX,
542            Op::Unbox => base::UNBOX,
543            Op::ScopeEnter => base::SCOPE_ENTER,
544            Op::ScopeLeave => base::SCOPE_LEAVE,
545            Op::ScopeCancel => base::SCOPE_CANCEL,
546            Op::Spawn => base::SPAWN,
547            Op::Await => base::AWAIT,
548            Op::Cancel => base::CANCEL,
549            Op::Settled => base::SETTLED,
550            Op::SharedLock => base::SHARED_LOCK,
551            Op::SharedUnlock => base::SHARED_UNLOCK,
552            Op::Trap => base::TRAP,
553            Op::AssertFailed => base::ASSERT_FAILED,
554        }
555    }
556
557    /// Which opcode a byte names, or `None` for one no encoder produced.
558    ///
559    /// The inverse is a table built from [`Op::all`] rather than a second
560    /// match, so there is one enumeration and not two.
561    pub fn from_number(number: u8) -> Option<Op> {
562        static BY_NUMBER: LazyLock<[Option<Op>; 256]> = LazyLock::new(|| {
563            let mut table = [None; 256];
564            for op in Op::all() {
565                table[op.number() as usize] = Some(op);
566            }
567            table
568        });
569        BY_NUMBER[number as usize]
570    }
571
572    /// What this opcode makes of the four fields.
573    ///
574    /// This is ADR 0041's audit table, one row per opcode, and it is what
575    /// both the decoder's canonicality check and the verifier's slot check
576    /// are driven by.
577    pub fn fields(self) -> Fields {
578        let ids = |lo: Half, hi: Half| Payload::Halves(lo, hi);
579        let one = |lo: Half| Payload::Halves(lo, Half::Unused);
580        match self {
581            Op::ConstUnit => fields(Operand::Word(UNIT), NONE, NONE, Payload::Empty),
582            Op::ConstBool => fields(Operand::Word(BOOL), NONE, NONE, Payload::Bool),
583            Op::ConstInt => fields(Operand::Word(INT), NONE, NONE, Payload::Imm),
584            // The callee's dense id, bound against the function table the
585            // same way `Op::Call`'s is — see the generic check `bounds`
586            // makes of every `Half::Function`. No name is looked up: this
587            // writes the id into `a` exactly as `Op::ConstInt` writes its
588            // immediate.
589            Op::FuncRef => fields(Operand::Word(INT), NONE, NONE, one(Half::Function)),
590            // The destination is the one place a `Repr::Tag` is produced.
591            Op::ConstTag => fields(
592                Operand::Word(TAG),
593                NONE,
594                NONE,
595                ids(Half::Case, Half::Layout),
596            ),
597            Op::ConstFloat => fields(Operand::Word(FLOAT), NONE, NONE, Payload::Imm),
598            Op::Str => fields(Operand::Word(REF), NONE, NONE, one(Half::Str)),
599            Op::Copy => fields(Operand::Value, Operand::Value, NONE, one(Half::Layout)),
600            Op::Clear => fields(Operand::Value, NONE, NONE, one(Half::Layout)),
601            Op::Neg(num) => {
602                let want = numeric(num);
603                fields(
604                    Operand::Word(want),
605                    Operand::Word(want),
606                    NONE,
607                    Payload::Empty,
608                )
609            }
610            Op::Arith(num, _) => {
611                let want = numeric(num);
612                fields(
613                    Operand::Word(want),
614                    Operand::Word(want),
615                    Operand::Word(want),
616                    Payload::Empty,
617                )
618            }
619            Op::Cmp(on, _) => {
620                let want = compared(on);
621                fields(
622                    Operand::Word(BOOL),
623                    Operand::Word(want),
624                    Operand::Word(want),
625                    Payload::Empty,
626                )
627            }
628            Op::ArithImm(_) => fields(Operand::Word(INT), Operand::Word(INT), NONE, Payload::Imm),
629            Op::CmpImm(_) => fields(Operand::Word(BOOL), Operand::Word(INT), NONE, Payload::Imm),
630            Op::Not => fields(
631                Operand::Word(BOOL),
632                Operand::Word(BOOL),
633                NONE,
634                Payload::Empty,
635            ),
636            Op::Convert(to) => {
637                let (from, into) = match to {
638                    Convert::IntToFloat => (INT, FLOAT),
639                    Convert::FloatToInt => (FLOAT, INT),
640                };
641                fields(
642                    Operand::Word(into),
643                    Operand::Word(from),
644                    NONE,
645                    Payload::Empty,
646                )
647            }
648            Op::Jump => fields(NONE, NONE, NONE, Payload::Displacement),
649            Op::BranchFalse => fields(Operand::Word(BOOL), NONE, NONE, Payload::Displacement),
650            // The discriminant of an enum location is its first word and is
651            // an `Int`; so is the layout id a `dyn` dispatch switches on.
652            Op::Switch => fields(Operand::Word(SWITCHED), NONE, NONE, one(Half::Table)),
653            // `src` is a value location of `Function::returns`, which is not
654            // in the instruction: the width check is the verifier's, from the
655            // function being checked.
656            Op::Return => fields(Operand::Word(ANY), NONE, NONE, Payload::Empty),
657            // A call's destination is a value location too, at the *callee's*
658            // `returns`. Same reason, same place.
659            Op::Call => fields(
660                Operand::Word(ANY),
661                NONE,
662                NONE,
663                ids(Half::Function, Half::Args),
664            ),
665            // The destination is a value location like every other call's,
666            // and the layout it is measured against is the one in the
667            // payload rather than one read off a declared callee: a closure
668            // call names a word in a slot, so `Inst::CallClosure` carries
669            // the answer's layout itself.
670            Op::CallClosure => fields(
671                Operand::Value,
672                Operand::Word(REF),
673                NONE,
674                ids(Half::Args, Half::Layout),
675            ),
676            Op::CallHost => fields(
677                Operand::Word(ANY),
678                NONE,
679                NONE,
680                ids(Half::HostOp, Half::Args),
681            ),
682            Op::CallResource => fields(
683                Operand::Word(ANY),
684                Operand::Word(HOST),
685                NONE,
686                ids(Half::HostOp, Half::Args),
687            ),
688            Op::CallBuiltin => fields(
689                Operand::Word(ANY),
690                NONE,
691                NONE,
692                ids(Half::Builtin, Half::Args),
693            ),
694            Op::AllocFixed => fields(Operand::Word(REF), NONE, NONE, one(Half::Layout)),
695            Op::AllocImm => fields(
696                Operand::Word(REF),
697                NONE,
698                NONE,
699                ids(Half::Layout, Half::Count),
700            ),
701            Op::AllocSlot => fields(
702                Operand::Word(REF),
703                Operand::Word(INT),
704                NONE,
705                one(Half::Layout),
706            ),
707            Op::LoadField => fields(
708                Operand::Value,
709                Operand::Word(REF),
710                NONE,
711                ids(Half::Offset, Half::Layout),
712            ),
713            Op::StoreField => fields(
714                Operand::Word(REF),
715                Operand::Value,
716                NONE,
717                ids(Half::Offset, Half::Layout),
718            ),
719            Op::LoadElem => fields(
720                Operand::Value,
721                Operand::Word(REF),
722                Operand::Word(INT),
723                one(Half::Layout),
724            ),
725            Op::StoreElem => fields(
726                Operand::Word(REF),
727                Operand::Word(INT),
728                Operand::Value,
729                one(Half::Layout),
730            ),
731            Op::ByteAt => fields(
732                Operand::Word(INT),
733                Operand::Word(REF),
734                Operand::Word(INT),
735                Payload::Empty,
736            ),
737            // No `Half::Layout` here, for `Op::Str`'s reason: the layout is
738            // always `Program::bytes_layout`, a program-wide constant rather
739            // than a fact this opcode has to carry.
740            Op::AllocBytes => fields(Operand::Word(REF), Operand::Word(INT), NONE, Payload::Empty),
741            Op::WriteByte => fields(
742                Operand::Word(REF),
743                Operand::Word(INT),
744                Operand::Word(INT),
745                Payload::Empty,
746            ),
747            // All five operands — `dst`, `dst_at`, `src`, `src_at`, `len` —
748            // live behind the `ArgsId`, because a sixteen-byte instruction
749            // has room for three slot operands and this needs five. See
750            // `Inst::CopyBytes`'s doc for why the argument-list machinery a
751            // call already has is what carries the other two.
752            Op::CopyBytes => fields(NONE, NONE, NONE, one(Half::Args)),
753            Op::FinishString => {
754                fields(Operand::Word(REF), Operand::Word(REF), NONE, Payload::Empty)
755            }
756            // No `Half::Layout` on either of the two allocating buffer
757            // opcodes, for `Op::AllocBytes`' reason twice over: an owner is
758            // always `Program::buffer_layout` and its store is always
759            // `Program::bytes_layout`.
760            Op::AllocBuffer => fields(Operand::Word(REF), Operand::Word(INT), NONE, Payload::Empty),
761            Op::AppendByte => fields(Operand::Word(REF), Operand::Word(INT), NONE, Payload::Empty),
762            // All four operands — `buffer`, `src`, `from`, `to` — live behind
763            // the `ArgsId`, because a sixteen-byte instruction has room for
764            // three slot operands and this needs four. See
765            // `Inst::AppendBytes`'s doc, and `Op::CopyBytes` above for the same
766            // arrangement at five.
767            Op::AppendBytes => fields(NONE, NONE, NONE, one(Half::Args)),
768            Op::FinishBuffer => {
769                fields(Operand::Word(REF), Operand::Word(REF), NONE, Payload::Empty)
770            }
771            Op::Len => fields(Operand::Word(INT), Operand::Word(REF), NONE, Payload::Empty),
772            Op::LayoutOf => fields(Operand::Word(INT), Operand::Word(REF), NONE, Payload::Empty),
773            Op::AddrOfSlot => fields(
774                Operand::Word(ADDR),
775                Operand::Word(ANY),
776                NONE,
777                Payload::Empty,
778            ),
779            Op::AddrOfField => fields(
780                Operand::Word(ADDR),
781                Operand::Word(REF),
782                NONE,
783                one(Half::Offset),
784            ),
785            Op::AddrOfElem => fields(
786                Operand::Word(ADDR),
787                Operand::Word(REF),
788                Operand::Word(INT),
789                one(Half::Layout),
790            ),
791            // Nothing bounds `at` against the value the address names, and
792            // that gap is inherited rather than introduced: a frame records
793            // no value's extent. `crate::verify` says the same, for the same
794            // reason, and `at` keeps its full 32 bits.
795            Op::AddrOfPart => fields(
796                Operand::Word(ADDR),
797                Operand::Word(ADDR),
798                NONE,
799                one(Half::Offset),
800            ),
801            Op::Load => fields(Operand::Value, Operand::Word(ADDR), NONE, one(Half::Layout)),
802            Op::Store => fields(Operand::Word(ADDR), Operand::Value, NONE, one(Half::Layout)),
803            Op::Box => fields(Operand::Word(REF), Operand::Value, NONE, one(Half::Layout)),
804            Op::Unbox => fields(Operand::Value, Operand::Word(REF), NONE, one(Half::Layout)),
805            Op::ScopeEnter => fields(Operand::Word(SCOPE), NONE, NONE, one(Half::Str)),
806            Op::ScopeLeave => fields(
807                Operand::Word(SCOPE),
808                Operand::Word(BOOL),
809                Operand::Value,
810                one(Half::Layout),
811            ),
812            Op::ScopeCancel => fields(Operand::Word(SCOPE), NONE, NONE, Payload::Empty),
813            // The answer's layout is what the machine allocates an object of,
814            // not a location in this frame, so all three fields are one word.
815            Op::Spawn => fields(
816                Operand::Word(TASK),
817                Operand::Word(SCOPE),
818                Operand::Word(REF),
819                one(Half::Layout),
820            ),
821            Op::Await => fields(Operand::Value, Operand::Word(TASK), NONE, one(Half::Layout)),
822            Op::Cancel => fields(Operand::Word(TASK), NONE, NONE, Payload::Empty),
823            Op::Settled => fields(Operand::Word(TASK), Operand::Value, NONE, one(Half::Layout)),
824            Op::SharedLock => fields(Operand::Word(REF), NONE, NONE, Payload::Empty),
825            Op::SharedUnlock => fields(Operand::Word(REF), NONE, NONE, Payload::Empty),
826            Op::Trap => fields(NONE, NONE, NONE, one(Half::Str)),
827            Op::AssertFailed => fields(Operand::Word(REF), NONE, NONE, Payload::Empty),
828        }
829    }
830}
831
832/// What a numeric operand may hold.
833fn numeric(num: Num) -> &'static [Repr] {
834    match num {
835        Num::Int => INT,
836        Num::Float => FLOAT,
837    }
838}
839
840/// What a comparison's two operands may hold.
841fn compared(on: Compare) -> &'static [Repr] {
842    match on {
843        Compare::Int => INT,
844        Compare::Float => FLOAT,
845        Compare::Bool => BOOL,
846        Compare::Str => REF,
847        // `is` compares words, and the only words whose identity is a
848        // language-level question are references.
849        Compare::Identity => REF,
850        Compare::Tag => TAG,
851    }
852}
853
854#[cfg(test)]
855mod tests {
856    use super::*;
857
858    /// ADR 0041's count, which is the one number the format's headroom is
859    /// argued from: a hundred and seventeen opcodes out of the 256 a byte
860    /// names.
861    ///
862    /// It was a hundred and two until `Op::ByteAt`, a hundred and three until
863    /// `Compare::Tag` brought its six, a hundred and thirteen once ADR
864    /// 0051's `AllocBytes`, `WriteByte`, `CopyBytes` and `FinishString`
865    /// brought four more, and a hundred and seventeen once ADR 0052's
866    /// `AllocBuffer`, `AppendByte`, `AppendBytes` and `FinishBuffer` brought
867    /// the growable four beside them. What the number is for is that a reader
868    /// can see the headroom rather than be told about it: more than half the
869    /// byte is still unspent, so the format has room for what comes and this
870    /// test is where that claim is kept honest.
871    #[test]
872    fn there_are_a_hundred_and_seventeen_opcodes() {
873        assert_eq!(Op::all().len(), 117);
874        assert_eq!(OPCODES, 117);
875    }
876
877    /// The numbering *is* the enumeration. `number` computes by arithmetic
878    /// what `all` produces by iteration, and a family base that drifted would
879    /// make the two disagree here rather than silently in an encoding.
880    #[test]
881    fn an_opcode_number_is_its_position_in_the_generated_table() {
882        for (at, op) in Op::all().into_iter().enumerate() {
883            assert_eq!(op.number() as usize, at, "{op:?}");
884        }
885    }
886
887    /// Two opcodes with one number would make decoding a guess.
888    #[test]
889    fn no_two_opcodes_share_a_number() {
890        let mut seen = vec![None; 256];
891        for op in Op::all() {
892            let at = op.number() as usize;
893            assert_eq!(seen[at], None, "{op:?} and {:?} share {at}", seen[at]);
894            seen[at] = Some(op);
895        }
896    }
897
898    /// `from_number` is a genuine inverse over the defined numbers, and
899    /// answers `None` over every other byte — which is what makes an unknown
900    /// opcode a refusal rather than an index into a table.
901    #[test]
902    fn every_byte_either_names_one_opcode_or_none_at_all() {
903        for byte in 0u8..=255 {
904            match Op::from_number(byte) {
905                Some(op) => {
906                    assert!((byte as usize) < OPCODES);
907                    assert_eq!(op.number(), byte);
908                }
909                None => assert!((byte as usize) >= OPCODES, "{byte} names nothing"),
910            }
911        }
912    }
913
914    /// The cross products are the whole of the arithmetic families, so
915    /// `add.int` and `add.float` are two numbers and `Num` is not read at run
916    /// time.
917    #[test]
918    fn the_arithmetic_families_are_the_cross_products_the_adr_gives() {
919        let all = Op::all();
920        let count = |f: fn(&Op) -> bool| all.iter().filter(|op| f(op)).count();
921        assert_eq!(count(|op| matches!(op, Op::Arith(_, _))), 10);
922        assert_eq!(count(|op| matches!(op, Op::Cmp(_, _))), 36);
923        assert_eq!(count(|op| matches!(op, Op::ArithImm(_))), 5);
924        assert_eq!(count(|op| matches!(op, Op::CmpImm(_))), 6);
925        assert_eq!(count(|op| matches!(op, Op::Neg(_))), 2);
926        assert_eq!(count(|op| matches!(op, Op::Convert(_))), 2);
927        assert_eq!(
928            count(|op| matches!(op, Op::AllocFixed | Op::AllocImm | Op::AllocSlot)),
929            3
930        );
931    }
932
933    /// No opcode names more than three slots and none carries more than the
934    /// payload, which is the invariant the whole sixteen-byte decision rests
935    /// on. A `Fields` that used a fourth field could not be written down, and
936    /// this is what says the table never wanted to.
937    #[test]
938    fn no_opcode_uses_a_field_the_format_does_not_have() {
939        for op in Op::all() {
940            let used = op.fields();
941            // Once a field is unused the ones after it are too: the encoder
942            // fills `a`, then `b`, then `c`, and a hole would make the table
943            // ambiguous to read.
944            let live: Vec<bool> = used
945                .operands()
946                .iter()
947                .map(|one| *one != Operand::Unused)
948                .collect();
949            assert!(
950                live.windows(2).all(|pair| pair[0] || !pair[1]),
951                "{op:?} leaves a hole in a, b, c"
952            );
953        }
954    }
955
956    /// A `Value` operand's width comes from a layout, so an opcode that
957    /// declares one must carry a layout to read it from.
958    #[test]
959    fn a_value_operand_always_has_a_layout_in_the_payload() {
960        for op in Op::all() {
961            if !op.fields().operands().contains(&Operand::Value) {
962                continue;
963            }
964            let held = match op.fields().payload {
965                Payload::Halves(lo, hi) => [lo, hi],
966                other => panic!("{op:?} has a value operand and a {other:?} payload"),
967            };
968            assert!(
969                held.contains(&Half::Layout),
970                "{op:?} has a value operand and names no layout"
971            );
972        }
973    }
974}