use core::fmt;
use crate::ids::{
ConstId, FieldIdx, FuncId, GlobalId, ImportId, NameRef, Reg, TableId, Target, TypeRef, UpvalIdx,
};
use crate::module::{Const, Function, Module};
use crate::policy::{FloatTy, IntConv, IntOp, IntPair, IntTy, Overflow, Policy};
use crate::types::{Kind, Prim};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Slot {
A,
B,
C,
D,
W,
}
impl Slot {
#[must_use]
pub const fn shift(self) -> u32 {
match self {
Slot::A => 8,
Slot::B => 16,
Slot::C | Slot::W => 32,
Slot::D => 48,
}
}
#[must_use]
pub const fn width(self) -> u32 {
match self {
Slot::A => 8,
Slot::B | Slot::C | Slot::D => 16,
Slot::W => 32,
}
}
const fn mask(self) -> u64 {
(1u64 << self.width()) - 1
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum FieldKind {
Reg,
Target,
Const,
Func,
Import,
Global,
Table,
Name,
TypeRef,
Field,
Upval,
Imm32,
Count,
Bool,
IntTy,
FloatTy,
Policy,
IntOp,
IntConv,
IntPair,
Kind,
Prim,
}
impl FieldKind {
#[must_use]
pub const fn is_modifier(self) -> bool {
matches!(
self,
FieldKind::IntTy
| FieldKind::FloatTy
| FieldKind::Policy
| FieldKind::IntOp
| FieldKind::IntConv
| FieldKind::IntPair
| FieldKind::Kind
| FieldKind::Prim
)
}
#[must_use]
pub const fn bits(self) -> u32 {
match self {
FieldKind::Reg
| FieldKind::Name
| FieldKind::TypeRef
| FieldKind::Field
| FieldKind::Upval => 16,
FieldKind::Target
| FieldKind::Const
| FieldKind::Func
| FieldKind::Import
| FieldKind::Global
| FieldKind::Table
| FieldKind::Imm32 => 32,
_ => 8,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct FieldSpec {
pub name: &'static str,
pub kind: FieldKind,
pub slot: Slot,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum InstError {
UnknownOpcode(u8),
InvalidOperand {
opcode: Opcode,
field: &'static str,
},
NonCanonical(Opcode),
}
impl fmt::Display for InstError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InstError::UnknownOpcode(op) => write!(f, "unknown opcode {op:#04x}"),
InstError::InvalidOperand { opcode, field } => {
write!(f, "invalid `{field}` operand of `{}`", opcode.mnemonic())
}
InstError::NonCanonical(opcode) => {
write!(
f,
"`{}` has non-zero bytes in unused slots",
opcode.mnemonic()
)
}
}
}
}
impl core::error::Error for InstError {}
#[derive(Clone, Copy)]
pub(crate) struct Cx<'a> {
pub(crate) labels: &'a [u32],
pub(crate) module: Option<&'a Module>,
pub(crate) func: Option<&'a Function>,
}
impl Cx<'_> {
pub(crate) const EMPTY: Cx<'static> = Cx {
labels: &[],
module: None,
func: None,
};
}
pub(crate) struct Note<'w> {
w: &'w mut dyn fmt::Write,
started: bool,
}
impl Note<'_> {
fn item(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
self.w.write_str(if self.started { ", " } else { " ; " })?;
self.started = true;
self.w.write_fmt(args)
}
}
pub(crate) fn write_quoted(w: &mut dyn fmt::Write, s: &str) -> fmt::Result {
const MAX: usize = 40;
match s.char_indices().nth(MAX) {
Some((cut, _)) => write!(w, "{:?}...", s.get(..cut).unwrap_or(s)),
None => write!(w, "{s:?}"),
}
}
pub(crate) fn write_const_preview(
w: &mut dyn fmt::Write,
module: &Module,
c: &Const,
) -> fmt::Result {
match c {
Const::Str(id) => match module.string(*id) {
Some(s) => write_quoted(w, s),
None => write!(w, "str {id} <invalid>"),
},
Const::Bytes(bytes) => write!(w, "bytes[{}]", bytes.len()),
Const::Array(items) => write!(w, "array[{}]", items.len()),
Const::Map(entries) => write!(w, "map[{}]", entries.len()),
other => write!(w, "{other}"),
}
}
pub(crate) trait Operand: Copy + fmt::Display {
const KIND: FieldKind;
fn to_raw(self) -> u32;
fn from_raw(raw: u32) -> Option<Self>;
fn suffix(self, w: &mut dyn fmt::Write) -> fmt::Result {
if Self::KIND.is_modifier() {
write!(w, ".{self}")
} else {
Ok(())
}
}
fn render(self, w: &mut dyn fmt::Write, _cx: &Cx<'_>) -> fmt::Result {
write!(w, "{self}")
}
fn comment(self, _cx: &Cx<'_>, _note: &mut Note<'_>) -> fmt::Result {
Ok(())
}
fn overflow(self) -> Option<Overflow> {
None
}
fn as_reg(self) -> Option<Reg> {
None
}
#[inline(always)]
fn as_target(self) -> Option<Target> {
None
}
#[inline(always)]
fn is_promote(self) -> bool {
false
}
}
fn is_dst(name: &str) -> bool {
name == "dst"
}
macro_rules! operand_u16 {
($($ty:ident => $kind:ident),* $(,)?) => {$(
impl Operand for $ty {
const KIND: FieldKind = FieldKind::$kind;
fn to_raw(self) -> u32 {
u32::from(self.0)
}
fn from_raw(raw: u32) -> Option<Self> {
u16::try_from(raw).ok().map($ty)
}
}
)*};
}
macro_rules! operand_u32 {
($($ty:ident => $kind:ident),* $(,)?) => {$(
impl Operand for $ty {
const KIND: FieldKind = FieldKind::$kind;
fn to_raw(self) -> u32 {
self.0
}
fn from_raw(raw: u32) -> Option<Self> {
Some($ty(raw))
}
}
)*};
}
macro_rules! operand_code {
($($ty:ident => $kind:ident),* $(,)?) => {$(
impl Operand for $ty {
const KIND: FieldKind = FieldKind::$kind;
fn to_raw(self) -> u32 {
u32::from(self.code())
}
fn from_raw(raw: u32) -> Option<Self> {
u8::try_from(raw).ok().and_then($ty::from_code)
}
}
)*};
}
macro_rules! operand_bits {
($($ty:ident => $kind:ident),* $(,)?) => {$(
impl Operand for $ty {
const KIND: FieldKind = FieldKind::$kind;
fn to_raw(self) -> u32 {
u32::from(self.bits())
}
fn from_raw(raw: u32) -> Option<Self> {
u8::try_from(raw).ok().and_then($ty::from_bits)
}
}
)*};
}
operand_u16!(UpvalIdx => Upval);
operand_u32!(TableId => Table);
operand_code!(IntTy => IntTy, FloatTy => FloatTy, Kind => Kind, Prim => Prim);
operand_bits!(IntPair => IntPair);
impl Operand for Reg {
const KIND: FieldKind = FieldKind::Reg;
fn to_raw(self) -> u32 {
u32::from(self.0)
}
fn from_raw(raw: u32) -> Option<Self> {
u16::try_from(raw).ok().map(Reg)
}
fn as_reg(self) -> Option<Reg> {
Some(self)
}
}
impl Operand for IntOp {
const KIND: FieldKind = FieldKind::IntOp;
fn to_raw(self) -> u32 {
u32::from(self.bits())
}
fn from_raw(raw: u32) -> Option<Self> {
u8::try_from(raw).ok().and_then(IntOp::from_bits)
}
fn overflow(self) -> Option<Overflow> {
Some(self.policy().overflow())
}
#[inline(always)]
fn is_promote(self) -> bool {
(self.bits() >> 3) & 3 == 3
}
}
impl Operand for IntConv {
const KIND: FieldKind = FieldKind::IntConv;
fn to_raw(self) -> u32 {
u32::from(self.bits())
}
fn from_raw(raw: u32) -> Option<Self> {
u8::try_from(raw).ok().and_then(IntConv::from_bits)
}
fn overflow(self) -> Option<Overflow> {
Some(IntConv::overflow(self))
}
#[inline(always)]
fn is_promote(self) -> bool {
self.bits() >> 6 == 3
}
}
impl Operand for FieldIdx {
const KIND: FieldKind = FieldKind::Field;
fn to_raw(self) -> u32 {
u32::from(self.0)
}
fn from_raw(raw: u32) -> Option<Self> {
u16::try_from(raw).ok().map(FieldIdx)
}
}
impl Operand for Policy {
const KIND: FieldKind = FieldKind::Policy;
fn to_raw(self) -> u32 {
u32::from(self.bits())
}
fn from_raw(raw: u32) -> Option<Self> {
u8::try_from(raw).ok().and_then(Policy::from_bits)
}
fn suffix(self, w: &mut dyn fmt::Write) -> fmt::Result {
if self.is_default() {
Ok(())
} else {
write!(w, ".{self}")
}
}
fn overflow(self) -> Option<Overflow> {
Some(Policy::overflow(self))
}
#[inline(always)]
fn is_promote(self) -> bool {
self.bits() & 3 == 3
}
}
impl Operand for Target {
const KIND: FieldKind = FieldKind::Target;
fn to_raw(self) -> u32 {
self.0
}
fn from_raw(raw: u32) -> Option<Self> {
Some(Target(raw))
}
#[inline(always)]
fn as_target(self) -> Option<Target> {
Some(self)
}
fn render(self, w: &mut dyn fmt::Write, cx: &Cx<'_>) -> fmt::Result {
match cx.labels.binary_search(&self.0) {
Ok(label) => write!(w, "L{label}"),
Err(_) => write!(w, "{self}"),
}
}
}
impl Operand for ConstId {
const KIND: FieldKind = FieldKind::Const;
fn to_raw(self) -> u32 {
self.0
}
fn from_raw(raw: u32) -> Option<Self> {
Some(ConstId(raw))
}
fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
let Some(module) = cx.module else {
return Ok(());
};
match module.constant(self) {
Some(c) => {
let mut text = alloc::string::String::new();
write_const_preview(&mut text, module, c)?;
note.item(format_args!("{text}"))
}
None => note.item(format_args!("{self} <invalid>")),
}
}
}
impl Operand for FuncId {
const KIND: FieldKind = FieldKind::Func;
fn to_raw(self) -> u32 {
self.0
}
fn from_raw(raw: u32) -> Option<Self> {
Some(FuncId(raw))
}
fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
let Some(module) = cx.module else {
return Ok(());
};
match module.function(self).and_then(|f| module.string(f.name())) {
Some(name) => note.item(format_args!("{name:?}")),
None => note.item(format_args!("{self} <invalid>")),
}
}
}
impl Operand for ImportId {
const KIND: FieldKind = FieldKind::Import;
fn to_raw(self) -> u32 {
self.0
}
fn from_raw(raw: u32) -> Option<Self> {
Some(ImportId(raw))
}
fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
let Some(module) = cx.module else {
return Ok(());
};
let names = module
.import(self)
.and_then(|i| Some((module.string(i.module)?, module.string(i.name)?)));
match names {
Some((m, n)) => note.item(format_args!("{m:?}.{n:?}")),
None => note.item(format_args!("{self} <invalid>")),
}
}
}
impl Operand for GlobalId {
const KIND: FieldKind = FieldKind::Global;
fn to_raw(self) -> u32 {
self.0
}
fn from_raw(raw: u32) -> Option<Self> {
Some(GlobalId(raw))
}
fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
let Some(module) = cx.module else {
return Ok(());
};
match module.global(self).and_then(|g| module.string(g.name)) {
Some(name) => note.item(format_args!("{name:?}")),
None => note.item(format_args!("{self} <invalid>")),
}
}
}
impl Operand for NameRef {
const KIND: FieldKind = FieldKind::Name;
fn to_raw(self) -> u32 {
u32::from(self.0)
}
fn from_raw(raw: u32) -> Option<Self> {
u16::try_from(raw).ok().map(NameRef)
}
fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
let (Some(module), Some(func)) = (cx.module, cx.func) else {
return Ok(());
};
match func
.names()
.get(self.index())
.and_then(|&s| module.string(s))
{
Some(name) => note.item(format_args!("{name:?}")),
None => note.item(format_args!("{self} <invalid>")),
}
}
}
impl Operand for TypeRef {
const KIND: FieldKind = FieldKind::TypeRef;
fn to_raw(self) -> u32 {
u32::from(self.0)
}
fn from_raw(raw: u32) -> Option<Self> {
u16::try_from(raw).ok().map(TypeRef)
}
fn comment(self, cx: &Cx<'_>, note: &mut Note<'_>) -> fmt::Result {
let Some(func) = cx.func else { return Ok(()) };
match func.type_refs().get(self.index()) {
Some(ty) => note.item(format_args!("{ty}")),
None => note.item(format_args!("{self} <invalid>")),
}
}
}
impl Operand for i32 {
const KIND: FieldKind = FieldKind::Imm32;
fn to_raw(self) -> u32 {
u32::from_le_bytes(self.to_le_bytes())
}
fn from_raw(raw: u32) -> Option<Self> {
Some(i32::from_le_bytes(raw.to_le_bytes()))
}
}
impl Operand for u8 {
const KIND: FieldKind = FieldKind::Count;
fn to_raw(self) -> u32 {
u32::from(self)
}
fn from_raw(raw: u32) -> Option<Self> {
u8::try_from(raw).ok()
}
}
impl Operand for bool {
const KIND: FieldKind = FieldKind::Bool;
fn to_raw(self) -> u32 {
u32::from(self)
}
fn from_raw(raw: u32) -> Option<Self> {
match raw {
0 => Some(false),
1 => Some(true),
_ => None,
}
}
}
const fn used_bits(fields: &[FieldSpec]) -> u64 {
let mut used = 0xff;
let mut i = 0;
while i < fields.len() {
let slot = fields[i].slot;
used |= slot.mask() << slot.shift();
i += 1;
}
used
}
#[inline(always)]
const fn has_unused_bits(word: u64, used: u64) -> bool {
word & !used != 0
}
#[inline]
fn read_field<T: Operand>(
word: u64,
slot: Slot,
opcode: Opcode,
field: &'static str,
) -> Result<T, InstError> {
let raw = ((word >> slot.shift()) & slot.mask()) as u32;
T::from_raw(raw).ok_or(InstError::InvalidOperand { opcode, field })
}
macro_rules! isa {
(
$(
$(#[$meta:meta])*
$code:literal $name:ident $mnemonic:literal {
$( $(#[$fmeta:meta])* $field:ident : $ty:ident @ $slot:ident ),* $(,)?
}
)*
) => {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Inst {
$(
$(#[$meta])*
$name { $( $(#[$fmeta])* $field: $ty ),* },
)*
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u8)]
pub enum Opcode {
$(
$(#[$meta])*
$name = $code,
)*
}
impl Opcode {
pub const ALL: &'static [Opcode] = &[ $( Opcode::$name ),* ];
#[must_use]
pub const fn from_u8(byte: u8) -> Option<Opcode> {
match byte {
$( $code => Some(Opcode::$name), )*
_ => None,
}
}
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
$( Opcode::$name => $mnemonic, )*
}
}
#[must_use]
pub const fn fields(self) -> &'static [FieldSpec] {
match self {
$(
Opcode::$name => {
const FIELDS: &[FieldSpec] = &[
$( FieldSpec {
name: stringify!($field),
kind: <$ty as Operand>::KIND,
slot: Slot::$slot,
} ),*
];
FIELDS
}
)*
}
}
}
impl Inst {
#[must_use]
pub const fn opcode(&self) -> Opcode {
match self {
$( Inst::$name { .. } => Opcode::$name, )*
}
}
#[must_use]
#[inline]
pub fn to_bytes(&self) -> [u8; 8] {
let word: u64 = match *self {
$(
Inst::$name { $( $field ),* } => {
#[allow(unused_mut)]
let mut word = u64::from(Opcode::$name as u8);
$( word |= u64::from(Operand::to_raw($field)) << Slot::$slot.shift(); )*
word
}
)*
};
word.to_le_bytes()
}
#[inline]
pub fn from_bytes(bytes: [u8; 8]) -> Result<Inst, InstError> {
let word = u64::from_le_bytes(bytes);
let [opcode, ..] = bytes;
Ok(match opcode {
$(
$code => {
const USED: u64 = used_bits(Opcode::$name.fields());
if has_unused_bits(word, USED) {
return Err(InstError::NonCanonical(Opcode::$name));
}
Inst::$name {
$( $field: read_field::<$ty>(
word,
Slot::$slot,
Opcode::$name,
stringify!($field),
)? ),*
}
}
)*
other => return Err(InstError::UnknownOpcode(other)),
})
}
#[must_use]
#[allow(unused_mut, unused_variables, clippy::let_and_return)]
pub fn overflow(&self) -> Option<Overflow> {
match *self {
$(
Inst::$name { $( $field ),* } => {
let mut found = None;
$(
if let Some(o) = Operand::overflow($field) {
found = Some(o);
}
)*
found
}
)*
}
}
#[inline]
#[allow(unused_variables)]
pub(crate) fn build_checks(&self) -> (Option<Target>, bool) {
match *self {
$(
Inst::$name { $( $field ),* } => {
let target: Option<Target> = None $( .or(Operand::as_target($field)) )*;
(target, false $( || Operand::is_promote($field) )*)
}
)*
}
}
#[allow(unused_mut, unused_variables, clippy::let_and_return)]
pub(crate) fn dst(&self) -> Option<Reg> {
match *self {
$(
Inst::$name { $( $field ),* } => {
let mut dst = None;
$(
if is_dst(stringify!($field)) {
dst = Operand::as_reg($field);
}
)*
dst
}
)*
}
}
#[allow(unused_assignments, unused_mut, unused_variables)]
pub(crate) fn render(&self, w: &mut dyn fmt::Write, cx: &Cx<'_>) -> fmt::Result {
match *self {
$(
Inst::$name { $( $field ),* } => {
w.write_str($mnemonic)?;
$( Operand::suffix($field, w)?; )*
let mut sep = " ";
$(
if !<$ty as Operand>::KIND.is_modifier() {
w.write_str(sep)?;
sep = ", ";
Operand::render($field, w, cx)?;
}
)*
let mut note = Note { w, started: false };
$( Operand::comment($field, cx, &mut note)?; )*
Ok(())
}
)*
}
}
}
};
}
isa! {
0x00 Nop "nop" {}
0x01 Mov "mov" {
dst: Reg @ B,
src: Reg @ C,
}
0x02 LoadConst "load_const" {
dst: Reg @ B,
k: ConstId @ W,
}
0x03 DLoadConst "dload_const" {
dst: Reg @ B,
k: ConstId @ W,
}
0x04 LoadInt "load_int" {
dst: Reg @ B,
val: i32 @ W,
ty: IntTy @ A,
}
0x05 DLoadInt "dload_int" {
dst: Reg @ B,
val: i32 @ W,
}
0x06 LoadBool "load_bool" {
dst: Reg @ B,
val: bool @ A,
}
0x07 LoadNil "load_nil" {
dst: Reg @ B,
}
0x08 LoadImport "load_import" {
dst: Reg @ B,
import: ImportId @ W,
}
0x09 GetGlobal "get_global" {
dst: Reg @ B,
global: GlobalId @ W,
}
0x0A SetGlobal "set_global" {
global: GlobalId @ W,
src: Reg @ B,
}
0x10 IAdd "iadd" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x11 ISub "isub" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x12 IMul "imul" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x13 IDiv "idiv" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x14 IRem "irem" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x15 IFloorDiv "ifloor_div" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x16 IFloorMod "ifloor_mod" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x17 IAnd "iand" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x18 IOr "ior" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x19 IXor "ixor" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x1A IShl "ishl" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x1B IShr "ishr" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x1C IMin "imin" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x1D IMax "imax" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
op: IntOp @ A,
}
0x1E INeg "ineg" {
dst: Reg @ B,
src: Reg @ C,
op: IntOp @ A,
}
0x1F INot "inot" {
dst: Reg @ B,
src: Reg @ C,
op: IntOp @ A,
}
0x20 IAbs "iabs" {
dst: Reg @ B,
src: Reg @ C,
op: IntOp @ A,
}
0x21 IEq "ieq" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: IntTy @ A,
}
0x22 INe "ine" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: IntTy @ A,
}
0x23 ILt "ilt" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: IntTy @ A,
}
0x24 ILe "ile" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: IntTy @ A,
}
0x25 IGt "igt" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: IntTy @ A,
}
0x26 IGe "ige" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: IntTy @ A,
}
0x30 FAdd "fadd" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x31 FSub "fsub" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x32 FMul "fmul" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x33 FDiv "fdiv" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x34 FRem "frem" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x35 FIeeeRem "fieee_rem" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x36 FMin "fmin" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x37 FMax "fmax" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x38 FFma "ffma" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x39 FNeg "fneg" {
dst: Reg @ B,
src: Reg @ C,
ty: FloatTy @ A,
}
0x3A FAbs "fabs" {
dst: Reg @ B,
src: Reg @ C,
ty: FloatTy @ A,
}
0x3B FSqrt "fsqrt" {
dst: Reg @ B,
src: Reg @ C,
ty: FloatTy @ A,
}
0x3C FFloor "ffloor" {
dst: Reg @ B,
src: Reg @ C,
ty: FloatTy @ A,
}
0x3D FCeil "fceil" {
dst: Reg @ B,
src: Reg @ C,
ty: FloatTy @ A,
}
0x3E FTrunc "ftrunc" {
dst: Reg @ B,
src: Reg @ C,
ty: FloatTy @ A,
}
0x3F FRound "fround" {
dst: Reg @ B,
src: Reg @ C,
ty: FloatTy @ A,
}
0x40 FRoundEven "fround_even" {
dst: Reg @ B,
src: Reg @ C,
ty: FloatTy @ A,
}
0x41 FEq "feq" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x42 FNe "fne" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x43 FLt "flt" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x44 FLe "fle" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x45 FGt "fgt" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x46 FGe "fge" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x47 FTotalCmp "ftotal_cmp" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
ty: FloatTy @ A,
}
0x50 BNot "bnot" {
dst: Reg @ B,
src: Reg @ C,
}
0x51 BAnd "band" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x52 BOr "bor" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x53 BXor "bxor" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x54 CEq "ceq" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x55 CNe "cne" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x56 CLt "clt" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x57 CLe "cle" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x58 CGt "cgt" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x59 CGe "cge" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x5A RefEq "ref_eq" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x60 IntCast "int_cast" {
dst: Reg @ B,
src: Reg @ C,
conv: IntConv @ A,
}
0x61 Zext "zext" {
dst: Reg @ B,
src: Reg @ C,
pair: IntPair @ A,
}
0x62 Sext "sext" {
dst: Reg @ B,
src: Reg @ C,
pair: IntPair @ A,
}
0x63 Trunc "trunc" {
dst: Reg @ B,
src: Reg @ C,
pair: IntPair @ A,
}
0x64 IntToF32 "int_to_f32" {
dst: Reg @ B,
src: Reg @ C,
ty: IntTy @ A,
}
0x65 IntToF64 "int_to_f64" {
dst: Reg @ B,
src: Reg @ C,
ty: IntTy @ A,
}
0x66 F32ToInt "f32_to_int" {
dst: Reg @ B,
src: Reg @ C,
op: IntOp @ A,
}
0x67 F64ToInt "f64_to_int" {
dst: Reg @ B,
src: Reg @ C,
op: IntOp @ A,
}
0x68 F32ToF64 "f32_to_f64" {
dst: Reg @ B,
src: Reg @ C,
}
0x69 F64ToF32 "f64_to_f32" {
dst: Reg @ B,
src: Reg @ C,
}
0x6A FloatToBits "float_to_bits" {
dst: Reg @ B,
src: Reg @ C,
ty: IntTy @ A,
}
0x6B BitsToFloat "bits_to_float" {
dst: Reg @ B,
src: Reg @ C,
ty: IntTy @ A,
}
0x6C CharFromU32 "char_from_u32" {
dst: Reg @ B,
src: Reg @ C,
}
0x6D CharToU32 "char_to_u32" {
dst: Reg @ B,
src: Reg @ C,
}
0x6E BoolToInt "bool_to_int" {
dst: Reg @ B,
src: Reg @ C,
ty: IntTy @ A,
}
0x70 DAdd "dadd" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x71 DSub "dsub" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x72 DMul "dmul" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x73 DDiv "ddiv" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x74 DRem "drem" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x75 DFloorDiv "dfloor_div" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x76 DFloorMod "dfloor_mod" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x77 DAnd "dand" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x78 DOr "dor" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x79 DXor "dxor" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x7A DShl "dshl" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x7B DShr "dshr" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
pol: Policy @ A,
}
0x7C DNeg "dneg" {
dst: Reg @ B,
src: Reg @ C,
pol: Policy @ A,
}
0x7D DNot "dnot" {
dst: Reg @ B,
src: Reg @ C,
pol: Policy @ A,
}
0x7E DEq "deq" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x7F DNe "dne" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x80 DLt "dlt" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x81 DLe "dle" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x82 DGt "dgt" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x83 DGe "dge" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x84 DTruthy "dtruthy" {
dst: Reg @ B,
src: Reg @ C,
}
0x85 DConcat "dconcat" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0x86 ToDyn "to_dyn" {
dst: Reg @ B,
src: Reg @ C,
from: Prim @ A,
}
0x87 FromDyn "from_dyn" {
dst: Reg @ B,
src: Reg @ C,
to: Prim @ A,
}
0x88 TypeOf "type_of" {
dst: Reg @ B,
src: Reg @ C,
}
0x89 IsKind "is_kind" {
dst: Reg @ B,
src: Reg @ C,
kind: Kind @ A,
}
0x8A IsType "is_type" {
dst: Reg @ B,
src: Reg @ C,
ty: TypeRef @ D,
}
0x8B Cast "cast" {
dst: Reg @ B,
src: Reg @ C,
ty: TypeRef @ D,
}
0x8C DGetIndex "dget_index" {
dst: Reg @ B,
obj: Reg @ C,
key: Reg @ D,
}
0x8D DSetIndex "dset_index" {
obj: Reg @ B,
key: Reg @ C,
src: Reg @ D,
}
0x8E GetProp "get_prop" {
dst: Reg @ B,
obj: Reg @ C,
name: NameRef @ D,
}
0x8F SetProp "set_prop" {
obj: Reg @ B,
name: NameRef @ D,
src: Reg @ C,
}
0x90 HasProp "has_prop" {
dst: Reg @ B,
obj: Reg @ C,
name: NameRef @ D,
}
0x91 DCall "dcall" {
dst: Reg @ B,
callee: Reg @ C,
argc: u8 @ A,
}
0x92 DIterNew "diter_new" {
dst: Reg @ B,
src: Reg @ C,
}
0x93 DLen "dlen" {
dst: Reg @ B,
src: Reg @ C,
}
0x94 DLNot "dlnot" {
dst: Reg @ B,
src: Reg @ C,
}
0xA0 Jmp "jmp" {
target: Target @ W,
}
0xA1 JmpIf "jmp_if" {
cond: Reg @ B,
target: Target @ W,
}
0xA2 JmpIfNot "jmp_if_not" {
cond: Reg @ B,
target: Target @ W,
}
0xA3 Switch "switch" {
src: Reg @ B,
table: TableId @ W,
ty: IntTy @ A,
}
0xA4 Call "call" {
dst: Reg @ B,
func: FuncId @ W,
argc: u8 @ A,
}
0xA5 CallIndirect "call_indirect" {
dst: Reg @ B,
callee: Reg @ C,
argc: u8 @ A,
}
0xA6 CallImport "call_import" {
dst: Reg @ B,
import: ImportId @ W,
argc: u8 @ A,
}
0xA7 TailCall "tail_call" {
func: FuncId @ W,
args: Reg @ B,
argc: u8 @ A,
}
0xA8 TailCallIndirect "tail_call_indirect" {
callee: Reg @ B,
args: Reg @ C,
argc: u8 @ A,
}
0xA9 Ret "ret" {
src: Reg @ B,
}
0xAA RetVoid "ret_void" {}
0xAB Throw "throw" {
src: Reg @ B,
}
0xAC ErrCode "err_code" {
dst: Reg @ B,
src: Reg @ C,
}
0xAD Safepoint "safepoint" {}
0xAE Unreachable "unreachable" {}
0xB0 MakeClosure "make_closure" {
dst: Reg @ B,
func: FuncId @ W,
}
0xB1 GetUpval "get_upval" {
dst: Reg @ B,
idx: UpvalIdx @ C,
}
0xB2 NewCell "new_cell" {
dst: Reg @ B,
src: Reg @ C,
ty: TypeRef @ D,
}
0xB3 CellGet "cell_get" {
dst: Reg @ B,
cell: Reg @ C,
}
0xB4 CellSet "cell_set" {
cell: Reg @ B,
src: Reg @ C,
}
0xC0 NewStruct "new_struct" {
dst: Reg @ B,
ty: TypeRef @ C,
}
0xC1 GetField "get_field" {
dst: Reg @ B,
obj: Reg @ C,
field: FieldIdx @ D,
}
0xC2 SetField "set_field" {
obj: Reg @ B,
field: FieldIdx @ D,
src: Reg @ C,
}
0xC3 NewArray "new_array" {
dst: Reg @ B,
len: Reg @ C,
ty: TypeRef @ D,
}
0xC4 ArrayLen "array_len" {
dst: Reg @ B,
arr: Reg @ C,
}
0xC5 ArrayGet "array_get" {
dst: Reg @ B,
arr: Reg @ C,
idx: Reg @ D,
}
0xC6 ArraySet "array_set" {
arr: Reg @ B,
idx: Reg @ C,
src: Reg @ D,
}
0xC7 ArrayPush "array_push" {
arr: Reg @ B,
src: Reg @ C,
}
0xC8 ArrayPop "array_pop" {
dst: Reg @ B,
arr: Reg @ C,
}
0xC9 NewMap "new_map" {
dst: Reg @ B,
ty: TypeRef @ C,
}
0xCA MapLen "map_len" {
dst: Reg @ B,
map: Reg @ C,
}
0xCB MapGet "map_get" {
dst: Reg @ B,
map: Reg @ C,
key: Reg @ D,
}
0xCC MapFind "map_find" {
dst: Reg @ B,
map: Reg @ C,
key: Reg @ D,
}
0xCD MapHas "map_has" {
dst: Reg @ B,
map: Reg @ C,
key: Reg @ D,
}
0xCE MapSet "map_set" {
map: Reg @ B,
key: Reg @ C,
src: Reg @ D,
}
0xCF MapDel "map_del" {
map: Reg @ B,
key: Reg @ C,
}
0xD0 MapPush "map_push" {
map: Reg @ B,
src: Reg @ C,
}
0xD1 IterNew "iter_new" {
dst: Reg @ B,
src: Reg @ C,
}
0xD2 IterNext "iter_next" {
has: Reg @ B,
iter: Reg @ C,
val: Reg @ D,
}
0xD3 IterKey "iter_key" {
dst: Reg @ B,
iter: Reg @ C,
}
0xD4 Dup "dup" {
dst: Reg @ B,
src: Reg @ C,
}
0xE0 StrLen "str_len" {
dst: Reg @ B,
s: Reg @ C,
}
0xE1 StrConcat "str_concat" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0xE2 StrConcatN "str_concat_n" {
dst: Reg @ B,
first: Reg @ C,
count: u8 @ A,
}
0xE3 StrEq "str_eq" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0xE4 StrCmp "str_cmp" {
dst: Reg @ B,
lhs: Reg @ C,
rhs: Reg @ D,
}
0xE5 StrSlice "str_slice" {
dst: Reg @ B,
s: Reg @ C,
range: Reg @ D,
utf8: bool @ A,
}
0xE6 StrByte "str_byte" {
dst: Reg @ B,
s: Reg @ C,
idx: Reg @ D,
}
0xF0 CoroNew "coro_new" {
dst: Reg @ B,
func: FuncId @ W,
argc: u8 @ A,
}
0xF1 CoroNewIndirect "coro_new_indirect" {
dst: Reg @ B,
callee: Reg @ C,
argc: u8 @ A,
}
0xF2 Yield "yield" {
dst: Reg @ B,
src: Reg @ C,
}
0xF3 Await "await" {
dst: Reg @ B,
src: Reg @ C,
}
0xF4 Resume "resume" {
dst: Reg @ B,
coro: Reg @ C,
src: Reg @ D,
}
0xF5 ResumeThrow "resume_throw" {
dst: Reg @ B,
coro: Reg @ C,
src: Reg @ D,
}
0xF6 CoroStatus "coro_status" {
dst: Reg @ B,
coro: Reg @ C,
}
0xF7 CoroCurrent "coro_current" {
dst: Reg @ B,
}
0xF9 YieldKv "yield_kv" {
dst: Reg @ B,
key: Reg @ C,
src: Reg @ D,
}
0xFA CoroClose "coro_close" {
dst: Reg @ B,
coro: Reg @ C,
src: Reg @ D,
}
0xFB CoroKey "coro_key" {
dst: Reg @ B,
coro: Reg @ C,
}
0xFC CoroResult "coro_result" {
dst: Reg @ B,
coro: Reg @ C,
}
0xF8 Spawn "spawn" {
dst: Reg @ B,
callee: Reg @ C,
argc: u8 @ A,
}
}
impl Inst {
#[must_use]
pub const fn branch_target(&self) -> Option<Target> {
match *self {
Inst::Jmp { target } | Inst::JmpIf { target, .. } | Inst::JmpIfNot { target, .. } => {
Some(target)
}
_ => None,
}
}
#[must_use]
pub const fn mnemonic(&self) -> &'static str {
self.opcode().mnemonic()
}
}
impl fmt::Display for Inst {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.render(f, &Cx::EMPTY)
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn decoded_instructions_stay_eight_bytes() {
assert_eq!(core::mem::size_of::<Inst>(), 8);
}
#[test]
fn opcodes_round_trip_through_their_byte() {
for &op in Opcode::ALL {
assert_eq!(Opcode::from_u8(op as u8), Some(op));
}
}
#[test]
fn modifiers_print_as_suffixes() {
let i = Inst::IntCast {
dst: Reg(1),
src: Reg(2),
conv: IntConv::new(IntTy::I64, IntTy::U8, crate::Overflow::Wrap),
};
assert_eq!(i.to_string(), "int_cast.i64.u8.wrap r1, r2");
let d = Inst::DAdd {
dst: Reg(0),
lhs: Reg(1),
rhs: Reg(2),
pol: Policy::new(),
};
assert_eq!(d.to_string(), "dadd r0, r1, r2");
let c = Inst::Call {
dst: Reg(3),
func: FuncId(1),
argc: 2,
};
assert_eq!(c.to_string(), "call r3, f1, 2");
}
#[test]
fn immediates_keep_their_sign() {
let i = Inst::LoadInt {
dst: Reg(0),
val: -5,
ty: IntTy::I32,
};
assert_eq!(Inst::from_bytes(i.to_bytes()), Ok(i));
assert_eq!(i.to_string(), "load_int.i32 r0, -5");
}
}