macro_rules! opcodes {
(
$(
$(#[$vmeta:meta])*
$name:ident = $byte:literal , operands = $operands:literal ;
)*
) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum OpCode {
$(
$(#[$vmeta])*
$name = $byte,
)*
}
impl OpCode {
pub const ALL: &'static [OpCode] = &[ $( OpCode::$name ),* ];
pub fn from_byte(byte: u8) -> Option<OpCode> {
match byte {
$( $byte => Some(OpCode::$name), )*
_ => None,
}
}
#[must_use]
pub fn to_byte(self) -> u8 {
self as u8
}
#[must_use]
pub fn disasm_operands(self) -> u8 {
match self {
$( OpCode::$name => $operands, )*
}
}
}
};
}
opcodes! {
Constant = 0, operands = 1;
Null = 1, operands = 0;
True = 2, operands = 0;
False = 3, operands = 0;
Add = 10, operands = 0;
Sub = 11, operands = 0;
Mul = 12, operands = 0;
Div = 13, operands = 0;
Negate = 14, operands = 0;
Not = 20, operands = 0;
And = 21, operands = 0;
Or = 22, operands = 0;
Implication = 23, operands = 0;
Equal = 30, operands = 0;
NotEqual = 31, operands = 0;
Less = 32, operands = 0;
Greater = 33, operands = 0;
LessEqual = 34, operands = 0;
GreaterEqual = 35, operands = 0;
Interpolate = 40, operands = 1;
GetLocal = 50, operands = 1;
SetLocal = 51, operands = 1;
GetUpvalue = 52, operands = 1;
SetUpvalue = 53, operands = 1;
PushWith = 54, operands = 0;
PopWith = 55, operands = 0;
LookupWith = 56, operands = 1;
MakeAttrs = 60, operands = 1;
GetAttr = 61, operands = 1;
HasAttr = 62, operands = 1;
UpdateAttrs = 63, operands = 0;
SelectOrDefault = 64, operands = 1;
DynGetAttr = 65, operands = 0;
DynHasAttr = 66, operands = 0;
DynSelectOrDefault = 67, operands = 0;
MakeList = 70, operands = 1;
Concat = 71, operands = 0;
MakeClosure = 80, operands = 1;
Call = 81, operands = 0;
Return = 82, operands = 0;
TailCall = 83, operands = 0;
Jump = 90, operands = 1;
JumpIfFalse = 91, operands = 1;
JumpIfTrue = 92, operands = 1;
Assert = 100, operands = 0;
Throw = 101, operands = 0;
Pop = 110, operands = 0;
Dup = 111, operands = 0;
GetLocalAttr = 120, operands = 2;
GetLocalCall = 121, operands = 1;
PushBuiltins = 130, operands = 1;
CallBuiltin = 131, operands = 2;
MakeThunk = 140, operands = 1;
Force = 141, operands = 1;
PatchThunkUpvalues = 142, operands = 0;
MakeLazyThunk = 143, operands = 0;
Import = 150, operands = 1;
}
#[cfg(test)]
mod tests {
use super::*;
const PINNED_BYTES: &[(OpCode, u8)] = &[
(OpCode::Constant, 0),
(OpCode::Null, 1),
(OpCode::True, 2),
(OpCode::False, 3),
(OpCode::Add, 10),
(OpCode::Sub, 11),
(OpCode::Mul, 12),
(OpCode::Div, 13),
(OpCode::Negate, 14),
(OpCode::Not, 20),
(OpCode::And, 21),
(OpCode::Or, 22),
(OpCode::Implication, 23),
(OpCode::Equal, 30),
(OpCode::NotEqual, 31),
(OpCode::Less, 32),
(OpCode::Greater, 33),
(OpCode::LessEqual, 34),
(OpCode::GreaterEqual, 35),
(OpCode::Interpolate, 40),
(OpCode::GetLocal, 50),
(OpCode::SetLocal, 51),
(OpCode::GetUpvalue, 52),
(OpCode::SetUpvalue, 53),
(OpCode::PushWith, 54),
(OpCode::PopWith, 55),
(OpCode::LookupWith, 56),
(OpCode::MakeAttrs, 60),
(OpCode::GetAttr, 61),
(OpCode::HasAttr, 62),
(OpCode::UpdateAttrs, 63),
(OpCode::SelectOrDefault, 64),
(OpCode::DynGetAttr, 65),
(OpCode::DynHasAttr, 66),
(OpCode::DynSelectOrDefault, 67),
(OpCode::MakeList, 70),
(OpCode::Concat, 71),
(OpCode::MakeClosure, 80),
(OpCode::Call, 81),
(OpCode::Return, 82),
(OpCode::TailCall, 83),
(OpCode::Jump, 90),
(OpCode::JumpIfFalse, 91),
(OpCode::JumpIfTrue, 92),
(OpCode::Assert, 100),
(OpCode::Throw, 101),
(OpCode::Pop, 110),
(OpCode::Dup, 111),
(OpCode::GetLocalAttr, 120),
(OpCode::GetLocalCall, 121),
(OpCode::PushBuiltins, 130),
(OpCode::CallBuiltin, 131),
(OpCode::MakeThunk, 140),
(OpCode::Force, 141),
(OpCode::PatchThunkUpvalues, 142),
(OpCode::MakeLazyThunk, 143),
(OpCode::Import, 150),
];
const PINNED_OPERANDS: &[(OpCode, u8)] = &[
(OpCode::Constant, 1),
(OpCode::GetLocal, 1),
(OpCode::SetLocal, 1),
(OpCode::GetUpvalue, 1),
(OpCode::SetUpvalue, 1),
(OpCode::LookupWith, 1),
(OpCode::GetAttr, 1),
(OpCode::HasAttr, 1),
(OpCode::SelectOrDefault, 1),
(OpCode::MakeAttrs, 1),
(OpCode::MakeList, 1),
(OpCode::Interpolate, 1),
(OpCode::MakeClosure, 1),
(OpCode::Jump, 1),
(OpCode::JumpIfFalse, 1),
(OpCode::JumpIfTrue, 1),
(OpCode::GetLocalCall, 1),
(OpCode::PushBuiltins, 1),
(OpCode::MakeThunk, 1),
(OpCode::Force, 1),
(OpCode::Import, 1),
(OpCode::GetLocalAttr, 2),
(OpCode::CallBuiltin, 2),
(OpCode::Null, 0),
(OpCode::True, 0),
(OpCode::False, 0),
(OpCode::Add, 0),
(OpCode::Sub, 0),
(OpCode::Mul, 0),
(OpCode::Div, 0),
(OpCode::Negate, 0),
(OpCode::Not, 0),
(OpCode::And, 0),
(OpCode::Or, 0),
(OpCode::Implication, 0),
(OpCode::Equal, 0),
(OpCode::NotEqual, 0),
(OpCode::Less, 0),
(OpCode::Greater, 0),
(OpCode::LessEqual, 0),
(OpCode::GreaterEqual, 0),
(OpCode::PushWith, 0),
(OpCode::PopWith, 0),
(OpCode::UpdateAttrs, 0),
(OpCode::DynGetAttr, 0),
(OpCode::DynHasAttr, 0),
(OpCode::DynSelectOrDefault, 0),
(OpCode::Concat, 0),
(OpCode::Call, 0),
(OpCode::Return, 0),
(OpCode::TailCall, 0),
(OpCode::Assert, 0),
(OpCode::Throw, 0),
(OpCode::Pop, 0),
(OpCode::Dup, 0),
(OpCode::PatchThunkUpvalues, 0),
(OpCode::MakeLazyThunk, 0),
];
#[test]
fn all_covers_full_instruction_set() {
assert_eq!(OpCode::ALL.len(), 57, "OpCode::ALL must list every opcode");
}
#[test]
fn opcode_bytes_are_byte_identical() {
assert_eq!(
PINNED_BYTES.len(),
OpCode::ALL.len(),
"PINNED_BYTES must pin every opcode"
);
for &(op, byte) in PINNED_BYTES {
assert_eq!(op.to_byte(), byte, "byte for {op:?} changed");
assert_eq!(op as u8, byte, "`as u8` for {op:?} changed");
assert_eq!(
OpCode::from_byte(byte),
Some(op),
"from_byte({byte}) must decode to {op:?}"
);
}
}
#[test]
fn roundtrip_all_opcodes() {
for &op in OpCode::ALL {
let byte = op.to_byte();
let decoded = OpCode::from_byte(byte)
.unwrap_or_else(|| panic!("failed to decode opcode byte {byte} for {op:?}"));
assert_eq!(decoded, op, "roundtrip failed for {op:?}");
}
}
#[test]
fn disasm_operands_are_byte_identical() {
assert_eq!(
PINNED_OPERANDS.len(),
OpCode::ALL.len(),
"PINNED_OPERANDS must pin every opcode"
);
for &(op, ar) in PINNED_OPERANDS {
assert_eq!(op.disasm_operands(), ar, "operand arity for {op:?} changed");
}
}
#[test]
fn invalid_byte_returns_none() {
assert!(OpCode::from_byte(255).is_none());
assert!(OpCode::from_byte(200).is_none());
assert!(OpCode::from_byte(5).is_none());
}
}