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}