Skip to main content

bytecode_lang/
inst.rs

1//! The instruction set.
2//!
3//! One table, the `isa!` invocation below, defines every instruction: its
4//! opcode byte, its mnemonic, and each operand's name, type, and slot in the
5//! eight-byte word. The [`Inst`] enum, [`Opcode`], the per-opcode
6//! [`FieldSpec`] metadata, the encoder ([`Inst::to_bytes`]), the decoder
7//! ([`Inst::from_bytes`]), and the disassembler's rendering are all generated
8//! from that one table, so they cannot disagree.
9//!
10//! # The eight-byte word
11//!
12//! ```text
13//! byte  0      1      2..4   4..6   6..8
14//!       opcode A      B      C      D
15//!                            \----W----/
16//! ```
17//!
18//! `A` is one byte (a modifier: a type, a policy, a count), `B`, `C`, and `D`
19//! are 16-bit (registers and per-function refs), and `W` is the 32-bit word
20//! overlapping `C` and `D` (module indices, branch targets, immediates). All
21//! little-endian. Bytes of slots an instruction does not use are zero, and the
22//! decoder rejects anything else, so every instruction has exactly one
23//! encoding. The decoded [`Inst`] is itself eight bytes and `Copy`: a function
24//! body is a flat `&[Inst]` that an interpreter walks with a program counter
25//! and never re-parses (the bvm-lang design).
26
27use core::fmt;
28
29use crate::ids::{
30    ConstId, FieldIdx, FuncId, GlobalId, ImportId, NameRef, Reg, TableId, Target, TypeRef, UpvalIdx,
31};
32use crate::module::{Const, Function, Module};
33use crate::policy::{FloatTy, IntConv, IntOp, IntPair, IntTy, Overflow, Policy};
34use crate::types::{Kind, Prim};
35
36/// Where an operand lives in the eight-byte instruction word.
37///
38/// # Examples
39///
40/// ```
41/// use bytecode_lang::Slot;
42///
43/// assert_eq!(Slot::A.width(), 8);
44/// assert_eq!(Slot::W.width(), 32);
45/// assert_eq!(Slot::C.shift(), 32);
46/// ```
47#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
48pub enum Slot {
49    /// Byte 1: an 8-bit modifier or count.
50    A,
51    /// Bytes 2–3: a 16-bit operand.
52    B,
53    /// Bytes 4–5: a 16-bit operand.
54    C,
55    /// Bytes 6–7: a 16-bit operand.
56    D,
57    /// Bytes 4–7: a 32-bit operand (overlaps `C` and `D`).
58    W,
59}
60
61impl Slot {
62    /// The slot's bit offset within the little-endian 64-bit word.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use bytecode_lang::Slot;
68    ///
69    /// assert_eq!(Slot::A.shift(), 8);
70    /// assert_eq!(Slot::D.shift(), 48);
71    /// ```
72    #[must_use]
73    pub const fn shift(self) -> u32 {
74        match self {
75            Slot::A => 8,
76            Slot::B => 16,
77            Slot::C | Slot::W => 32,
78            Slot::D => 48,
79        }
80    }
81
82    /// The slot's width in bits.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use bytecode_lang::Slot;
88    ///
89    /// assert_eq!(Slot::B.width(), 16);
90    /// ```
91    #[must_use]
92    pub const fn width(self) -> u32 {
93        match self {
94            Slot::A => 8,
95            Slot::B | Slot::C | Slot::D => 16,
96            Slot::W => 32,
97        }
98    }
99
100    const fn mask(self) -> u64 {
101        (1u64 << self.width()) - 1
102    }
103}
104
105/// What an operand is: the type of one [`Inst`] field.
106///
107/// Modifier kinds ([`is_modifier`](FieldKind::is_modifier)) print as a
108/// mnemonic suffix (`iadd.i64.wrap`); the others print as operands.
109///
110/// # Examples
111///
112/// ```
113/// use bytecode_lang::FieldKind;
114///
115/// assert!(FieldKind::IntOp.is_modifier());
116/// assert!(!FieldKind::Reg.is_modifier());
117/// assert_eq!(FieldKind::Target.bits(), 32);
118/// ```
119#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
120pub enum FieldKind {
121    /// A [`Reg`].
122    Reg,
123    /// A branch [`Target`].
124    Target,
125    /// A [`ConstId`].
126    Const,
127    /// A [`FuncId`].
128    Func,
129    /// An [`ImportId`].
130    Import,
131    /// A [`GlobalId`].
132    Global,
133    /// A [`TableId`].
134    Table,
135    /// A [`NameRef`].
136    Name,
137    /// A [`TypeRef`].
138    TypeRef,
139    /// A [`FieldIdx`].
140    Field,
141    /// An [`UpvalIdx`].
142    Upval,
143    /// A 32-bit signed immediate.
144    Imm32,
145    /// An 8-bit count (arguments, string parts).
146    Count,
147    /// A boolean flag.
148    Bool,
149    /// An [`IntTy`] modifier.
150    IntTy,
151    /// A [`FloatTy`] modifier.
152    FloatTy,
153    /// A [`Policy`] modifier.
154    Policy,
155    /// An [`IntOp`] modifier.
156    IntOp,
157    /// An [`IntConv`] modifier.
158    IntConv,
159    /// An [`IntPair`] modifier.
160    IntPair,
161    /// A [`Kind`] modifier.
162    Kind,
163    /// A [`Prim`] modifier.
164    Prim,
165}
166
167impl FieldKind {
168    /// Whether the field is a modifier (printed as a mnemonic suffix).
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// use bytecode_lang::FieldKind;
174    ///
175    /// assert!(FieldKind::Kind.is_modifier());
176    /// assert!(!FieldKind::Count.is_modifier());
177    /// ```
178    #[must_use]
179    pub const fn is_modifier(self) -> bool {
180        matches!(
181            self,
182            FieldKind::IntTy
183                | FieldKind::FloatTy
184                | FieldKind::Policy
185                | FieldKind::IntOp
186                | FieldKind::IntConv
187                | FieldKind::IntPair
188                | FieldKind::Kind
189                | FieldKind::Prim
190        )
191    }
192
193    /// The field's width in bits, which is also the width of the slot it
194    /// must occupy.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use bytecode_lang::FieldKind;
200    ///
201    /// assert_eq!(FieldKind::Reg.bits(), 16);
202    /// assert_eq!(FieldKind::Policy.bits(), 8);
203    /// ```
204    #[must_use]
205    pub const fn bits(self) -> u32 {
206        match self {
207            FieldKind::Reg
208            | FieldKind::Name
209            | FieldKind::TypeRef
210            | FieldKind::Field
211            | FieldKind::Upval => 16,
212            FieldKind::Target
213            | FieldKind::Const
214            | FieldKind::Func
215            | FieldKind::Import
216            | FieldKind::Global
217            | FieldKind::Table
218            | FieldKind::Imm32 => 32,
219            _ => 8,
220        }
221    }
222}
223
224/// One operand of an opcode: its field name, its kind, and its slot.
225///
226/// # Examples
227///
228/// ```
229/// use bytecode_lang::{FieldKind, Opcode, Slot};
230///
231/// let fields = Opcode::Mov.fields();
232/// assert_eq!(fields[0].name, "dst");
233/// assert_eq!((fields[0].kind, fields[0].slot), (FieldKind::Reg, Slot::B));
234/// ```
235#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
236pub struct FieldSpec {
237    /// The [`Inst`] field's name.
238    pub name: &'static str,
239    /// What the field is.
240    pub kind: FieldKind,
241    /// Where it lives in the instruction word.
242    pub slot: Slot,
243}
244
245/// Why eight bytes are not an instruction.
246///
247/// # Examples
248///
249/// ```
250/// use bytecode_lang::{Inst, InstError, Opcode};
251///
252/// assert_eq!(Inst::from_bytes([0xff, 0, 0, 0, 0, 0, 0, 0]), Err(InstError::UnknownOpcode(0xff)));
253/// // `nop` with a non-zero unused byte has two encodings otherwise, so it is refused.
254/// assert_eq!(Inst::from_bytes([0x00, 1, 0, 0, 0, 0, 0, 0]), Err(InstError::NonCanonical(Opcode::Nop)));
255/// ```
256#[derive(Clone, Copy, PartialEq, Eq, Debug)]
257#[non_exhaustive]
258pub enum InstError {
259    /// The opcode byte names no instruction.
260    UnknownOpcode(u8),
261    /// A modifier field holds a value its type does not define (for
262    /// example an integer type code above 7, or a boolean above 1).
263    InvalidOperand {
264        /// The instruction.
265        opcode: Opcode,
266        /// The field's name.
267        field: &'static str,
268    },
269    /// A byte the instruction does not use is not zero.
270    NonCanonical(Opcode),
271}
272
273impl fmt::Display for InstError {
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        match self {
276            InstError::UnknownOpcode(op) => write!(f, "unknown opcode {op:#04x}"),
277            InstError::InvalidOperand { opcode, field } => {
278                write!(f, "invalid `{field}` operand of `{}`", opcode.mnemonic())
279            }
280            InstError::NonCanonical(opcode) => {
281                write!(
282                    f,
283                    "`{}` has non-zero bytes in unused slots",
284                    opcode.mnemonic()
285                )
286            }
287        }
288    }
289}
290
291impl core::error::Error for InstError {}
292
293/// Rendering context: label positions and the module, when the caller has
294/// them (the module disassembler), or nothing (`Inst`'s `Display`).
295#[derive(Clone, Copy)]
296pub(crate) struct Cx<'a> {
297    /// Sorted pcs that are branch targets; `L<i>` names `labels[i]`.
298    pub(crate) labels: &'a [u32],
299    pub(crate) module: Option<&'a Module>,
300    pub(crate) func: Option<&'a Function>,
301}
302
303impl Cx<'_> {
304    pub(crate) const EMPTY: Cx<'static> = Cx {
305        labels: &[],
306        module: None,
307        func: None,
308    };
309}
310
311/// Writes ` ; a, b` comments after an instruction, starting the comment
312/// only if there is something to say.
313pub(crate) struct Note<'w> {
314    w: &'w mut dyn fmt::Write,
315    started: bool,
316}
317
318impl Note<'_> {
319    fn item(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
320        self.w.write_str(if self.started { ", " } else { "  ; " })?;
321        self.started = true;
322        self.w.write_fmt(args)
323    }
324}
325
326/// Writes `s` as a quoted, escaped string of at most 40 characters.
327pub(crate) fn write_quoted(w: &mut dyn fmt::Write, s: &str) -> fmt::Result {
328    const MAX: usize = 40;
329    match s.char_indices().nth(MAX) {
330        Some((cut, _)) => write!(w, "{:?}...", s.get(..cut).unwrap_or(s)),
331        None => write!(w, "{s:?}"),
332    }
333}
334
335/// A short description of a constant for disassembly comments.
336pub(crate) fn write_const_preview(
337    w: &mut dyn fmt::Write,
338    module: &Module,
339    c: &Const,
340) -> fmt::Result {
341    match c {
342        Const::Str(id) => match module.string(*id) {
343            Some(s) => write_quoted(w, s),
344            None => write!(w, "str {id} <invalid>"),
345        },
346        Const::Bytes(bytes) => write!(w, "bytes[{}]", bytes.len()),
347        Const::Array(items) => write!(w, "array[{}]", items.len()),
348        Const::Map(entries) => write!(w, "map[{}]", entries.len()),
349        other => write!(w, "{other}"),
350    }
351}
352
353/// A type that can sit in an instruction slot.
354pub(crate) trait Operand: Copy + fmt::Display {
355    const KIND: FieldKind;
356
357    fn to_raw(self) -> u32;
358
359    /// The value for a raw slot, or `None` if the raw value names none.
360    /// Strict: `to_raw(from_raw(r)?) == r` for every accepted `r`.
361    fn from_raw(raw: u32) -> Option<Self>;
362
363    /// Modifiers print `.value` after the mnemonic.
364    fn suffix(self, w: &mut dyn fmt::Write) -> fmt::Result {
365        if Self::KIND.is_modifier() {
366            write!(w, ".{self}")
367        } else {
368            Ok(())
369        }
370    }
371
372    fn render(self, w: &mut dyn fmt::Write, _cx: &Cx<'_>) -> fmt::Result {
373        write!(w, "{self}")
374    }
375
376    fn comment(self, _cx: &Cx<'_>, _note: &mut Note<'_>) -> fmt::Result {
377        Ok(())
378    }
379
380    /// The overflow policy this field carries, if it carries one.
381    fn overflow(self) -> Option<Overflow> {
382        None
383    }
384
385    /// The register this field names, if it is a register.
386    fn as_reg(self) -> Option<Reg> {
387        None
388    }
389
390    /// The branch target this field holds, if it is one.
391    #[inline(always)]
392    fn as_target(self) -> Option<Target> {
393        None
394    }
395
396    /// Whether this field carries `overflow = promote`. A constant `false`
397    /// for every kind but the three policy modifiers, so the generated
398    /// per-instruction test folds to a bit test or to nothing.
399    #[inline(always)]
400    fn is_promote(self) -> bool {
401        false
402    }
403}
404
405/// Whether a field name is the conventional destination register.
406fn is_dst(name: &str) -> bool {
407    name == "dst"
408}
409
410macro_rules! operand_u16 {
411    ($($ty:ident => $kind:ident),* $(,)?) => {$(
412        impl Operand for $ty {
413            const KIND: FieldKind = FieldKind::$kind;
414            fn to_raw(self) -> u32 {
415                u32::from(self.0)
416            }
417            fn from_raw(raw: u32) -> Option<Self> {
418                u16::try_from(raw).ok().map($ty)
419            }
420        }
421    )*};
422}
423
424macro_rules! operand_u32 {
425    ($($ty:ident => $kind:ident),* $(,)?) => {$(
426        impl Operand for $ty {
427            const KIND: FieldKind = FieldKind::$kind;
428            fn to_raw(self) -> u32 {
429                self.0
430            }
431            fn from_raw(raw: u32) -> Option<Self> {
432                Some($ty(raw))
433            }
434        }
435    )*};
436}
437
438macro_rules! operand_code {
439    ($($ty:ident => $kind:ident),* $(,)?) => {$(
440        impl Operand for $ty {
441            const KIND: FieldKind = FieldKind::$kind;
442            fn to_raw(self) -> u32 {
443                u32::from(self.code())
444            }
445            fn from_raw(raw: u32) -> Option<Self> {
446                u8::try_from(raw).ok().and_then($ty::from_code)
447            }
448        }
449    )*};
450}
451
452macro_rules! operand_bits {
453    ($($ty:ident => $kind:ident),* $(,)?) => {$(
454        impl Operand for $ty {
455            const KIND: FieldKind = FieldKind::$kind;
456            fn to_raw(self) -> u32 {
457                u32::from(self.bits())
458            }
459            fn from_raw(raw: u32) -> Option<Self> {
460                u8::try_from(raw).ok().and_then($ty::from_bits)
461            }
462        }
463    )*};
464}
465
466operand_u16!(UpvalIdx => Upval);
467operand_u32!(TableId => Table);
468operand_code!(IntTy => IntTy, FloatTy => FloatTy, Kind => Kind, Prim => Prim);
469operand_bits!(IntPair => IntPair);
470
471impl Operand for Reg {
472    const KIND: FieldKind = FieldKind::Reg;
473    fn to_raw(self) -> u32 {
474        u32::from(self.0)
475    }
476    fn from_raw(raw: u32) -> Option<Self> {
477        u16::try_from(raw).ok().map(Reg)
478    }
479    fn as_reg(self) -> Option<Reg> {
480        Some(self)
481    }
482}
483
484impl Operand for IntOp {
485    const KIND: FieldKind = FieldKind::IntOp;
486    fn to_raw(self) -> u32 {
487        u32::from(self.bits())
488    }
489    fn from_raw(raw: u32) -> Option<Self> {
490        u8::try_from(raw).ok().and_then(IntOp::from_bits)
491    }
492    fn overflow(self) -> Option<Overflow> {
493        Some(self.policy().overflow())
494    }
495    #[inline(always)]
496    fn is_promote(self) -> bool {
497        (self.bits() >> 3) & 3 == 3
498    }
499}
500
501impl Operand for IntConv {
502    const KIND: FieldKind = FieldKind::IntConv;
503    fn to_raw(self) -> u32 {
504        u32::from(self.bits())
505    }
506    fn from_raw(raw: u32) -> Option<Self> {
507        u8::try_from(raw).ok().and_then(IntConv::from_bits)
508    }
509    fn overflow(self) -> Option<Overflow> {
510        Some(IntConv::overflow(self))
511    }
512    #[inline(always)]
513    fn is_promote(self) -> bool {
514        self.bits() >> 6 == 3
515    }
516}
517
518impl Operand for FieldIdx {
519    const KIND: FieldKind = FieldKind::Field;
520    fn to_raw(self) -> u32 {
521        u32::from(self.0)
522    }
523    fn from_raw(raw: u32) -> Option<Self> {
524        u16::try_from(raw).ok().map(FieldIdx)
525    }
526}
527
528impl Operand for Policy {
529    const KIND: FieldKind = FieldKind::Policy;
530    fn to_raw(self) -> u32 {
531        u32::from(self.bits())
532    }
533    fn from_raw(raw: u32) -> Option<Self> {
534        u8::try_from(raw).ok().and_then(Policy::from_bits)
535    }
536    /// The default policy adds no suffix.
537    fn suffix(self, w: &mut dyn fmt::Write) -> fmt::Result {
538        if self.is_default() {
539            Ok(())
540        } else {
541            write!(w, ".{self}")
542        }
543    }
544    fn overflow(self) -> Option<Overflow> {
545        Some(Policy::overflow(self))
546    }
547    #[inline(always)]
548    fn is_promote(self) -> bool {
549        self.bits() & 3 == 3
550    }
551}
552
553impl Operand for Target {
554    const KIND: FieldKind = FieldKind::Target;
555    fn to_raw(self) -> u32 {
556        self.0
557    }
558    fn from_raw(raw: u32) -> Option<Self> {
559        Some(Target(raw))
560    }
561    #[inline(always)]
562    fn as_target(self) -> Option<Target> {
563        Some(self)
564    }
565    /// `L<n>` when the target has a label, `@pc` otherwise.
566    fn render(self, w: &mut dyn fmt::Write, cx: &Cx<'_>) -> fmt::Result {
567        match cx.labels.binary_search(&self.0) {
568            Ok(label) => write!(w, "L{label}"),
569            Err(_) => write!(w, "{self}"),
570        }
571    }
572}
573
574impl Operand for ConstId {
575    const KIND: FieldKind = FieldKind::Const;
576    fn to_raw(self) -> u32 {
577        self.0
578    }
579    fn from_raw(raw: u32) -> Option<Self> {
580        Some(ConstId(raw))
581    }
582    fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
583        let Some(module) = cx.module else {
584            return Ok(());
585        };
586        match module.constant(self) {
587            Some(c) => {
588                let mut text = alloc::string::String::new();
589                write_const_preview(&mut text, module, c)?;
590                note.item(format_args!("{text}"))
591            }
592            None => note.item(format_args!("{self} <invalid>")),
593        }
594    }
595}
596
597impl Operand for FuncId {
598    const KIND: FieldKind = FieldKind::Func;
599    fn to_raw(self) -> u32 {
600        self.0
601    }
602    fn from_raw(raw: u32) -> Option<Self> {
603        Some(FuncId(raw))
604    }
605    fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
606        let Some(module) = cx.module else {
607            return Ok(());
608        };
609        match module.function(self).and_then(|f| module.string(f.name())) {
610            Some(name) => note.item(format_args!("{name:?}")),
611            None => note.item(format_args!("{self} <invalid>")),
612        }
613    }
614}
615
616impl Operand for ImportId {
617    const KIND: FieldKind = FieldKind::Import;
618    fn to_raw(self) -> u32 {
619        self.0
620    }
621    fn from_raw(raw: u32) -> Option<Self> {
622        Some(ImportId(raw))
623    }
624    fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
625        let Some(module) = cx.module else {
626            return Ok(());
627        };
628        let names = module
629            .import(self)
630            .and_then(|i| Some((module.string(i.module)?, module.string(i.name)?)));
631        match names {
632            Some((m, n)) => note.item(format_args!("{m:?}.{n:?}")),
633            None => note.item(format_args!("{self} <invalid>")),
634        }
635    }
636}
637
638impl Operand for GlobalId {
639    const KIND: FieldKind = FieldKind::Global;
640    fn to_raw(self) -> u32 {
641        self.0
642    }
643    fn from_raw(raw: u32) -> Option<Self> {
644        Some(GlobalId(raw))
645    }
646    fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
647        let Some(module) = cx.module else {
648            return Ok(());
649        };
650        match module.global(self).and_then(|g| module.string(g.name)) {
651            Some(name) => note.item(format_args!("{name:?}")),
652            None => note.item(format_args!("{self} <invalid>")),
653        }
654    }
655}
656
657impl Operand for NameRef {
658    const KIND: FieldKind = FieldKind::Name;
659    fn to_raw(self) -> u32 {
660        u32::from(self.0)
661    }
662    fn from_raw(raw: u32) -> Option<Self> {
663        u16::try_from(raw).ok().map(NameRef)
664    }
665    fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
666        let (Some(module), Some(func)) = (cx.module, cx.func) else {
667            return Ok(());
668        };
669        match func
670            .names()
671            .get(self.index())
672            .and_then(|&s| module.string(s))
673        {
674            Some(name) => note.item(format_args!("{name:?}")),
675            None => note.item(format_args!("{self} <invalid>")),
676        }
677    }
678}
679
680impl Operand for TypeRef {
681    const KIND: FieldKind = FieldKind::TypeRef;
682    fn to_raw(self) -> u32 {
683        u32::from(self.0)
684    }
685    fn from_raw(raw: u32) -> Option<Self> {
686        u16::try_from(raw).ok().map(TypeRef)
687    }
688    fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
689        let Some(func) = cx.func else { return Ok(()) };
690        match func.type_refs().get(self.index()) {
691            Some(ty) => note.item(format_args!("{ty}")),
692            None => note.item(format_args!("{self} <invalid>")),
693        }
694    }
695}
696
697impl Operand for i32 {
698    const KIND: FieldKind = FieldKind::Imm32;
699    fn to_raw(self) -> u32 {
700        u32::from_le_bytes(self.to_le_bytes())
701    }
702    fn from_raw(raw: u32) -> Option<Self> {
703        Some(i32::from_le_bytes(raw.to_le_bytes()))
704    }
705}
706
707impl Operand for u8 {
708    const KIND: FieldKind = FieldKind::Count;
709    fn to_raw(self) -> u32 {
710        u32::from(self)
711    }
712    fn from_raw(raw: u32) -> Option<Self> {
713        u8::try_from(raw).ok()
714    }
715}
716
717impl Operand for bool {
718    const KIND: FieldKind = FieldKind::Bool;
719    fn to_raw(self) -> u32 {
720        u32::from(self)
721    }
722    fn from_raw(raw: u32) -> Option<Self> {
723        match raw {
724            0 => Some(false),
725            1 => Some(true),
726            _ => None,
727        }
728    }
729}
730
731/// The bits of the word an opcode's fields (and the opcode byte) occupy.
732const fn used_bits(fields: &[FieldSpec]) -> u64 {
733    let mut used = 0xff;
734    let mut i = 0;
735    while i < fields.len() {
736        let slot = fields[i].slot;
737        used |= slot.mask() << slot.shift();
738        i += 1;
739    }
740    used
741}
742
743/// Whether `word` sets a bit outside `used`. (A function rather than an
744/// inline expression: for opcodes that use every slot the mask is zero,
745/// which is correct but reads as a mistake to a linter.)
746#[inline(always)]
747const fn has_unused_bits(word: u64, used: u64) -> bool {
748    word & !used != 0
749}
750
751/// Reads one field from a raw word.
752#[inline]
753fn read_field<T: Operand>(
754    word: u64,
755    slot: Slot,
756    opcode: Opcode,
757    field: &'static str,
758) -> Result<T, InstError> {
759    // The mask keeps the value within the slot's width, which is at most 32
760    // bits, so the narrowing is exact.
761    let raw = ((word >> slot.shift()) & slot.mask()) as u32;
762    T::from_raw(raw).ok_or(InstError::InvalidOperand { opcode, field })
763}
764
765macro_rules! isa {
766    (
767        $(
768            $(#[$meta:meta])*
769            $code:literal $name:ident $mnemonic:literal {
770                $( $(#[$fmeta:meta])* $field:ident : $ty:ident @ $slot:ident ),* $(,)?
771            }
772        )*
773    ) => {
774        /// One LSB instruction, decoded.
775        ///
776        /// Every variant is eight bytes and `Copy`. Field order is display
777        /// order; [`Opcode::fields`] gives each field's slot in the encoded
778        /// word. The normative semantics of every instruction, every operand
779        /// type rule, and every error it can raise are in `specs/LSB.md`;
780        /// each variant's documentation summarizes them.
781        ///
782        /// *Window operands.* Instructions that need more registers than
783        /// eight bytes hold read them from consecutive registers: a call's
784        /// arguments are `dst+1 ..= dst+argc`, a closure's captures follow
785        /// its destination, `str_slice` reads its end index from `range+1`.
786        ///
787        /// The enum is deliberately exhaustive: every execution tier must
788        /// implement every instruction, and a new one is a format change.
789        ///
790        /// # Examples
791        ///
792        /// ```
793        /// use bytecode_lang::{Inst, IntOp, IntTy, Opcode, Reg};
794        ///
795        /// let add = Inst::IAdd { op: IntOp::new(IntTy::I64), dst: Reg(2), lhs: Reg(0), rhs: Reg(1) };
796        /// assert_eq!(add.opcode(), Opcode::IAdd);
797        /// assert_eq!(add.to_string(), "iadd.i64 r2, r0, r1");
798        /// assert_eq!(Inst::from_bytes(add.to_bytes()), Ok(add));
799        /// assert_eq!(core::mem::size_of::<Inst>(), 8);
800        /// ```
801        #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
802        pub enum Inst {
803            $(
804                $(#[$meta])*
805                $name { $( $(#[$fmeta])* $field: $ty ),* },
806            )*
807        }
808
809        /// The operation an [`Inst`] performs: its first encoded byte.
810        ///
811        /// # Examples
812        ///
813        /// ```
814        /// use bytecode_lang::Opcode;
815        ///
816        /// assert_eq!(Opcode::from_u8(0x01), Some(Opcode::Mov));
817        /// assert_eq!(Opcode::Mov.mnemonic(), "mov");
818        /// assert_eq!(Opcode::Mov as u8, 0x01);
819        /// assert!(Opcode::ALL.len() > 150);
820        /// ```
821        #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
822        #[repr(u8)]
823        pub enum Opcode {
824            $(
825                $(#[$meta])*
826                $name = $code,
827            )*
828        }
829
830        impl Opcode {
831            /// Every opcode, in table order.
832            pub const ALL: &'static [Opcode] = &[ $( Opcode::$name ),* ];
833
834            /// The opcode with this byte, or `None`.
835            ///
836            /// # Examples
837            ///
838            /// ```
839            /// use bytecode_lang::Opcode;
840            ///
841            /// assert_eq!(Opcode::from_u8(0xa0), Some(Opcode::Jmp));
842            /// assert_eq!(Opcode::from_u8(0xff), None);
843            /// ```
844            #[must_use]
845            pub const fn from_u8(byte: u8) -> Option<Opcode> {
846                match byte {
847                    $( $code => Some(Opcode::$name), )*
848                    _ => None,
849                }
850            }
851
852            /// The assembler name.
853            ///
854            /// # Examples
855            ///
856            /// ```
857            /// use bytecode_lang::Opcode;
858            ///
859            /// assert_eq!(Opcode::TailCall.mnemonic(), "tail_call");
860            /// ```
861            #[must_use]
862            pub const fn mnemonic(self) -> &'static str {
863                match self {
864                    $( Opcode::$name => $mnemonic, )*
865                }
866            }
867
868            /// The opcode's operands, in display order, with their slots.
869            ///
870            /// # Examples
871            ///
872            /// ```
873            /// use bytecode_lang::{FieldKind, Opcode, Slot};
874            ///
875            /// let f = Opcode::Jmp.fields();
876            /// assert_eq!(f.len(), 1);
877            /// assert_eq!((f[0].kind, f[0].slot), (FieldKind::Target, Slot::W));
878            /// ```
879            #[must_use]
880            pub const fn fields(self) -> &'static [FieldSpec] {
881                match self {
882                    $(
883                        Opcode::$name => {
884                            const FIELDS: &[FieldSpec] = &[
885                                $( FieldSpec {
886                                    name: stringify!($field),
887                                    kind: <$ty as Operand>::KIND,
888                                    slot: Slot::$slot,
889                                } ),*
890                            ];
891                            FIELDS
892                        }
893                    )*
894                }
895            }
896        }
897
898        impl Inst {
899            /// The instruction's opcode.
900            ///
901            /// # Examples
902            ///
903            /// ```
904            /// use bytecode_lang::{Inst, Opcode};
905            ///
906            /// assert_eq!(Inst::RetVoid {}.opcode(), Opcode::RetVoid);
907            /// ```
908            #[must_use]
909            pub const fn opcode(&self) -> Opcode {
910                match self {
911                    $( Inst::$name { .. } => Opcode::$name, )*
912                }
913            }
914
915            /// The canonical eight-byte encoding.
916            ///
917            /// # Examples
918            ///
919            /// ```
920            /// use bytecode_lang::{Inst, Reg};
921            ///
922            /// let mov = Inst::Mov { dst: Reg(1), src: Reg(2) };
923            /// assert_eq!(mov.to_bytes(), [0x01, 0, 1, 0, 2, 0, 0, 0]);
924            /// ```
925            #[must_use]
926            #[inline]
927            pub fn to_bytes(&self) -> [u8; 8] {
928                let word: u64 = match *self {
929                    $(
930                        Inst::$name { $( $field ),* } => {
931                            #[allow(unused_mut)]
932                            let mut word = u64::from(Opcode::$name as u8);
933                            $( word |= u64::from(Operand::to_raw($field)) << Slot::$slot.shift(); )*
934                            word
935                        }
936                    )*
937                };
938                word.to_le_bytes()
939            }
940
941            /// Decodes eight bytes. Fails on an unknown opcode, on a modifier
942            /// value its type does not define, and on non-zero bytes in
943            /// unused slots, so every instruction has exactly one encoding.
944            ///
945            /// # Errors
946            ///
947            /// [`InstError`] says which of the three applies.
948            ///
949            /// # Examples
950            ///
951            /// ```
952            /// use bytecode_lang::{Inst, InstError, Opcode};
953            ///
954            /// assert_eq!(Inst::from_bytes([0, 0, 0, 0, 0, 0, 0, 0]), Ok(Inst::Nop {}));
955            /// // `load_bool` with 2 in its boolean slot.
956            /// assert_eq!(
957            ///     Inst::from_bytes([0x06, 2, 0, 0, 0, 0, 0, 0]),
958            ///     Err(InstError::InvalidOperand { opcode: Opcode::LoadBool, field: "val" }),
959            /// );
960            /// ```
961            #[inline]
962            pub fn from_bytes(bytes: [u8; 8]) -> Result<Inst, InstError> {
963                let word = u64::from_le_bytes(bytes);
964                let [opcode, ..] = bytes;
965                Ok(match opcode {
966                    $(
967                        $code => {
968                            // Bits outside the opcode's slots must be zero;
969                            // with every field strict (`from_raw` accepts
970                            // only values it re-encodes identically), that
971                            // makes the encoding unique.
972                            const USED: u64 = used_bits(Opcode::$name.fields());
973                            if has_unused_bits(word, USED) {
974                                return Err(InstError::NonCanonical(Opcode::$name));
975                            }
976                            Inst::$name {
977                                $( $field: read_field::<$ty>(
978                                    word,
979                                    Slot::$slot,
980                                    Opcode::$name,
981                                    stringify!($field),
982                                )? ),*
983                            }
984                        }
985                    )*
986                    other => return Err(InstError::UnknownOpcode(other)),
987                })
988            }
989
990            /// The overflow policy the instruction carries (in an
991            /// [`IntOp`], [`Policy`], or [`IntConv`] modifier), or `None` for
992            /// instructions that carry none.
993            ///
994            /// # Examples
995            ///
996            /// ```
997            /// use bytecode_lang::{Inst, Overflow, Policy, Reg};
998            ///
999            /// let p = Policy::new().with_overflow(Overflow::Promote);
1000            /// let add = Inst::DAdd { dst: Reg(0), lhs: Reg(1), rhs: Reg(2), pol: p };
1001            /// assert_eq!(add.overflow(), Some(Overflow::Promote));
1002            /// assert_eq!(Inst::Nop {}.overflow(), None);
1003            /// ```
1004            #[must_use]
1005            #[allow(unused_mut, unused_variables, clippy::let_and_return)]
1006            pub fn overflow(&self) -> Option<Overflow> {
1007                match *self {
1008                    $(
1009                        Inst::$name { $( $field ),* } => {
1010                            let mut found = None;
1011                            $(
1012                                if let Some(o) = Operand::overflow($field) {
1013                                    found = Some(o);
1014                                }
1015                            )*
1016                            found
1017                        }
1018                    )*
1019                }
1020            }
1021
1022            /// What the builder checks of every instruction, in one
1023            /// dispatch: its branch target (if it is a branch) and whether
1024            /// it carries `overflow = promote`. Every arm but the branches
1025            /// and the policy-carrying instructions folds to a constant.
1026            #[inline]
1027            #[allow(unused_variables)]
1028            pub(crate) fn build_checks(&self) -> (Option<Target>, bool) {
1029                match *self {
1030                    $(
1031                        Inst::$name { $( $field ),* } => {
1032                            let target: Option<Target> = None $( .or(Operand::as_target($field)) )*;
1033                            (target, false $( || Operand::is_promote($field) )*)
1034                        }
1035                    )*
1036                }
1037            }
1038
1039            /// The register in the field named `dst`, if the instruction has one.
1040            #[allow(unused_mut, unused_variables, clippy::let_and_return)]
1041            pub(crate) fn dst(&self) -> Option<Reg> {
1042                match *self {
1043                    $(
1044                        Inst::$name { $( $field ),* } => {
1045                            let mut dst = None;
1046                            $(
1047                                if is_dst(stringify!($field)) {
1048                                    dst = Operand::as_reg($field);
1049                                }
1050                            )*
1051                            dst
1052                        }
1053                    )*
1054                }
1055            }
1056
1057            /// Renders `mnemonic.modifiers op, op ...  ; comments`.
1058            #[allow(unused_assignments, unused_mut, unused_variables)]
1059            pub(crate) fn render(&self, w: &mut dyn fmt::Write, cx: &Cx<'_>) -> fmt::Result {
1060                match *self {
1061                    $(
1062                        Inst::$name { $( $field ),* } => {
1063                            w.write_str($mnemonic)?;
1064                            $( Operand::suffix($field, w)?; )*
1065                            let mut sep = " ";
1066                            $(
1067                                if !<$ty as Operand>::KIND.is_modifier() {
1068                                    w.write_str(sep)?;
1069                                    sep = ", ";
1070                                    Operand::render($field, w, cx)?;
1071                                }
1072                            )*
1073                            let mut note = Note { w, started: false };
1074                            $( Operand::comment($field, cx, &mut note)?; )*
1075                            Ok(())
1076                        }
1077                    )*
1078                }
1079            }
1080        }
1081    };
1082}
1083
1084isa! {
1085    // ---------------------------------------------------------------------
1086    // Moves, constants, globals
1087    // ---------------------------------------------------------------------
1088
1089    /// Does nothing.
1090    0x00 Nop "nop" {}
1091
1092    /// `dst = src`. Both registers have the same declared type; the 64-bit
1093    /// slot is copied as is.
1094    0x01 Mov "mov" {
1095        /// Destination register.
1096        dst: Reg @ B,
1097        /// Source register.
1098        src: Reg @ C,
1099    }
1100
1101    /// `dst = constants[k]` in the destination's typed representation (an
1102    /// `int` constant into an integer register it fits, a `str` or `bytes`
1103    /// constant into a `str` register, an aggregate into a matching `ref`).
1104    0x02 LoadConst "load_const" {
1105        /// Destination register.
1106        dst: Reg @ B,
1107        /// The constant.
1108        k: ConstId @ W,
1109    }
1110
1111    /// `dst = constants[k]` boxed as a `dyn` value (integers become dynamic
1112    /// ints and must fit `i64`; floats become dynamic floats; aggregates
1113    /// become dynamic arrays and maps).
1114    0x03 DLoadConst "dload_const" {
1115        /// Destination `dyn` register.
1116        dst: Reg @ B,
1117        /// The constant.
1118        k: ConstId @ W,
1119    }
1120
1121    /// `dst = val` as an integer of type `ty`; the immediate is
1122    /// sign-extended and must be representable in `ty`.
1123    0x04 LoadInt "load_int" {
1124        /// Destination integer register.
1125        dst: Reg @ B,
1126        /// The immediate.
1127        val: i32 @ W,
1128        /// The destination's integer type.
1129        ty: IntTy @ A,
1130    }
1131
1132    /// `dst = val` as a dynamic integer.
1133    0x05 DLoadInt "dload_int" {
1134        /// Destination `dyn` register.
1135        dst: Reg @ B,
1136        /// The immediate.
1137        val: i32 @ W,
1138    }
1139
1140    /// `dst = val`.
1141    0x06 LoadBool "load_bool" {
1142        /// Destination `bool` register.
1143        dst: Reg @ B,
1144        /// The immediate.
1145        val: bool @ A,
1146    }
1147
1148    /// `dst = nil`: the dynamic nil, or the null `str`/`ref`.
1149    0x07 LoadNil "load_nil" {
1150        /// Destination `dyn`, `str`, or `ref` register.
1151        dst: Reg @ B,
1152    }
1153
1154    /// `dst =` a callable reference to a host function.
1155    0x08 LoadImport "load_import" {
1156        /// Destination `ref` (function type) or `dyn` register.
1157        dst: Reg @ B,
1158        /// The import.
1159        import: ImportId @ W,
1160    }
1161
1162    /// `dst = globals[global]`.
1163    0x09 GetGlobal "get_global" {
1164        /// Destination register of the global's type.
1165        dst: Reg @ B,
1166        /// The global.
1167        global: GlobalId @ W,
1168    }
1169
1170    /// `globals[global] = src`; the global must be mutable.
1171    0x0A SetGlobal "set_global" {
1172        /// The global.
1173        global: GlobalId @ W,
1174        /// Source register of the global's type.
1175        src: Reg @ B,
1176    }
1177
1178    // ---------------------------------------------------------------------
1179    // Integer arithmetic (OPS §3). Every one carries an IntOp: the operand
1180    // type and the complete policy set.
1181    // ---------------------------------------------------------------------
1182
1183    /// `dst = lhs + rhs`; overflow per `op.policy().overflow()`.
1184    0x10 IAdd "iadd" {
1185        /// Destination register.
1186        dst: Reg @ B,
1187        /// Left operand.
1188        lhs: Reg @ C,
1189        /// Right operand.
1190        rhs: Reg @ D,
1191        /// Operand type and policy.
1192        op: IntOp @ A,
1193    }
1194
1195    /// `dst = lhs - rhs`; overflow per policy.
1196    0x11 ISub "isub" {
1197        /// Destination register.
1198        dst: Reg @ B,
1199        /// Left operand.
1200        lhs: Reg @ C,
1201        /// Right operand.
1202        rhs: Reg @ D,
1203        /// Operand type and policy.
1204        op: IntOp @ A,
1205    }
1206
1207    /// `dst = lhs * rhs`; overflow per policy.
1208    0x12 IMul "imul" {
1209        /// Destination register.
1210        dst: Reg @ B,
1211        /// Left operand.
1212        lhs: Reg @ C,
1213        /// Right operand.
1214        rhs: Reg @ D,
1215        /// Operand type and policy.
1216        op: IntOp @ A,
1217    }
1218
1219    /// `dst = lhs / rhs`, truncating toward zero; `rhs == 0` per `div_zero`,
1220    /// signed `MIN / -1` per `overflow` (`wrap` gives `MIN`).
1221    0x13 IDiv "idiv" {
1222        /// Destination register.
1223        dst: Reg @ B,
1224        /// Dividend.
1225        lhs: Reg @ C,
1226        /// Divisor.
1227        rhs: Reg @ D,
1228        /// Operand type and policy.
1229        op: IntOp @ A,
1230    }
1231
1232    /// `dst = lhs rem rhs`, the sign following the dividend; `rhs == 0` per
1233    /// `div_zero`; signed `MIN rem -1` is `0`, never an error.
1234    0x14 IRem "irem" {
1235        /// Destination register.
1236        dst: Reg @ B,
1237        /// Dividend.
1238        lhs: Reg @ C,
1239        /// Divisor.
1240        rhs: Reg @ D,
1241        /// Operand type and policy.
1242        op: IntOp @ A,
1243    }
1244
1245    /// `dst = floor(lhs / rhs)` (Python `//`); zero and overflow rules as
1246    /// `idiv`.
1247    0x15 IFloorDiv "ifloor_div" {
1248        /// Destination register.
1249        dst: Reg @ B,
1250        /// Dividend.
1251        lhs: Reg @ C,
1252        /// Divisor.
1253        rhs: Reg @ D,
1254        /// Operand type and policy.
1255        op: IntOp @ A,
1256    }
1257
1258    /// `dst = lhs - rhs * floor_div(lhs, rhs)`, the sign following the
1259    /// divisor; `rhs == 0` per `div_zero`; signed `MIN floor_mod -1` is `0`.
1260    0x16 IFloorMod "ifloor_mod" {
1261        /// Destination register.
1262        dst: Reg @ B,
1263        /// Dividend.
1264        lhs: Reg @ C,
1265        /// Divisor.
1266        rhs: Reg @ D,
1267        /// Operand type and policy.
1268        op: IntOp @ A,
1269    }
1270
1271    /// `dst = lhs & rhs`.
1272    0x17 IAnd "iand" {
1273        /// Destination register.
1274        dst: Reg @ B,
1275        /// Left operand.
1276        lhs: Reg @ C,
1277        /// Right operand.
1278        rhs: Reg @ D,
1279        /// Operand type and policy.
1280        op: IntOp @ A,
1281    }
1282
1283    /// `dst = lhs | rhs`.
1284    0x18 IOr "ior" {
1285        /// Destination register.
1286        dst: Reg @ B,
1287        /// Left operand.
1288        lhs: Reg @ C,
1289        /// Right operand.
1290        rhs: Reg @ D,
1291        /// Operand type and policy.
1292        op: IntOp @ A,
1293    }
1294
1295    /// `dst = lhs ^ rhs`.
1296    0x19 IXor "ixor" {
1297        /// Destination register.
1298        dst: Reg @ B,
1299        /// Left operand.
1300        lhs: Reg @ C,
1301        /// Right operand.
1302        rhs: Reg @ D,
1303        /// Operand type and policy.
1304        op: IntOp @ A,
1305    }
1306
1307    /// `dst = lhs << rhs`; bits shifted out are discarded; an amount at least
1308    /// the width (or negative, for a signed type) per `shift`.
1309    0x1A IShl "ishl" {
1310        /// Destination register.
1311        dst: Reg @ B,
1312        /// Value to shift.
1313        lhs: Reg @ C,
1314        /// Shift amount, of the same type.
1315        rhs: Reg @ D,
1316        /// Operand type and policy.
1317        op: IntOp @ A,
1318    }
1319
1320    /// `dst = lhs >> rhs`, arithmetic for signed types and logical for
1321    /// unsigned; amount rule as `ishl`.
1322    0x1B IShr "ishr" {
1323        /// Destination register.
1324        dst: Reg @ B,
1325        /// Value to shift.
1326        lhs: Reg @ C,
1327        /// Shift amount, of the same type.
1328        rhs: Reg @ D,
1329        /// Operand type and policy.
1330        op: IntOp @ A,
1331    }
1332
1333    /// `dst = min(lhs, rhs)`.
1334    0x1C IMin "imin" {
1335        /// Destination register.
1336        dst: Reg @ B,
1337        /// Left operand.
1338        lhs: Reg @ C,
1339        /// Right operand.
1340        rhs: Reg @ D,
1341        /// Operand type and policy.
1342        op: IntOp @ A,
1343    }
1344
1345    /// `dst = max(lhs, rhs)`.
1346    0x1D IMax "imax" {
1347        /// Destination register.
1348        dst: Reg @ B,
1349        /// Left operand.
1350        lhs: Reg @ C,
1351        /// Right operand.
1352        rhs: Reg @ D,
1353        /// Operand type and policy.
1354        op: IntOp @ A,
1355    }
1356
1357    /// `dst = 0 - src`; `neg(MIN)` per `overflow`.
1358    0x1E INeg "ineg" {
1359        /// Destination register.
1360        dst: Reg @ B,
1361        /// Operand.
1362        src: Reg @ C,
1363        /// Operand type and policy.
1364        op: IntOp @ A,
1365    }
1366
1367    /// `dst = !src` (bitwise complement).
1368    0x1F INot "inot" {
1369        /// Destination register.
1370        dst: Reg @ B,
1371        /// Operand.
1372        src: Reg @ C,
1373        /// Operand type and policy.
1374        op: IntOp @ A,
1375    }
1376
1377    /// `dst = |src|`; `abs(MIN)` per `overflow`.
1378    0x20 IAbs "iabs" {
1379        /// Destination register.
1380        dst: Reg @ B,
1381        /// Operand.
1382        src: Reg @ C,
1383        /// Operand type and policy.
1384        op: IntOp @ A,
1385    }
1386
1387    /// `dst = lhs == rhs` (bool).
1388    0x21 IEq "ieq" {
1389        /// Destination `bool` register.
1390        dst: Reg @ B,
1391        /// Left operand.
1392        lhs: Reg @ C,
1393        /// Right operand.
1394        rhs: Reg @ D,
1395        /// Operand type (signedness decides ordering).
1396        ty: IntTy @ A,
1397    }
1398
1399    /// `dst = lhs != rhs` (bool).
1400    0x22 INe "ine" {
1401        /// Destination `bool` register.
1402        dst: Reg @ B,
1403        /// Left operand.
1404        lhs: Reg @ C,
1405        /// Right operand.
1406        rhs: Reg @ D,
1407        /// Operand type.
1408        ty: IntTy @ A,
1409    }
1410
1411    /// `dst = lhs < rhs` (bool), signed or unsigned per `ty`.
1412    0x23 ILt "ilt" {
1413        /// Destination `bool` register.
1414        dst: Reg @ B,
1415        /// Left operand.
1416        lhs: Reg @ C,
1417        /// Right operand.
1418        rhs: Reg @ D,
1419        /// Operand type.
1420        ty: IntTy @ A,
1421    }
1422
1423    /// `dst = lhs <= rhs` (bool).
1424    0x24 ILe "ile" {
1425        /// Destination `bool` register.
1426        dst: Reg @ B,
1427        /// Left operand.
1428        lhs: Reg @ C,
1429        /// Right operand.
1430        rhs: Reg @ D,
1431        /// Operand type.
1432        ty: IntTy @ A,
1433    }
1434
1435    /// `dst = lhs > rhs` (bool).
1436    0x25 IGt "igt" {
1437        /// Destination `bool` register.
1438        dst: Reg @ B,
1439        /// Left operand.
1440        lhs: Reg @ C,
1441        /// Right operand.
1442        rhs: Reg @ D,
1443        /// Operand type.
1444        ty: IntTy @ A,
1445    }
1446
1447    /// `dst = lhs >= rhs` (bool).
1448    0x26 IGe "ige" {
1449        /// Destination `bool` register.
1450        dst: Reg @ B,
1451        /// Left operand.
1452        lhs: Reg @ C,
1453        /// Right operand.
1454        rhs: Reg @ D,
1455        /// Operand type.
1456        ty: IntTy @ A,
1457    }
1458
1459    // ---------------------------------------------------------------------
1460    // Float arithmetic (OPS §4). Never an error: IEEE results throughout.
1461    // ---------------------------------------------------------------------
1462
1463    /// `dst = lhs + rhs` (round to nearest, ties to even).
1464    0x30 FAdd "fadd" {
1465        /// Destination register.
1466        dst: Reg @ B,
1467        /// Left operand.
1468        lhs: Reg @ C,
1469        /// Right operand.
1470        rhs: Reg @ D,
1471        /// Operand type.
1472        ty: FloatTy @ A,
1473    }
1474
1475    /// `dst = lhs - rhs`.
1476    0x31 FSub "fsub" {
1477        /// Destination register.
1478        dst: Reg @ B,
1479        /// Left operand.
1480        lhs: Reg @ C,
1481        /// Right operand.
1482        rhs: Reg @ D,
1483        /// Operand type.
1484        ty: FloatTy @ A,
1485    }
1486
1487    /// `dst = lhs * rhs`.
1488    0x32 FMul "fmul" {
1489        /// Destination register.
1490        dst: Reg @ B,
1491        /// Left operand.
1492        lhs: Reg @ C,
1493        /// Right operand.
1494        rhs: Reg @ D,
1495        /// Operand type.
1496        ty: FloatTy @ A,
1497    }
1498
1499    /// `dst = lhs / rhs`; division by zero gives ±inf or NaN.
1500    0x33 FDiv "fdiv" {
1501        /// Destination register.
1502        dst: Reg @ B,
1503        /// Dividend.
1504        lhs: Reg @ C,
1505        /// Divisor.
1506        rhs: Reg @ D,
1507        /// Operand type.
1508        ty: FloatTy @ A,
1509    }
1510
1511    /// `dst = fmod(lhs, rhs)`: the truncated remainder, sign of the dividend.
1512    0x34 FRem "frem" {
1513        /// Destination register.
1514        dst: Reg @ B,
1515        /// Dividend.
1516        lhs: Reg @ C,
1517        /// Divisor.
1518        rhs: Reg @ D,
1519        /// Operand type.
1520        ty: FloatTy @ A,
1521    }
1522
1523    /// `dst = remainder(lhs, rhs)`: IEEE 754 remainder (nearest quotient).
1524    0x35 FIeeeRem "fieee_rem" {
1525        /// Destination register.
1526        dst: Reg @ B,
1527        /// Dividend.
1528        lhs: Reg @ C,
1529        /// Divisor.
1530        rhs: Reg @ D,
1531        /// Operand type.
1532        ty: FloatTy @ A,
1533    }
1534
1535    /// `dst = min(lhs, rhs)`; NaN if either is NaN; `min(-0, +0) = -0`.
1536    0x36 FMin "fmin" {
1537        /// Destination register.
1538        dst: Reg @ B,
1539        /// Left operand.
1540        lhs: Reg @ C,
1541        /// Right operand.
1542        rhs: Reg @ D,
1543        /// Operand type.
1544        ty: FloatTy @ A,
1545    }
1546
1547    /// `dst = max(lhs, rhs)`; NaN if either is NaN; `max(-0, +0) = +0`.
1548    0x37 FMax "fmax" {
1549        /// Destination register.
1550        dst: Reg @ B,
1551        /// Left operand.
1552        lhs: Reg @ C,
1553        /// Right operand.
1554        rhs: Reg @ D,
1555        /// Operand type.
1556        ty: FloatTy @ A,
1557    }
1558
1559    /// `dst = fma(lhs, rhs, dst)`: fused multiply-add, correctly rounded.
1560    /// `dst` is both the addend and the result.
1561    0x38 FFma "ffma" {
1562        /// Addend in, result out.
1563        dst: Reg @ B,
1564        /// First factor.
1565        lhs: Reg @ C,
1566        /// Second factor.
1567        rhs: Reg @ D,
1568        /// Operand type.
1569        ty: FloatTy @ A,
1570    }
1571
1572    /// `dst = -src` (sign flip; payload preserved).
1573    0x39 FNeg "fneg" {
1574        /// Destination register.
1575        dst: Reg @ B,
1576        /// Operand.
1577        src: Reg @ C,
1578        /// Operand type.
1579        ty: FloatTy @ A,
1580    }
1581
1582    /// `dst = |src|` (sign clear; payload preserved).
1583    0x3A FAbs "fabs" {
1584        /// Destination register.
1585        dst: Reg @ B,
1586        /// Operand.
1587        src: Reg @ C,
1588        /// Operand type.
1589        ty: FloatTy @ A,
1590    }
1591
1592    /// `dst = sqrt(src)`, correctly rounded.
1593    0x3B FSqrt "fsqrt" {
1594        /// Destination register.
1595        dst: Reg @ B,
1596        /// Operand.
1597        src: Reg @ C,
1598        /// Operand type.
1599        ty: FloatTy @ A,
1600    }
1601
1602    /// `dst = floor(src)`.
1603    0x3C FFloor "ffloor" {
1604        /// Destination register.
1605        dst: Reg @ B,
1606        /// Operand.
1607        src: Reg @ C,
1608        /// Operand type.
1609        ty: FloatTy @ A,
1610    }
1611
1612    /// `dst = ceil(src)`.
1613    0x3D FCeil "fceil" {
1614        /// Destination register.
1615        dst: Reg @ B,
1616        /// Operand.
1617        src: Reg @ C,
1618        /// Operand type.
1619        ty: FloatTy @ A,
1620    }
1621
1622    /// `dst = trunc(src)` (toward zero).
1623    0x3E FTrunc "ftrunc" {
1624        /// Destination register.
1625        dst: Reg @ B,
1626        /// Operand.
1627        src: Reg @ C,
1628        /// Operand type.
1629        ty: FloatTy @ A,
1630    }
1631
1632    /// `dst = round(src)`, ties away from zero.
1633    0x3F FRound "fround" {
1634        /// Destination register.
1635        dst: Reg @ B,
1636        /// Operand.
1637        src: Reg @ C,
1638        /// Operand type.
1639        ty: FloatTy @ A,
1640    }
1641
1642    /// `dst = round(src)`, ties to even.
1643    0x40 FRoundEven "fround_even" {
1644        /// Destination register.
1645        dst: Reg @ B,
1646        /// Operand.
1647        src: Reg @ C,
1648        /// Operand type.
1649        ty: FloatTy @ A,
1650    }
1651
1652    /// `dst = lhs == rhs` (bool); false if either is NaN.
1653    0x41 FEq "feq" {
1654        /// Destination `bool` register.
1655        dst: Reg @ B,
1656        /// Left operand.
1657        lhs: Reg @ C,
1658        /// Right operand.
1659        rhs: Reg @ D,
1660        /// Operand type.
1661        ty: FloatTy @ A,
1662    }
1663
1664    /// `dst = lhs != rhs` (bool); true if either is NaN.
1665    0x42 FNe "fne" {
1666        /// Destination `bool` register.
1667        dst: Reg @ B,
1668        /// Left operand.
1669        lhs: Reg @ C,
1670        /// Right operand.
1671        rhs: Reg @ D,
1672        /// Operand type.
1673        ty: FloatTy @ A,
1674    }
1675
1676    /// `dst = lhs < rhs` (bool); false if either is NaN.
1677    0x43 FLt "flt" {
1678        /// Destination `bool` register.
1679        dst: Reg @ B,
1680        /// Left operand.
1681        lhs: Reg @ C,
1682        /// Right operand.
1683        rhs: Reg @ D,
1684        /// Operand type.
1685        ty: FloatTy @ A,
1686    }
1687
1688    /// `dst = lhs <= rhs` (bool); false if either is NaN.
1689    0x44 FLe "fle" {
1690        /// Destination `bool` register.
1691        dst: Reg @ B,
1692        /// Left operand.
1693        lhs: Reg @ C,
1694        /// Right operand.
1695        rhs: Reg @ D,
1696        /// Operand type.
1697        ty: FloatTy @ A,
1698    }
1699
1700    /// `dst = lhs > rhs` (bool); false if either is NaN.
1701    0x45 FGt "fgt" {
1702        /// Destination `bool` register.
1703        dst: Reg @ B,
1704        /// Left operand.
1705        lhs: Reg @ C,
1706        /// Right operand.
1707        rhs: Reg @ D,
1708        /// Operand type.
1709        ty: FloatTy @ A,
1710    }
1711
1712    /// `dst = lhs >= rhs` (bool); false if either is NaN.
1713    0x46 FGe "fge" {
1714        /// Destination `bool` register.
1715        dst: Reg @ B,
1716        /// Left operand.
1717        lhs: Reg @ C,
1718        /// Right operand.
1719        rhs: Reg @ D,
1720        /// Operand type.
1721        ty: FloatTy @ A,
1722    }
1723
1724    /// `dst = totalOrder(lhs, rhs)` as an `i8` of -1, 0, or 1 (IEEE 754
1725    /// total order: `-NaN < -inf < ... < -0 < +0 < ... < +inf < +NaN`).
1726    0x47 FTotalCmp "ftotal_cmp" {
1727        /// Destination `i8` register.
1728        dst: Reg @ B,
1729        /// Left operand.
1730        lhs: Reg @ C,
1731        /// Right operand.
1732        rhs: Reg @ D,
1733        /// Operand type.
1734        ty: FloatTy @ A,
1735    }
1736
1737    // ---------------------------------------------------------------------
1738    // Booleans, chars, references
1739    // ---------------------------------------------------------------------
1740
1741    /// `dst = !src` (logical).
1742    0x50 BNot "bnot" {
1743        /// Destination `bool` register.
1744        dst: Reg @ B,
1745        /// Operand.
1746        src: Reg @ C,
1747    }
1748
1749    /// `dst = lhs & rhs` (both evaluated; short-circuit is control flow).
1750    0x51 BAnd "band" {
1751        /// Destination `bool` register.
1752        dst: Reg @ B,
1753        /// Left operand.
1754        lhs: Reg @ C,
1755        /// Right operand.
1756        rhs: Reg @ D,
1757    }
1758
1759    /// `dst = lhs | rhs`.
1760    0x52 BOr "bor" {
1761        /// Destination `bool` register.
1762        dst: Reg @ B,
1763        /// Left operand.
1764        lhs: Reg @ C,
1765        /// Right operand.
1766        rhs: Reg @ D,
1767    }
1768
1769    /// `dst = lhs ^ rhs` (also boolean inequality).
1770    0x53 BXor "bxor" {
1771        /// Destination `bool` register.
1772        dst: Reg @ B,
1773        /// Left operand.
1774        lhs: Reg @ C,
1775        /// Right operand.
1776        rhs: Reg @ D,
1777    }
1778
1779    /// `dst = lhs == rhs` on `char` scalar values.
1780    0x54 CEq "ceq" {
1781        /// Destination `bool` register.
1782        dst: Reg @ B,
1783        /// Left operand.
1784        lhs: Reg @ C,
1785        /// Right operand.
1786        rhs: Reg @ D,
1787    }
1788
1789    /// `dst = lhs != rhs` on `char` scalar values.
1790    0x55 CNe "cne" {
1791        /// Destination `bool` register.
1792        dst: Reg @ B,
1793        /// Left operand.
1794        lhs: Reg @ C,
1795        /// Right operand.
1796        rhs: Reg @ D,
1797    }
1798
1799    /// `dst = lhs < rhs` on `char` scalar values.
1800    0x56 CLt "clt" {
1801        /// Destination `bool` register.
1802        dst: Reg @ B,
1803        /// Left operand.
1804        lhs: Reg @ C,
1805        /// Right operand.
1806        rhs: Reg @ D,
1807    }
1808
1809    /// `dst = lhs <= rhs` on `char` scalar values.
1810    0x57 CLe "cle" {
1811        /// Destination `bool` register.
1812        dst: Reg @ B,
1813        /// Left operand.
1814        lhs: Reg @ C,
1815        /// Right operand.
1816        rhs: Reg @ D,
1817    }
1818
1819    /// `dst = lhs > rhs` on `char` scalar values.
1820    0x58 CGt "cgt" {
1821        /// Destination `bool` register.
1822        dst: Reg @ B,
1823        /// Left operand.
1824        lhs: Reg @ C,
1825        /// Right operand.
1826        rhs: Reg @ D,
1827    }
1828
1829    /// `dst = lhs >= rhs` on `char` scalar values.
1830    0x59 CGe "cge" {
1831        /// Destination `bool` register.
1832        dst: Reg @ B,
1833        /// Left operand.
1834        lhs: Reg @ C,
1835        /// Right operand.
1836        rhs: Reg @ D,
1837    }
1838
1839    /// `dst = lhs` and `rhs` are the same object (or both `nil`). Operands
1840    /// are `str` or `ref`; string contents are not compared.
1841    0x5A RefEq "ref_eq" {
1842        /// Destination `bool` register.
1843        dst: Reg @ B,
1844        /// Left operand.
1845        lhs: Reg @ C,
1846        /// Right operand.
1847        rhs: Reg @ D,
1848    }
1849
1850    // ---------------------------------------------------------------------
1851    // Conversions (OPS §5)
1852    // ---------------------------------------------------------------------
1853
1854    /// `dst = src` converted between integer types: exact if it fits, else
1855    /// per the conversion's `overflow` (`wrap` truncates or sign-extends).
1856    0x60 IntCast "int_cast" {
1857        /// Destination register of type `conv.to()`.
1858        dst: Reg @ B,
1859        /// Source register of type `conv.from()`.
1860        src: Reg @ C,
1861        /// Source type, destination type, overflow policy.
1862        conv: IntConv @ A,
1863    }
1864
1865    /// Zero-extends a narrower integer to a wider one; never an error.
1866    0x61 Zext "zext" {
1867        /// Destination register.
1868        dst: Reg @ B,
1869        /// Source register.
1870        src: Reg @ C,
1871        /// Source and destination types (destination at least as wide).
1872        pair: IntPair @ A,
1873    }
1874
1875    /// Sign-extends a narrower integer to a wider one; never an error.
1876    0x62 Sext "sext" {
1877        /// Destination register.
1878        dst: Reg @ B,
1879        /// Source register.
1880        src: Reg @ C,
1881        /// Source and destination types (destination at least as wide).
1882        pair: IntPair @ A,
1883    }
1884
1885    /// Keeps the low bits of a wider integer; never an error.
1886    0x63 Trunc "trunc" {
1887        /// Destination register.
1888        dst: Reg @ B,
1889        /// Source register.
1890        src: Reg @ C,
1891        /// Source and destination types (destination at most as wide).
1892        pair: IntPair @ A,
1893    }
1894
1895    /// `dst = src` as `f32`, rounded to nearest, ties to even.
1896    0x64 IntToF32 "int_to_f32" {
1897        /// Destination `f32` register.
1898        dst: Reg @ B,
1899        /// Source integer register.
1900        src: Reg @ C,
1901        /// The source's integer type.
1902        ty: IntTy @ A,
1903    }
1904
1905    /// `dst = src` as `f64`, rounded to nearest, ties to even.
1906    0x65 IntToF64 "int_to_f64" {
1907        /// Destination `f64` register.
1908        dst: Reg @ B,
1909        /// Source integer register.
1910        src: Reg @ C,
1911        /// The source's integer type.
1912        ty: IntTy @ A,
1913    }
1914
1915    /// `dst = src` truncated toward zero to the integer type `op.ty()`;
1916    /// NaN or out of range per `op.policy().float_to_int()`.
1917    0x66 F32ToInt "f32_to_int" {
1918        /// Destination integer register.
1919        dst: Reg @ B,
1920        /// Source `f32` register.
1921        src: Reg @ C,
1922        /// Destination type and policy.
1923        op: IntOp @ A,
1924    }
1925
1926    /// `dst = src` truncated toward zero to the integer type `op.ty()`;
1927    /// NaN or out of range per `op.policy().float_to_int()`.
1928    0x67 F64ToInt "f64_to_int" {
1929        /// Destination integer register.
1930        dst: Reg @ B,
1931        /// Source `f64` register.
1932        src: Reg @ C,
1933        /// Destination type and policy.
1934        op: IntOp @ A,
1935    }
1936
1937    /// `dst = src` widened to `f64` (exact).
1938    0x68 F32ToF64 "f32_to_f64" {
1939        /// Destination `f64` register.
1940        dst: Reg @ B,
1941        /// Source `f32` register.
1942        src: Reg @ C,
1943    }
1944
1945    /// `dst = src` narrowed to `f32`, rounded to nearest, ties to even.
1946    0x69 F64ToF32 "f64_to_f32" {
1947        /// Destination `f32` register.
1948        dst: Reg @ B,
1949        /// Source `f64` register.
1950        src: Reg @ C,
1951    }
1952
1953    /// `dst` = the IEEE bits of `src` as an integer of the same width
1954    /// (`ty` is a 32- or 64-bit integer type).
1955    0x6A FloatToBits "float_to_bits" {
1956        /// Destination integer register.
1957        dst: Reg @ B,
1958        /// Source float register.
1959        src: Reg @ C,
1960        /// The destination's integer type.
1961        ty: IntTy @ A,
1962    }
1963
1964    /// `dst` = the float whose IEEE bits are `src` (`ty` is a 32- or 64-bit
1965    /// integer type; the float has the same width).
1966    0x6B BitsToFloat "bits_to_float" {
1967        /// Destination float register.
1968        dst: Reg @ B,
1969        /// Source integer register.
1970        src: Reg @ C,
1971        /// The source's integer type.
1972        ty: IntTy @ A,
1973    }
1974
1975    /// `dst = char(src)`; `InvalidChar` (E0005) if `src` (a `u32`) is not a
1976    /// Unicode scalar value.
1977    0x6C CharFromU32 "char_from_u32" {
1978        /// Destination `char` register.
1979        dst: Reg @ B,
1980        /// Source `u32` register.
1981        src: Reg @ C,
1982    }
1983
1984    /// `dst = src` as its `u32` scalar value. Total; an LSB addition to OPS.
1985    0x6D CharToU32 "char_to_u32" {
1986        /// Destination `u32` register.
1987        dst: Reg @ B,
1988        /// Source `char` register.
1989        src: Reg @ C,
1990    }
1991
1992    /// `dst = 1` if `src` is true, else `0`.
1993    0x6E BoolToInt "bool_to_int" {
1994        /// Destination integer register.
1995        dst: Reg @ B,
1996        /// Source `bool` register.
1997        src: Reg @ C,
1998        /// The destination's integer type.
1999        ty: IntTy @ A,
2000    }
2001
2002    // ---------------------------------------------------------------------
2003    // Dynamic values. Operands and results are `dyn`. Numbers take a fast
2004    // path per OPS (int is i64, mixed int/float computes in f64); every
2005    // other operand combination calls the module's hook or raises TypeError.
2006    // ---------------------------------------------------------------------
2007
2008    /// Dynamic `+`.
2009    0x70 DAdd "dadd" {
2010        /// Destination register.
2011        dst: Reg @ B,
2012        /// Left operand.
2013        lhs: Reg @ C,
2014        /// Right operand.
2015        rhs: Reg @ D,
2016        /// Policy for the integer path.
2017        pol: Policy @ A,
2018    }
2019
2020    /// Dynamic `-`.
2021    0x71 DSub "dsub" {
2022        /// Destination register.
2023        dst: Reg @ B,
2024        /// Left operand.
2025        lhs: Reg @ C,
2026        /// Right operand.
2027        rhs: Reg @ D,
2028        /// Policy for the integer path.
2029        pol: Policy @ A,
2030    }
2031
2032    /// Dynamic `*`.
2033    0x72 DMul "dmul" {
2034        /// Destination register.
2035        dst: Reg @ B,
2036        /// Left operand.
2037        lhs: Reg @ C,
2038        /// Right operand.
2039        rhs: Reg @ D,
2040        /// Policy for the integer path.
2041        pol: Policy @ A,
2042    }
2043
2044    /// Dynamic division: truncating for two ints, IEEE otherwise.
2045    0x73 DDiv "ddiv" {
2046        /// Destination register.
2047        dst: Reg @ B,
2048        /// Dividend.
2049        lhs: Reg @ C,
2050        /// Divisor.
2051        rhs: Reg @ D,
2052        /// Policy for the integer path.
2053        pol: Policy @ A,
2054    }
2055
2056    /// Dynamic remainder: `irem` for two ints, `frem` otherwise.
2057    0x74 DRem "drem" {
2058        /// Destination register.
2059        dst: Reg @ B,
2060        /// Dividend.
2061        lhs: Reg @ C,
2062        /// Divisor.
2063        rhs: Reg @ D,
2064        /// Policy for the integer path.
2065        pol: Policy @ A,
2066    }
2067
2068    /// Dynamic floor division (Python `//`).
2069    0x75 DFloorDiv "dfloor_div" {
2070        /// Destination register.
2071        dst: Reg @ B,
2072        /// Dividend.
2073        lhs: Reg @ C,
2074        /// Divisor.
2075        rhs: Reg @ D,
2076        /// Policy for the integer path.
2077        pol: Policy @ A,
2078    }
2079
2080    /// Dynamic floor modulo (Python `%`).
2081    0x76 DFloorMod "dfloor_mod" {
2082        /// Destination register.
2083        dst: Reg @ B,
2084        /// Dividend.
2085        lhs: Reg @ C,
2086        /// Divisor.
2087        rhs: Reg @ D,
2088        /// Policy for the integer path.
2089        pol: Policy @ A,
2090    }
2091
2092    /// Dynamic bitwise and (ints only, else hook).
2093    0x77 DAnd "dand" {
2094        /// Destination register.
2095        dst: Reg @ B,
2096        /// Left operand.
2097        lhs: Reg @ C,
2098        /// Right operand.
2099        rhs: Reg @ D,
2100        /// Policy for the integer path.
2101        pol: Policy @ A,
2102    }
2103
2104    /// Dynamic bitwise or.
2105    0x78 DOr "dor" {
2106        /// Destination register.
2107        dst: Reg @ B,
2108        /// Left operand.
2109        lhs: Reg @ C,
2110        /// Right operand.
2111        rhs: Reg @ D,
2112        /// Policy for the integer path.
2113        pol: Policy @ A,
2114    }
2115
2116    /// Dynamic bitwise xor.
2117    0x79 DXor "dxor" {
2118        /// Destination register.
2119        dst: Reg @ B,
2120        /// Left operand.
2121        lhs: Reg @ C,
2122        /// Right operand.
2123        rhs: Reg @ D,
2124        /// Policy for the integer path.
2125        pol: Policy @ A,
2126    }
2127
2128    /// Dynamic shift left (64-bit; amount rule per `shift`).
2129    0x7A DShl "dshl" {
2130        /// Destination register.
2131        dst: Reg @ B,
2132        /// Value to shift.
2133        lhs: Reg @ C,
2134        /// Shift amount.
2135        rhs: Reg @ D,
2136        /// Policy for the integer path.
2137        pol: Policy @ A,
2138    }
2139
2140    /// Dynamic arithmetic shift right.
2141    0x7B DShr "dshr" {
2142        /// Destination register.
2143        dst: Reg @ B,
2144        /// Value to shift.
2145        lhs: Reg @ C,
2146        /// Shift amount.
2147        rhs: Reg @ D,
2148        /// Policy for the integer path.
2149        pol: Policy @ A,
2150    }
2151
2152    /// Dynamic negation.
2153    0x7C DNeg "dneg" {
2154        /// Destination register.
2155        dst: Reg @ B,
2156        /// Operand.
2157        src: Reg @ C,
2158        /// Policy for the integer path.
2159        pol: Policy @ A,
2160    }
2161
2162    /// Dynamic bitwise complement (ints only, else hook).
2163    0x7D DNot "dnot" {
2164        /// Destination register.
2165        dst: Reg @ B,
2166        /// Operand.
2167        src: Reg @ C,
2168        /// Policy for the integer path.
2169        pol: Policy @ A,
2170    }
2171
2172    /// Dynamic equality into a `bool` register (numbers exactly, strings by
2173    /// content, other kinds through the `eq` hook or by identity).
2174    0x7E DEq "deq" {
2175        /// Destination `bool` register.
2176        dst: Reg @ B,
2177        /// Left operand.
2178        lhs: Reg @ C,
2179        /// Right operand.
2180        rhs: Reg @ D,
2181    }
2182
2183    /// Dynamic inequality: the negation of `deq`.
2184    0x7F DNe "dne" {
2185        /// Destination `bool` register.
2186        dst: Reg @ B,
2187        /// Left operand.
2188        lhs: Reg @ C,
2189        /// Right operand.
2190        rhs: Reg @ D,
2191    }
2192
2193    /// Dynamic `<` into a `bool` register (numbers, strings bytewise, chars;
2194    /// else the `lt` hook or TypeError).
2195    0x80 DLt "dlt" {
2196        /// Destination `bool` register.
2197        dst: Reg @ B,
2198        /// Left operand.
2199        lhs: Reg @ C,
2200        /// Right operand.
2201        rhs: Reg @ D,
2202    }
2203
2204    /// Dynamic `<=`.
2205    0x81 DLe "dle" {
2206        /// Destination `bool` register.
2207        dst: Reg @ B,
2208        /// Left operand.
2209        lhs: Reg @ C,
2210        /// Right operand.
2211        rhs: Reg @ D,
2212    }
2213
2214    /// Dynamic `>` (`lt` with the operands swapped).
2215    0x82 DGt "dgt" {
2216        /// Destination `bool` register.
2217        dst: Reg @ B,
2218        /// Left operand.
2219        lhs: Reg @ C,
2220        /// Right operand.
2221        rhs: Reg @ D,
2222    }
2223
2224    /// Dynamic `>=` (`le` with the operands swapped).
2225    0x83 DGe "dge" {
2226        /// Destination `bool` register.
2227        dst: Reg @ B,
2228        /// Left operand.
2229        lhs: Reg @ C,
2230        /// Right operand.
2231        rhs: Reg @ D,
2232    }
2233
2234    /// `dst` = the truthiness of `src` as a `bool`: nil is false, bools are
2235    /// themselves, numbers are true unless zero; other kinds go to the
2236    /// `truthy` hook, or are true unless an empty string, array, or map.
2237    0x84 DTruthy "dtruthy" {
2238        /// Destination `bool` register.
2239        dst: Reg @ B,
2240        /// Operand.
2241        src: Reg @ C,
2242    }
2243
2244    /// Dynamic string concatenation (two strings, else the `concat` hook).
2245    0x85 DConcat "dconcat" {
2246        /// Destination register.
2247        dst: Reg @ B,
2248        /// Left operand.
2249        lhs: Reg @ C,
2250        /// Right operand.
2251        rhs: Reg @ D,
2252    }
2253
2254    /// `dst = src` boxed into a `dyn` (`from` names `src`'s static type;
2255    /// `u64` above `i64::MAX` raises ArithOverflow; `str`/`ref` are a no-op).
2256    0x86 ToDyn "to_dyn" {
2257        /// Destination `dyn` register.
2258        dst: Reg @ B,
2259        /// Source register.
2260        src: Reg @ C,
2261        /// The source's static type.
2262        from: Prim @ A,
2263    }
2264
2265    /// `dst = src` unboxed to the static type `to`; TypeError if the kind
2266    /// does not match, ArithOverflow if an int does not fit. (`ref` targets
2267    /// use `cast`.)
2268    0x87 FromDyn "from_dyn" {
2269        /// Destination register.
2270        dst: Reg @ B,
2271        /// Source `dyn` register.
2272        src: Reg @ C,
2273        /// The destination's static type.
2274        to: Prim @ A,
2275    }
2276
2277    /// `dst` = the [`Kind`] code of `src`, as a `u8`.
2278    0x88 TypeOf "type_of" {
2279        /// Destination `u8` register.
2280        dst: Reg @ B,
2281        /// Source `dyn`, `str`, or `ref` register.
2282        src: Reg @ C,
2283    }
2284
2285    /// `dst` = whether `src` is of kind `kind`.
2286    0x89 IsKind "is_kind" {
2287        /// Destination `bool` register.
2288        dst: Reg @ B,
2289        /// Source `dyn`, `str`, or `ref` register.
2290        src: Reg @ C,
2291        /// The kind to test.
2292        kind: Kind @ A,
2293    }
2294
2295    /// `dst` = whether `src` is a non-nil instance of struct type `ty` or of
2296    /// a type that has it as an ancestor.
2297    0x8A IsType "is_type" {
2298        /// Destination `bool` register.
2299        dst: Reg @ B,
2300        /// Source `dyn` or `ref` register.
2301        src: Reg @ C,
2302        /// The struct type.
2303        ty: TypeRef @ D,
2304    }
2305
2306    /// `dst = src` as `ref ty`; TypeError if `src` is not nil and not an
2307    /// instance of `ty` (or a descendant).
2308    0x8B Cast "cast" {
2309        /// Destination `ref ty` register.
2310        dst: Reg @ B,
2311        /// Source `dyn` or `ref` register.
2312        src: Reg @ C,
2313        /// The target type.
2314        ty: TypeRef @ D,
2315    }
2316
2317    /// Dynamic indexing `dst = obj[key]`: arrays by int, maps by key, strings
2318    /// by int (the byte); misses and other kinds go to the `get_index` hook.
2319    0x8C DGetIndex "dget_index" {
2320        /// Destination `dyn` register.
2321        dst: Reg @ B,
2322        /// The indexed value.
2323        obj: Reg @ C,
2324        /// The key.
2325        key: Reg @ D,
2326    }
2327
2328    /// Dynamic indexed store `obj[key] = src`.
2329    0x8D DSetIndex "dset_index" {
2330        /// The indexed value.
2331        obj: Reg @ B,
2332        /// The key.
2333        key: Reg @ C,
2334        /// The value to store.
2335        src: Reg @ D,
2336    }
2337
2338    /// Dynamic property read `dst = obj.name`: a struct field or method by
2339    /// name, a map entry with a string key, else the `get_prop` hook.
2340    0x8E GetProp "get_prop" {
2341        /// Destination `dyn` register.
2342        dst: Reg @ B,
2343        /// The object.
2344        obj: Reg @ C,
2345        /// The property name.
2346        name: NameRef @ D,
2347    }
2348
2349    /// Dynamic property write `obj.name = src`.
2350    0x8F SetProp "set_prop" {
2351        /// The object.
2352        obj: Reg @ B,
2353        /// The property name.
2354        name: NameRef @ D,
2355        /// The value to store.
2356        src: Reg @ C,
2357    }
2358
2359    /// `dst` = whether `obj` has property `name`.
2360    0x90 HasProp "has_prop" {
2361        /// Destination `bool` register.
2362        dst: Reg @ B,
2363        /// The object.
2364        obj: Reg @ C,
2365        /// The property name.
2366        name: NameRef @ D,
2367    }
2368
2369    /// Dynamic call: `dst = callee(dst+1, ..., dst+argc)` with `dyn`
2370    /// arguments, converted to the callee's parameter types; TypeError on an
2371    /// arity or type mismatch; non-callables go to the `call` hook.
2372    0x91 DCall "dcall" {
2373        /// Result register (`dyn`); arguments follow it.
2374        dst: Reg @ B,
2375        /// The callee.
2376        callee: Reg @ C,
2377        /// The number of arguments.
2378        argc: u8 @ A,
2379    }
2380
2381    /// `dst` = a dynamic iterator over `src` (array or map; else the `iter`
2382    /// hook).
2383    0x92 DIterNew "diter_new" {
2384        /// Destination `dyn` register.
2385        dst: Reg @ B,
2386        /// The value to iterate.
2387        src: Reg @ C,
2388    }
2389
2390    /// `dst` = the length of `src` as an `i64` (string bytes, array or map
2391    /// entries; else the `len` hook).
2392    0x93 DLen "dlen" {
2393        /// Destination `i64` register.
2394        dst: Reg @ B,
2395        /// The value.
2396        src: Reg @ C,
2397    }
2398
2399    /// Dynamic logical not (PHP `!`, Python `not`): `dst = !truthy(src)` as a
2400    /// `bool`, with exactly the truthiness rules of `dtruthy` (the `truthy`
2401    /// hook included). `dnot` is the bitwise `~`.
2402    0x94 DLNot "dlnot" {
2403        /// Destination `bool` register.
2404        dst: Reg @ B,
2405        /// Operand.
2406        src: Reg @ C,
2407    }
2408
2409    // ---------------------------------------------------------------------
2410    // Control flow, calls, exceptions
2411    // ---------------------------------------------------------------------
2412
2413    /// Continue at `target`.
2414    0xA0 Jmp "jmp" {
2415        /// The next instruction.
2416        target: Target @ W,
2417    }
2418
2419    /// Continue at `target` if `cond` is true, else fall through.
2420    0xA1 JmpIf "jmp_if" {
2421        /// A `bool` register.
2422        cond: Reg @ B,
2423        /// Taken when `cond` is true.
2424        target: Target @ W,
2425    }
2426
2427    /// Continue at `target` if `cond` is false, else fall through.
2428    0xA2 JmpIfNot "jmp_if_not" {
2429        /// A `bool` register.
2430        cond: Reg @ B,
2431        /// Taken when `cond` is false.
2432        target: Target @ W,
2433    }
2434
2435    /// Continue at `tables[table].targets[src]` if `0 <= src < len`, else at
2436    /// the table's default.
2437    0xA3 Switch "switch" {
2438        /// The selector.
2439        src: Reg @ B,
2440        /// The jump table.
2441        table: TableId @ W,
2442        /// The selector's integer type.
2443        ty: IntTy @ A,
2444    }
2445
2446    /// `dst = func(dst+1, ..., dst+argc)`; a call to a function with no
2447    /// result leaves `dst` untouched.
2448    0xA4 Call "call" {
2449        /// Result register; arguments follow it.
2450        dst: Reg @ B,
2451        /// The callee.
2452        func: FuncId @ W,
2453        /// The number of arguments.
2454        argc: u8 @ A,
2455    }
2456
2457    /// `dst = callee(dst+1, ..., dst+argc)` through a typed function
2458    /// reference (`ref` of a function type).
2459    0xA5 CallIndirect "call_indirect" {
2460        /// Result register; arguments follow it.
2461        dst: Reg @ B,
2462        /// The function reference.
2463        callee: Reg @ C,
2464        /// The number of arguments.
2465        argc: u8 @ A,
2466    }
2467
2468    /// `dst = import(dst+1, ..., dst+argc)`: a host call.
2469    0xA6 CallImport "call_import" {
2470        /// Result register; arguments follow it.
2471        dst: Reg @ B,
2472        /// The host function.
2473        import: ImportId @ W,
2474        /// The number of arguments.
2475        argc: u8 @ A,
2476    }
2477
2478    /// Replaces the running frame with a call to `func(args, ...,
2479    /// args+argc-1)`; the callee's results equal the caller's.
2480    0xA7 TailCall "tail_call" {
2481        /// The callee.
2482        func: FuncId @ W,
2483        /// First argument register.
2484        args: Reg @ B,
2485        /// The number of arguments.
2486        argc: u8 @ A,
2487    }
2488
2489    /// Tail call through a typed function reference.
2490    0xA8 TailCallIndirect "tail_call_indirect" {
2491        /// The function reference.
2492        callee: Reg @ B,
2493        /// First argument register.
2494        args: Reg @ C,
2495        /// The number of arguments.
2496        argc: u8 @ A,
2497    }
2498
2499    /// Return `src` to the caller.
2500    0xA9 Ret "ret" {
2501        /// The result register.
2502        src: Reg @ B,
2503    }
2504
2505    /// Return with no result.
2506    0xAA RetVoid "ret_void" {}
2507
2508    /// Raise `src` as an error: control moves to the innermost covering
2509    /// handler, in this frame or a caller's.
2510    0xAB Throw "throw" {
2511        /// The error value (`dyn` or `ref`).
2512        src: Reg @ B,
2513    }
2514
2515    /// `dst` = the numeric [`ErrorKind`](crate::ErrorKind) code of a caught
2516    /// runtime error, or 0 for any other value.
2517    0xAC ErrCode "err_code" {
2518        /// Destination `u32` register.
2519        dst: Reg @ B,
2520        /// The caught value.
2521        src: Reg @ C,
2522    }
2523
2524    /// A point where the garbage collector may run and fuel is charged.
2525    /// Calls and allocations are safepoints too; every loop must pass one.
2526    0xAD Safepoint "safepoint" {}
2527
2528    /// Abort the run with `Unreachable` (E0109).
2529    0xAE Unreachable "unreachable" {}
2530
2531    // ---------------------------------------------------------------------
2532    // Closures and cells
2533    // ---------------------------------------------------------------------
2534
2535    /// `dst` = a closure of `func` capturing, by value, the registers
2536    /// `dst+1 ..= dst+n` (`n` = the function's capture count).
2537    0xB0 MakeClosure "make_closure" {
2538        /// Destination register; the captured values follow it.
2539        dst: Reg @ B,
2540        /// The function.
2541        func: FuncId @ W,
2542    }
2543
2544    /// `dst` = the running closure's captured value `idx`.
2545    0xB1 GetUpval "get_upval" {
2546        /// Destination register of the capture's type.
2547        dst: Reg @ B,
2548        /// The capture.
2549        idx: UpvalIdx @ C,
2550    }
2551
2552    /// `dst` = a new cell of type `ty` holding `src`.
2553    0xB2 NewCell "new_cell" {
2554        /// Destination `ref` register.
2555        dst: Reg @ B,
2556        /// The initial value.
2557        src: Reg @ C,
2558        /// The cell type.
2559        ty: TypeRef @ D,
2560    }
2561
2562    /// `dst` = the value in `cell`.
2563    0xB3 CellGet "cell_get" {
2564        /// Destination register.
2565        dst: Reg @ B,
2566        /// The cell.
2567        cell: Reg @ C,
2568    }
2569
2570    /// Store `src` into `cell`.
2571    0xB4 CellSet "cell_set" {
2572        /// The cell.
2573        cell: Reg @ B,
2574        /// The value.
2575        src: Reg @ C,
2576    }
2577
2578    // ---------------------------------------------------------------------
2579    // Typed heap objects. A nil object raises NullReference (E0101).
2580    // ---------------------------------------------------------------------
2581
2582    /// `dst` = a new instance of struct type `ty`, every field at its
2583    /// type's default.
2584    0xC0 NewStruct "new_struct" {
2585        /// Destination `ref` register.
2586        dst: Reg @ B,
2587        /// The struct type.
2588        ty: TypeRef @ C,
2589    }
2590
2591    /// `dst = obj.field`.
2592    0xC1 GetField "get_field" {
2593        /// Destination register of the field's type.
2594        dst: Reg @ B,
2595        /// The instance.
2596        obj: Reg @ C,
2597        /// The field slot.
2598        field: FieldIdx @ D,
2599    }
2600
2601    /// `obj.field = src`.
2602    0xC2 SetField "set_field" {
2603        /// The instance.
2604        obj: Reg @ B,
2605        /// The field slot.
2606        field: FieldIdx @ D,
2607        /// The value.
2608        src: Reg @ C,
2609    }
2610
2611    /// `dst` = a new array of type `ty` with `len` (an `i64`, at least 0)
2612    /// default elements.
2613    0xC3 NewArray "new_array" {
2614        /// Destination `ref` register.
2615        dst: Reg @ B,
2616        /// The length.
2617        len: Reg @ C,
2618        /// The array type.
2619        ty: TypeRef @ D,
2620    }
2621
2622    /// `dst` = the array's length (`i64`).
2623    0xC4 ArrayLen "array_len" {
2624        /// Destination `i64` register.
2625        dst: Reg @ B,
2626        /// The array.
2627        arr: Reg @ C,
2628    }
2629
2630    /// `dst = arr[idx]`; IndexOutOfBounds unless `0 <= idx < len`.
2631    0xC5 ArrayGet "array_get" {
2632        /// Destination register of the element type.
2633        dst: Reg @ B,
2634        /// The array.
2635        arr: Reg @ C,
2636        /// The `i64` index.
2637        idx: Reg @ D,
2638    }
2639
2640    /// `arr[idx] = src`; IndexOutOfBounds unless `0 <= idx < len`.
2641    0xC6 ArraySet "array_set" {
2642        /// The array.
2643        arr: Reg @ B,
2644        /// The `i64` index.
2645        idx: Reg @ C,
2646        /// The value.
2647        src: Reg @ D,
2648    }
2649
2650    /// Append `src` to the array.
2651    0xC7 ArrayPush "array_push" {
2652        /// The array.
2653        arr: Reg @ B,
2654        /// The value.
2655        src: Reg @ C,
2656    }
2657
2658    /// `dst` = the removed last element; IndexOutOfBounds if empty.
2659    0xC8 ArrayPop "array_pop" {
2660        /// Destination register of the element type.
2661        dst: Reg @ B,
2662        /// The array.
2663        arr: Reg @ C,
2664    }
2665
2666    /// `dst` = a new, empty map of type `ty`.
2667    0xC9 NewMap "new_map" {
2668        /// Destination `ref` register.
2669        dst: Reg @ B,
2670        /// The map type.
2671        ty: TypeRef @ C,
2672    }
2673
2674    /// `dst` = the number of entries (`i64`).
2675    0xCA MapLen "map_len" {
2676        /// Destination `i64` register.
2677        dst: Reg @ B,
2678        /// The map.
2679        map: Reg @ C,
2680    }
2681
2682    /// `dst = map[key]`; KeyNotFound if absent.
2683    0xCB MapGet "map_get" {
2684        /// Destination register of the value type.
2685        dst: Reg @ B,
2686        /// The map.
2687        map: Reg @ C,
2688        /// The key.
2689        key: Reg @ D,
2690    }
2691
2692    /// `dst = map[key]`, or `nil` if absent (value type `dyn`, `str`, or
2693    /// `ref`).
2694    0xCC MapFind "map_find" {
2695        /// Destination register of the value type.
2696        dst: Reg @ B,
2697        /// The map.
2698        map: Reg @ C,
2699        /// The key.
2700        key: Reg @ D,
2701    }
2702
2703    /// `dst` = whether `key` is present.
2704    0xCD MapHas "map_has" {
2705        /// Destination `bool` register.
2706        dst: Reg @ B,
2707        /// The map.
2708        map: Reg @ C,
2709        /// The key.
2710        key: Reg @ D,
2711    }
2712
2713    /// `map[key] = src`: a new key goes last; an existing key keeps its
2714    /// position.
2715    0xCE MapSet "map_set" {
2716        /// The map.
2717        map: Reg @ B,
2718        /// The key.
2719        key: Reg @ C,
2720        /// The value.
2721        src: Reg @ D,
2722    }
2723
2724    /// Remove `key` if present; the other entries keep their order.
2725    0xCF MapDel "map_del" {
2726        /// The map.
2727        map: Reg @ B,
2728        /// The key.
2729        key: Reg @ C,
2730    }
2731
2732    /// Append `src` under the next integer key (PHP `$a[] = v`): one more
2733    /// than the largest integer key ever inserted, or 0.
2734    0xD0 MapPush "map_push" {
2735        /// The map.
2736        map: Reg @ B,
2737        /// The value.
2738        src: Reg @ C,
2739    }
2740
2741    /// `dst` = a typed iterator over the array or map `src`.
2742    0xD1 IterNew "iter_new" {
2743        /// Destination `ref` (iterator type) register.
2744        dst: Reg @ B,
2745        /// The collection.
2746        src: Reg @ C,
2747    }
2748
2749    /// Advance: if another element exists, `val` = its value and `has` =
2750    /// true; else `has` = false and `val` is untouched.
2751    0xD2 IterNext "iter_next" {
2752        /// Destination `bool` register.
2753        has: Reg @ B,
2754        /// The iterator.
2755        iter: Reg @ C,
2756        /// Destination register of the value type.
2757        val: Reg @ D,
2758    }
2759
2760    /// `dst` = the key of the element the last `iter_next` produced.
2761    0xD3 IterKey "iter_key" {
2762        /// Destination register of the key type.
2763        dst: Reg @ B,
2764        /// The iterator.
2765        iter: Reg @ C,
2766    }
2767
2768    /// `dst` = a shallow copy of `src` (array, map, struct, or cell; a new
2769    /// identity, so value semantics such as PHP's array assignment are
2770    /// explicit; implementations may copy lazily). Strings and callables
2771    /// are returned as is.
2772    0xD4 Dup "dup" {
2773        /// Destination register.
2774        dst: Reg @ B,
2775        /// Source register.
2776        src: Reg @ C,
2777    }
2778
2779    // ---------------------------------------------------------------------
2780    // Strings (immutable byte strings)
2781    // ---------------------------------------------------------------------
2782
2783    /// `dst` = the length in bytes (`i64`).
2784    0xE0 StrLen "str_len" {
2785        /// Destination `i64` register.
2786        dst: Reg @ B,
2787        /// The string.
2788        s: Reg @ C,
2789    }
2790
2791    /// `dst = lhs ++ rhs`.
2792    0xE1 StrConcat "str_concat" {
2793        /// Destination `str` register.
2794        dst: Reg @ B,
2795        /// First part.
2796        lhs: Reg @ C,
2797        /// Second part.
2798        rhs: Reg @ D,
2799    }
2800
2801    /// `dst` = the concatenation of the `count` strings in `first ..
2802    /// first+count` (the empty string if `count` is 0).
2803    0xE2 StrConcatN "str_concat_n" {
2804        /// Destination `str` register.
2805        dst: Reg @ B,
2806        /// First part.
2807        first: Reg @ C,
2808        /// Number of parts.
2809        count: u8 @ A,
2810    }
2811
2812    /// `dst` = whether the strings have equal bytes.
2813    0xE3 StrEq "str_eq" {
2814        /// Destination `bool` register.
2815        dst: Reg @ B,
2816        /// Left operand.
2817        lhs: Reg @ C,
2818        /// Right operand.
2819        rhs: Reg @ D,
2820    }
2821
2822    /// `dst` = -1, 0, or 1 (`i8`) by bytewise lexicographic order.
2823    0xE4 StrCmp "str_cmp" {
2824        /// Destination `i8` register.
2825        dst: Reg @ B,
2826        /// Left operand.
2827        lhs: Reg @ C,
2828        /// Right operand.
2829        rhs: Reg @ D,
2830    }
2831
2832    /// `dst = s[start..end]` by byte offset, `start` in `range` and `end` in
2833    /// `range+1` (both `i64`); IndexOutOfBounds unless `0 <= start <= end <=
2834    /// len`; with `utf8`, InvalidStrIndex if a bound splits a character.
2835    0xE5 StrSlice "str_slice" {
2836        /// Destination `str` register.
2837        dst: Reg @ B,
2838        /// The string.
2839        s: Reg @ C,
2840        /// Start index; the end index is in the next register.
2841        range: Reg @ D,
2842        /// Whether bounds must fall on UTF-8 character boundaries.
2843        utf8: bool @ A,
2844    }
2845
2846    /// `dst` = the byte at `idx` (`u8`); IndexOutOfBounds unless `0 <= idx
2847    /// < len`.
2848    0xE6 StrByte "str_byte" {
2849        /// Destination `u8` register.
2850        dst: Reg @ B,
2851        /// The string.
2852        s: Reg @ C,
2853        /// The `i64` index.
2854        idx: Reg @ D,
2855    }
2856
2857    // ---------------------------------------------------------------------
2858    // Coroutines, generators, async. Coroutines are stackful and asymmetric:
2859    // `yield`/`await` suspend the innermost running coroutine with every
2860    // frame above its body; `resume` continues it. Values crossing a
2861    // suspension are `dyn`. Every instruction here is a safepoint.
2862    // ---------------------------------------------------------------------
2863
2864    /// `dst` = a new coroutine, state `created`, that will run
2865    /// `func(dst+1, ..., dst+argc)` when first resumed. The arguments are
2866    /// copied now. (A)
2867    0xF0 CoroNew "coro_new" {
2868        /// Destination `dyn` or `ref coroutine` register; arguments follow it.
2869        dst: Reg @ B,
2870        /// The body.
2871        func: FuncId @ W,
2872        /// The number of arguments.
2873        argc: u8 @ A,
2874    }
2875
2876    /// `dst` = a new coroutine running the closure or function reference
2877    /// `callee` with the arguments `dst+1 ..= dst+argc` (converted from
2878    /// `dyn` as `dcall` does when `callee` is `dyn`). (A)
2879    0xF1 CoroNewIndirect "coro_new_indirect" {
2880        /// Destination `dyn` or `ref coroutine` register; arguments follow it.
2881        dst: Reg @ B,
2882        /// The callable.
2883        callee: Reg @ C,
2884        /// The number of arguments.
2885        argc: u8 @ A,
2886    }
2887
2888    /// Suspend the running coroutine in state `yielded`, handing `src` to
2889    /// its resumer under the next automatic key (PHP's rule: one more than
2890    /// the largest integer key yielded so far, starting at 0); when resumed,
2891    /// `dst` = the value sent. The yield is an unwind point: `resume_throw`
2892    /// and `coro_close` raise here. CannotSuspend (E0111) outside a
2893    /// coroutine or across a host frame.
2894    0xF2 Yield "yield" {
2895        /// Receives the value sent by the next `resume` (`dyn`).
2896        dst: Reg @ B,
2897        /// The value yielded (`dyn`).
2898        src: Reg @ C,
2899    }
2900
2901    /// Suspend the running coroutine in state `awaiting`, handing the
2902    /// awaitable `src` to its resumer (the scheduler); when resumed, `dst` =
2903    /// the value sent (the awaited result), or the error sent by
2904    /// `resume_throw` is raised here. CannotSuspend as `yield`.
2905    0xF3 Await "await" {
2906        /// Receives the awaited result (`dyn`).
2907        dst: Reg @ B,
2908        /// The awaitable (`dyn`).
2909        src: Reg @ C,
2910    }
2911
2912    /// Resume `coro` (state `created`, `yielded`, or `awaiting`), sending
2913    /// `src`; when it next suspends or finishes, `dst` = the value it
2914    /// yielded, the awaitable it awaits, or its return value (nil for a void
2915    /// body). An error escaping its body marks it `failed` and is raised
2916    /// here. InvalidCoroState (E0110) for any other state.
2917    0xF4 Resume "resume" {
2918        /// Receives the yielded, awaited, or returned value (`dyn`).
2919        dst: Reg @ B,
2920        /// The coroutine.
2921        coro: Reg @ C,
2922        /// The value sent (`dyn`; ignored by a `created` coroutine).
2923        src: Reg @ D,
2924    }
2925
2926    /// Resume `coro` by raising `src` at its suspension point (in a
2927    /// `created` coroutine: before its first instruction, where no handler
2928    /// covers it). Otherwise as `resume`.
2929    0xF5 ResumeThrow "resume_throw" {
2930        /// Receives the next yielded, awaited, or returned value (`dyn`).
2931        dst: Reg @ B,
2932        /// The coroutine.
2933        coro: Reg @ C,
2934        /// The error value raised inside it (`dyn`).
2935        src: Reg @ D,
2936    }
2937
2938    /// `dst` = the [`CoroState`](crate::CoroState) code of `coro`, as a
2939    /// `u8`.
2940    0xF6 CoroStatus "coro_status" {
2941        /// Destination `u8` register.
2942        dst: Reg @ B,
2943        /// The coroutine.
2944        coro: Reg @ C,
2945    }
2946
2947    /// `dst` = the running coroutine, or nil on the main stack.
2948    0xF7 CoroCurrent "coro_current" {
2949        /// Destination `dyn` or `ref coroutine` register.
2950        dst: Reg @ B,
2951    }
2952
2953    /// As `yield`, with an explicit key (PHP `yield $k => $v`); an integer
2954    /// key larger than any before it moves the automatic-key counter.
2955    0xF9 YieldKv "yield_kv" {
2956        /// Receives the value sent by the next `resume` (`dyn`).
2957        dst: Reg @ B,
2958        /// The key (`dyn`).
2959        key: Reg @ C,
2960        /// The value yielded (`dyn`).
2961        src: Reg @ D,
2962    }
2963
2964    /// Close `coro` (Python `close()`/`aclose()`, PHP generator
2965    /// destruction): `created` → `returned` without running; `returned` or
2966    /// `failed` → nothing. A suspended coroutine is marked *closing* and
2967    /// `src` (the close signal, e.g. `GeneratorExit`) is raised at its
2968    /// suspension point, so its `finally` blocks run. If the body then
2969    /// returns, or the error escaping it is `src` itself, the close
2970    /// succeeds (`returned`; `dst` = the return value or nil). Another
2971    /// error marks it `failed` and is raised here. An `await` while closing
2972    /// suspends normally (`dst` = the awaitable; drive it with `resume`); a
2973    /// `yield` while closing leaves it suspended there and raises
2974    /// CloseIgnored (E0113) here.
2975    0xFA CoroClose "coro_close" {
2976        /// Receives the return value, the awaitable, or nil (`dyn`).
2977        dst: Reg @ B,
2978        /// The coroutine.
2979        coro: Reg @ C,
2980        /// The close signal raised inside it (`dyn`).
2981        src: Reg @ D,
2982    }
2983
2984    /// `dst` = the key of the value `coro` most recently yielded, or nil
2985    /// before its first yield (PHP `Generator::key()`).
2986    0xFB CoroKey "coro_key" {
2987        /// Destination `dyn` register.
2988        dst: Reg @ B,
2989        /// The coroutine.
2990        coro: Reg @ C,
2991    }
2992
2993    /// `dst` = the return value of a `returned` coroutine (Python's
2994    /// `StopIteration.value`, PHP `Generator::getReturn()`), nil for a void
2995    /// body. InvalidCoroState (E0110) in any other state.
2996    0xFC CoroResult "coro_result" {
2997        /// Destination `dyn` register.
2998        dst: Reg @ B,
2999        /// The coroutine.
3000        coro: Reg @ C,
3001    }
3002
3003    /// Start a task: make a coroutine as `coro_new_indirect` does and pass
3004    /// it to the module's `spawn` hook (the host scheduler); `dst` = the
3005    /// hook's result (a task handle). NoScheduler (E0112) without the hook.
3006    /// (A)
3007    0xF8 Spawn "spawn" {
3008        /// Receives the task handle (`dyn`); arguments follow it.
3009        dst: Reg @ B,
3010        /// The callable.
3011        callee: Reg @ C,
3012        /// The number of arguments.
3013        argc: u8 @ A,
3014    }
3015}
3016
3017impl Inst {
3018    /// The branch target of `jmp`, `jmp_if`, or `jmp_if_not`, if this is
3019    /// one. (Switch targets live in the function's jump tables.)
3020    ///
3021    /// # Examples
3022    ///
3023    /// ```
3024    /// use bytecode_lang::{Inst, Reg, Target};
3025    ///
3026    /// assert_eq!(Inst::Jmp { target: Target(4) }.branch_target(), Some(Target(4)));
3027    /// assert_eq!(Inst::Mov { dst: Reg(0), src: Reg(1) }.branch_target(), None);
3028    /// ```
3029    #[must_use]
3030    pub const fn branch_target(&self) -> Option<Target> {
3031        match *self {
3032            Inst::Jmp { target } | Inst::JmpIf { target, .. } | Inst::JmpIfNot { target, .. } => {
3033                Some(target)
3034            }
3035            _ => None,
3036        }
3037    }
3038
3039    /// The assembler name of this instruction's opcode.
3040    ///
3041    /// # Examples
3042    ///
3043    /// ```
3044    /// use bytecode_lang::Inst;
3045    ///
3046    /// assert_eq!(Inst::Safepoint {}.mnemonic(), "safepoint");
3047    /// ```
3048    #[must_use]
3049    pub const fn mnemonic(&self) -> &'static str {
3050        self.opcode().mnemonic()
3051    }
3052}
3053
3054/// `mnemonic.modifiers operand, operand`, with raw `@pc` branch targets.
3055impl fmt::Display for Inst {
3056    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3057        self.render(f, &Cx::EMPTY)
3058    }
3059}
3060
3061#[cfg(test)]
3062mod tests {
3063    use alloc::string::ToString;
3064
3065    use super::*;
3066
3067    #[test]
3068    fn decoded_instructions_stay_eight_bytes() {
3069        assert_eq!(core::mem::size_of::<Inst>(), 8);
3070    }
3071
3072    #[test]
3073    fn opcodes_round_trip_through_their_byte() {
3074        for &op in Opcode::ALL {
3075            assert_eq!(Opcode::from_u8(op as u8), Some(op));
3076        }
3077    }
3078
3079    #[test]
3080    fn modifiers_print_as_suffixes() {
3081        let i = Inst::IntCast {
3082            dst: Reg(1),
3083            src: Reg(2),
3084            conv: IntConv::new(IntTy::I64, IntTy::U8, crate::Overflow::Wrap),
3085        };
3086        assert_eq!(i.to_string(), "int_cast.i64.u8.wrap r1, r2");
3087        let d = Inst::DAdd {
3088            dst: Reg(0),
3089            lhs: Reg(1),
3090            rhs: Reg(2),
3091            pol: Policy::new(),
3092        };
3093        assert_eq!(d.to_string(), "dadd r0, r1, r2");
3094        let c = Inst::Call {
3095            dst: Reg(3),
3096            func: FuncId(1),
3097            argc: 2,
3098        };
3099        assert_eq!(c.to_string(), "call r3, f1, 2");
3100    }
3101
3102    #[test]
3103    fn immediates_keep_their_sign() {
3104        let i = Inst::LoadInt {
3105            dst: Reg(0),
3106            val: -5,
3107            ty: IntTy::I32,
3108        };
3109        assert_eq!(Inst::from_bytes(i.to_bytes()), Ok(i));
3110        assert_eq!(i.to_string(), "load_int.i32 r0, -5");
3111    }
3112}