#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum OpCode {
Nop = 0x00,
Const = 0x01,
LoadLocal = 0x02,
StoreLocal = 0x03,
LoadGlobal = 0x04,
StoreGlobal = 0x05,
LoadField = 0x06,
StoreField = 0x07,
LoadIndex = 0x08,
StoreIndex = 0x09,
LoadUpvalue = 0x0A,
StoreUpvalue = 0x0B,
Move = 0x0C,
Pop = 0x0D,
Dup = 0x0E,
Swap = 0x0F,
Add = 0x10,
Sub = 0x11,
Mul = 0x12,
Div = 0x13,
Mod = 0x14,
Neg = 0x15,
BitAnd = 0x16,
BitOr = 0x17,
BitXor = 0x18,
BitNot = 0x19,
ShiftLeft = 0x1A,
ShiftRight = 0x1B,
NewObject = 0x1C,
NewArray = 0x1D,
NewClosure = 0x1E,
GetType = 0x1F,
Equal = 0x20,
NotEqual = 0x21,
Greater = 0x22,
GreaterEqual = 0x23,
Less = 0x24,
LessEqual = 0x25,
Not = 0x26,
And = 0x27,
Or = 0x28,
InstanceOf = 0x29,
InlineCache = 0x2A,
Specialize = 0x2B,
Deoptimize = 0x2C,
NewTuple = 0x2D,
Jump = 0x30,
JumpIfTrue = 0x31,
JumpIfFalse = 0x32,
Call = 0x33,
TailCall = 0x34,
Return = 0x35,
Throw = 0x36,
EnterTry = 0x37,
ExitTry = 0x38,
For = 0x39,
MethodCall = 0x3A,
Match = 0x3B,
}
impl OpCode {
#[inline]
pub const fn to_u8(self) -> u8 {
self as u8
}
const VALID_OPCODES: [bool; 64] = {
let mut valid = [false; 64];
let codes: &[u8] = &[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B,
0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
0x2A, 0x2B, 0x2C, 0x2D, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3A, 0x3B,
];
let mut i = 0;
while i < codes.len() {
valid[codes[i] as usize] = true;
i += 1;
}
valid
};
pub fn from_u8(value: u8) -> Option<Self> {
if (value as usize) < Self::VALID_OPCODES.len() && Self::VALID_OPCODES[value as usize] {
#[allow(unsafe_code)]
Some(unsafe { std::mem::transmute::<u8, Self>(value) })
} else {
None
}
}
const OPCODE_NAMES: [&'static str; 60] = [
"Nop",
"Const",
"LoadLocal",
"StoreLocal",
"LoadGlobal",
"StoreGlobal",
"LoadField",
"StoreField",
"LoadIndex",
"StoreIndex",
"LoadUpvalue",
"StoreUpvalue",
"Move",
"Pop",
"Dup",
"Swap",
"Add",
"Sub",
"Mul",
"Div",
"Mod",
"Neg",
"BitAnd",
"BitOr",
"BitXor",
"BitNot",
"ShiftLeft",
"ShiftRight",
"NewObject",
"NewArray",
"NewClosure",
"GetType",
"Equal",
"NotEqual",
"Greater",
"GreaterEqual",
"Less",
"LessEqual",
"Not",
"And",
"Or",
"InstanceOf",
"InlineCache",
"Specialize",
"Deoptimize",
"NewTuple",
"Reserved2E",
"Reserved2F",
"Jump",
"JumpIfTrue",
"JumpIfFalse",
"Call",
"TailCall",
"Return",
"Throw",
"EnterTry",
"ExitTry",
"For",
"MethodCall",
"Match",
];
pub fn name(self) -> &'static str {
let idx = self as u8 as usize;
if idx < Self::OPCODE_NAMES.len() {
Self::OPCODE_NAMES[idx]
} else {
"Unknown"
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_OPCODES: &[OpCode] = &[
OpCode::Nop,
OpCode::Const,
OpCode::LoadLocal,
OpCode::StoreLocal,
OpCode::LoadGlobal,
OpCode::StoreGlobal,
OpCode::LoadField,
OpCode::StoreField,
OpCode::LoadIndex,
OpCode::StoreIndex,
OpCode::LoadUpvalue,
OpCode::StoreUpvalue,
OpCode::Move,
OpCode::Pop,
OpCode::Dup,
OpCode::Swap,
OpCode::Add,
OpCode::Sub,
OpCode::Mul,
OpCode::Div,
OpCode::Mod,
OpCode::Neg,
OpCode::BitAnd,
OpCode::BitOr,
OpCode::BitXor,
OpCode::BitNot,
OpCode::ShiftLeft,
OpCode::ShiftRight,
OpCode::NewObject,
OpCode::NewArray,
OpCode::NewClosure,
OpCode::GetType,
OpCode::Equal,
OpCode::NotEqual,
OpCode::Greater,
OpCode::GreaterEqual,
OpCode::Less,
OpCode::LessEqual,
OpCode::Not,
OpCode::And,
OpCode::Or,
OpCode::InstanceOf,
OpCode::InlineCache,
OpCode::Specialize,
OpCode::Deoptimize,
OpCode::NewTuple,
OpCode::Jump,
OpCode::JumpIfTrue,
OpCode::JumpIfFalse,
OpCode::Call,
OpCode::TailCall,
OpCode::Return,
OpCode::Throw,
OpCode::EnterTry,
OpCode::ExitTry,
OpCode::For,
OpCode::MethodCall,
OpCode::Match,
];
#[test]
fn test_opcode_to_u8_roundtrip_all() {
for opcode in ALL_OPCODES {
let u8_val = opcode.to_u8();
let recovered = OpCode::from_u8(u8_val).expect("Failed to recover opcode");
assert_eq!(*opcode, recovered, "Opcode roundtrip failed for {opcode:?}");
}
}
#[test]
fn test_invalid_opcode() {
assert!(OpCode::from_u8(0xFF).is_none());
assert!(OpCode::from_u8(0x60).is_none());
assert!(OpCode::from_u8(0xAA).is_none());
assert!(OpCode::from_u8(0x3C).is_none()); assert!(OpCode::from_u8(0x2E).is_none()); }
#[test]
fn test_opcode_names_all() {
for opcode in ALL_OPCODES {
let name = opcode.name();
assert!(!name.is_empty(), "Opcode {opcode:?} has empty name");
}
}
#[test]
fn test_opcode_names_specific() {
assert_eq!(OpCode::Nop.name(), "Nop");
assert_eq!(OpCode::Const.name(), "Const");
assert_eq!(OpCode::LoadLocal.name(), "LoadLocal");
assert_eq!(OpCode::StoreLocal.name(), "StoreLocal");
assert_eq!(OpCode::LoadGlobal.name(), "LoadGlobal");
assert_eq!(OpCode::StoreGlobal.name(), "StoreGlobal");
assert_eq!(OpCode::LoadField.name(), "LoadField");
assert_eq!(OpCode::StoreField.name(), "StoreField");
assert_eq!(OpCode::LoadIndex.name(), "LoadIndex");
assert_eq!(OpCode::StoreIndex.name(), "StoreIndex");
assert_eq!(OpCode::LoadUpvalue.name(), "LoadUpvalue");
assert_eq!(OpCode::StoreUpvalue.name(), "StoreUpvalue");
assert_eq!(OpCode::Move.name(), "Move");
assert_eq!(OpCode::Pop.name(), "Pop");
assert_eq!(OpCode::Dup.name(), "Dup");
assert_eq!(OpCode::Swap.name(), "Swap");
assert_eq!(OpCode::Add.name(), "Add");
assert_eq!(OpCode::Sub.name(), "Sub");
assert_eq!(OpCode::Mul.name(), "Mul");
assert_eq!(OpCode::Div.name(), "Div");
assert_eq!(OpCode::Mod.name(), "Mod");
assert_eq!(OpCode::Neg.name(), "Neg");
assert_eq!(OpCode::BitAnd.name(), "BitAnd");
assert_eq!(OpCode::BitOr.name(), "BitOr");
assert_eq!(OpCode::BitXor.name(), "BitXor");
assert_eq!(OpCode::BitNot.name(), "BitNot");
assert_eq!(OpCode::ShiftLeft.name(), "ShiftLeft");
assert_eq!(OpCode::ShiftRight.name(), "ShiftRight");
assert_eq!(OpCode::NewObject.name(), "NewObject");
assert_eq!(OpCode::NewArray.name(), "NewArray");
assert_eq!(OpCode::NewClosure.name(), "NewClosure");
assert_eq!(OpCode::GetType.name(), "GetType");
assert_eq!(OpCode::Equal.name(), "Equal");
assert_eq!(OpCode::NotEqual.name(), "NotEqual");
assert_eq!(OpCode::Greater.name(), "Greater");
assert_eq!(OpCode::GreaterEqual.name(), "GreaterEqual");
assert_eq!(OpCode::Less.name(), "Less");
assert_eq!(OpCode::LessEqual.name(), "LessEqual");
assert_eq!(OpCode::Not.name(), "Not");
assert_eq!(OpCode::And.name(), "And");
assert_eq!(OpCode::Or.name(), "Or");
assert_eq!(OpCode::InstanceOf.name(), "InstanceOf");
assert_eq!(OpCode::InlineCache.name(), "InlineCache");
assert_eq!(OpCode::Specialize.name(), "Specialize");
assert_eq!(OpCode::Deoptimize.name(), "Deoptimize");
assert_eq!(OpCode::NewTuple.name(), "NewTuple");
assert_eq!(OpCode::Jump.name(), "Jump");
assert_eq!(OpCode::JumpIfTrue.name(), "JumpIfTrue");
assert_eq!(OpCode::JumpIfFalse.name(), "JumpIfFalse");
assert_eq!(OpCode::Call.name(), "Call");
assert_eq!(OpCode::TailCall.name(), "TailCall");
assert_eq!(OpCode::Return.name(), "Return");
assert_eq!(OpCode::Throw.name(), "Throw");
assert_eq!(OpCode::EnterTry.name(), "EnterTry");
assert_eq!(OpCode::ExitTry.name(), "ExitTry");
assert_eq!(OpCode::For.name(), "For");
assert_eq!(OpCode::MethodCall.name(), "MethodCall");
assert_eq!(OpCode::Match.name(), "Match");
}
#[test]
fn test_opcode_u8_values() {
assert_eq!(OpCode::Nop.to_u8(), 0x00);
assert_eq!(OpCode::Swap.to_u8(), 0x0F);
assert_eq!(OpCode::Add.to_u8(), 0x10);
assert_eq!(OpCode::GetType.to_u8(), 0x1F);
assert_eq!(OpCode::Equal.to_u8(), 0x20);
assert_eq!(OpCode::NewTuple.to_u8(), 0x2D);
assert_eq!(OpCode::Jump.to_u8(), 0x30);
assert_eq!(OpCode::Match.to_u8(), 0x3B);
}
#[test]
fn test_from_u8_boundary_values() {
assert!(OpCode::from_u8(0x00).is_some()); assert!(OpCode::from_u8(0x0F).is_some()); assert!(OpCode::from_u8(0x10).is_some()); assert!(OpCode::from_u8(0x1F).is_some()); assert!(OpCode::from_u8(0x20).is_some()); assert!(OpCode::from_u8(0x2D).is_some()); assert!(OpCode::from_u8(0x2E).is_none()); assert!(OpCode::from_u8(0x2F).is_none()); assert!(OpCode::from_u8(0x30).is_some()); assert!(OpCode::from_u8(0x3B).is_some()); assert!(OpCode::from_u8(0x3C).is_none()); }
#[test]
fn test_opcode_clone() {
let op = OpCode::Add;
let cloned = op.clone();
assert_eq!(op, cloned);
}
#[test]
fn test_opcode_copy() {
let op = OpCode::Sub;
let copied = op;
assert_eq!(op, copied);
}
#[test]
fn test_opcode_eq() {
assert_eq!(OpCode::Add, OpCode::Add);
assert_ne!(OpCode::Add, OpCode::Sub);
}
#[test]
fn test_opcode_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(OpCode::Add);
set.insert(OpCode::Sub);
assert!(set.contains(&OpCode::Add));
assert!(set.contains(&OpCode::Sub));
assert!(!set.contains(&OpCode::Mul));
}
#[test]
fn test_opcode_debug() {
let debug_str = format!("{:?}", OpCode::Add);
assert!(debug_str.contains("Add"));
}
#[test]
fn test_stack_operations_range() {
for i in 0x00..=0x0F {
assert!(
OpCode::from_u8(i).is_some(),
"Stack op 0x{:02X} should exist",
i
);
}
}
#[test]
fn test_arithmetic_operations_range() {
for i in 0x10..=0x1F {
assert!(
OpCode::from_u8(i).is_some(),
"Arithmetic op 0x{:02X} should exist",
i
);
}
}
#[test]
fn test_control_flow_operations_range() {
for i in 0x30..=0x3B {
assert!(
OpCode::from_u8(i).is_some(),
"Control flow op 0x{:02X} should exist",
i
);
}
}
#[test]
fn test_opcode_nop_is_zero() {
assert_eq!(OpCode::Nop.to_u8(), 0);
}
#[test]
fn test_opcode_category_stack() {
let stack_ops = [
OpCode::Nop,
OpCode::Const,
OpCode::LoadLocal,
OpCode::StoreLocal,
OpCode::LoadGlobal,
OpCode::StoreGlobal,
OpCode::LoadField,
OpCode::StoreField,
OpCode::LoadIndex,
OpCode::StoreIndex,
OpCode::LoadUpvalue,
OpCode::StoreUpvalue,
OpCode::Move,
OpCode::Pop,
OpCode::Dup,
OpCode::Swap,
];
for op in stack_ops {
assert!(op.to_u8() <= 0x0F, "{:?} should be in stack range", op);
}
}
#[test]
fn test_opcode_category_arithmetic() {
let arith_ops = [
OpCode::Add,
OpCode::Sub,
OpCode::Mul,
OpCode::Div,
OpCode::Mod,
OpCode::Neg,
OpCode::BitAnd,
OpCode::BitOr,
OpCode::BitXor,
OpCode::BitNot,
OpCode::ShiftLeft,
OpCode::ShiftRight,
];
for op in arith_ops {
let val = op.to_u8();
assert!(
val >= 0x10 && val <= 0x1F,
"{:?} should be in arithmetic range",
op
);
}
}
#[test]
fn test_opcode_category_comparison() {
let cmp_ops = [
OpCode::Equal,
OpCode::NotEqual,
OpCode::Greater,
OpCode::GreaterEqual,
OpCode::Less,
OpCode::LessEqual,
OpCode::Not,
OpCode::And,
OpCode::Or,
];
for op in cmp_ops {
let val = op.to_u8();
assert!(
val >= 0x20 && val <= 0x2F,
"{:?} should be in comparison range",
op
);
}
}
#[test]
fn test_opcode_category_control() {
let ctrl_ops = [
OpCode::Jump,
OpCode::JumpIfTrue,
OpCode::JumpIfFalse,
OpCode::Call,
OpCode::TailCall,
OpCode::Return,
OpCode::Throw,
OpCode::EnterTry,
OpCode::ExitTry,
OpCode::For,
OpCode::MethodCall,
OpCode::Match,
];
for op in ctrl_ops {
let val = op.to_u8();
assert!(
val >= 0x30 && val <= 0x3F,
"{:?} should be in control range",
op
);
}
}
#[test]
fn test_all_opcodes_count() {
assert_eq!(ALL_OPCODES.len(), 58);
}
#[test]
fn test_opcode_unique_values() {
use std::collections::HashSet;
let mut values = HashSet::new();
for op in ALL_OPCODES {
let val = op.to_u8();
assert!(values.insert(val), "Duplicate opcode value 0x{:02X}", val);
}
}
}