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