use crate::error::{StatorError, StatorResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperandType {
Register,
RegisterCount,
Immediate,
ConstantPoolIdx,
RuntimeId,
FeedbackSlot,
JumpOffset,
Flag,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum OperandWidth {
Narrow = 1,
Wide = 2,
ExtraWide = 4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Operand {
Register(u32),
RegisterCount(u32),
Immediate(i32),
ConstantPoolIdx(u32),
RuntimeId(u32),
FeedbackSlot(u32),
JumpOffset(i32),
Flag(u8),
}
impl Operand {
fn required_width(self) -> OperandWidth {
match self {
Operand::Register(v)
| Operand::RegisterCount(v)
| Operand::ConstantPoolIdx(v)
| Operand::FeedbackSlot(v)
| Operand::RuntimeId(v) => {
if v <= u8::MAX as u32 {
OperandWidth::Narrow
} else if v <= u16::MAX as u32 {
OperandWidth::Wide
} else {
OperandWidth::ExtraWide
}
}
Operand::Immediate(v) | Operand::JumpOffset(v) => {
if (i8::MIN as i32..=i8::MAX as i32).contains(&v) {
OperandWidth::Narrow
} else if (i16::MIN as i32..=i16::MAX as i32).contains(&v) {
OperandWidth::Wide
} else {
OperandWidth::ExtraWide
}
}
Operand::Flag(_) => OperandWidth::Narrow,
}
}
}
const MAX_INSTRUCTION_OPERANDS: usize = 5;
const EMPTY_OPERAND: Operand = Operand::Flag(0);
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct Instruction {
pub opcode: Opcode,
operand_count: u8,
operands: [Operand; MAX_INSTRUCTION_OPERANDS],
}
impl Instruction {
fn collect_operands(
opcode: Opcode,
operands: impl IntoIterator<Item = Operand>,
) -> StatorResult<([Operand; MAX_INSTRUCTION_OPERANDS], u8)> {
let mut collected = [EMPTY_OPERAND; MAX_INSTRUCTION_OPERANDS];
let mut operand_count = 0usize;
for operand in operands {
if operand_count == MAX_INSTRUCTION_OPERANDS {
return Err(StatorError::Internal(format!(
"{opcode:?} exceeds inline operand capacity of {MAX_INSTRUCTION_OPERANDS}"
)));
}
collected[operand_count] = operand;
operand_count += 1;
}
Ok((collected, operand_count as u8))
}
pub fn new(opcode: Opcode, operands: impl IntoIterator<Item = Operand>) -> StatorResult<Self> {
let (operands, operand_count) = Self::collect_operands(opcode, operands)?;
let expected = opcode.operand_types().len();
if usize::from(operand_count) != expected {
return Err(StatorError::Internal(format!(
"{opcode:?} expects {expected} operand(s), got {}",
operand_count
)));
}
Ok(Self {
opcode,
operand_count,
operands,
})
}
pub fn new_unchecked(opcode: Opcode, operands: impl IntoIterator<Item = Operand>) -> Self {
let (operands, operand_count) = Self::collect_operands(opcode, operands)
.expect("opcode operand metadata must fit in inline storage");
Self {
opcode,
operand_count,
operands,
}
}
#[inline(always)]
pub fn operand_count(&self) -> usize {
usize::from(self.operand_count)
}
#[inline(always)]
pub fn operands(&self) -> &[Operand] {
&self.operands[..self.operand_count()]
}
#[inline(always)]
pub fn operand(&self, idx: usize) -> &Operand {
self.operand_at(idx).unwrap_or_else(|| {
panic!(
"{:?} operand index {idx} out of bounds (len = {})",
self.opcode,
self.operand_count()
)
})
}
#[inline(always)]
pub fn operand_at(&self, idx: usize) -> Option<&Operand> {
self.operands().get(idx)
}
#[inline(always)]
pub fn operand_mut(&mut self, idx: usize) -> &mut Operand {
let len = self.operand_count();
if idx >= len {
panic!(
"{:?} operand index {idx} out of bounds (len = {len})",
self.opcode
);
}
&mut self.operands[idx]
}
#[inline(always)]
pub unsafe fn operand_unchecked(&self, idx: usize) -> &Operand {
debug_assert!(idx < self.operand_count());
unsafe { self.operands.get_unchecked(idx) }
}
#[inline(always)]
pub fn reg(&self, idx: usize) -> u32 {
match self.operand(idx) {
Operand::Register(register) => *register,
operand => panic!("expected register operand, got {operand:?}"),
}
}
#[inline(always)]
pub fn register_count(&self, idx: usize) -> u32 {
match self.operand(idx) {
Operand::RegisterCount(count) => *count,
operand => panic!("expected register-count operand, got {operand:?}"),
}
}
#[inline(always)]
pub fn immediate(&self, idx: usize) -> i32 {
match self.operand(idx) {
Operand::Immediate(immediate) => *immediate,
operand => panic!("expected immediate operand, got {operand:?}"),
}
}
#[inline(always)]
pub fn constant_pool_idx(&self, idx: usize) -> u32 {
match self.operand(idx) {
Operand::ConstantPoolIdx(index) => *index,
operand => panic!("expected constant-pool operand, got {operand:?}"),
}
}
#[inline(always)]
pub fn feedback_slot(&self, idx: usize) -> u32 {
match self.operand(idx) {
Operand::FeedbackSlot(slot) => *slot,
operand => panic!("expected feedback-slot operand, got {operand:?}"),
}
}
#[inline(always)]
pub fn runtime_id(&self, idx: usize) -> u32 {
match self.operand(idx) {
Operand::RuntimeId(id) => *id,
operand => panic!("expected runtime-id operand, got {operand:?}"),
}
}
#[inline(always)]
pub fn jump_offset(&self, idx: usize) -> i32 {
match self.operand(idx) {
Operand::JumpOffset(offset) => *offset,
operand => panic!("expected jump-offset operand, got {operand:?}"),
}
}
#[inline(always)]
pub fn flag(&self, idx: usize) -> u8 {
match self.operand(idx) {
Operand::Flag(flag) => *flag,
operand => panic!("expected flag operand, got {operand:?}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Opcode {
LdaZero,
LdaSmi,
LdaUndefined,
LdaTheHole,
LdaNull,
LdaTrue,
LdaFalse,
LdaConstant,
LdaGlobal,
LdaGlobalInsideTypeof,
StaGlobal,
LdaContextSlot,
LdaImmutableContextSlot,
LdaCurrentContextSlot,
LdaImmutableCurrentContextSlot,
StaContextSlot,
StaCurrentContextSlot,
LdaLookupSlot,
LdaLookupContextSlot,
LdaLookupGlobalSlot,
LdaLookupSlotInsideTypeof,
LdaLookupContextSlotInsideTypeof,
LdaLookupGlobalSlotInsideTypeof,
StaLookupSlot,
DeleteLookupSlot,
Ldar,
Star,
Mov,
LdaNamedProperty,
LdaNamedPropertyFromSuper,
LdaKeyedProperty,
LdaEnumeratedKeyedProperty,
StaNamedProperty,
StaNamedOwnProperty,
StaKeyedProperty,
DefineNamedOwnProperty,
DefineKeyedOwnProperty,
StaInArrayLiteral,
DefineKeyedOwnPropertyInLiteral,
SetLiteralPrototype,
DefineGetterProperty,
DefineSetterProperty,
DefineKeyedGetterProperty,
DefineKeyedSetterProperty,
CollectTypeProfile,
Add,
Sub,
Mul,
Div,
Mod,
Exp,
BitwiseOr,
BitwiseXor,
BitwiseAnd,
ShiftLeft,
ShiftRight,
ShiftRightLogical,
AddSmi,
SubSmi,
MulSmi,
DivSmi,
ModSmi,
ExpSmi,
BitwiseOrSmi,
BitwiseXorSmi,
BitwiseAndSmi,
ShiftLeftSmi,
ShiftRightSmi,
ShiftRightLogicalSmi,
Inc,
Dec,
Negate,
BitwiseNot,
ToBooleanLogicalNot,
LogicalNot,
TypeOf,
DeletePropertyStrict,
DeletePropertySloppy,
TestEqual,
TestNotEqual,
TestEqualStrict,
TestLessThan,
TestGreaterThan,
TestLessThanOrEqual,
TestGreaterThanOrEqual,
TestReferenceEqual,
TestInstanceOf,
TestIn,
TestUndetectable,
TestNull,
TestUndefined,
TestTypeOf,
ToName,
ToNumber,
ToNumeric,
ToObject,
ToString,
ToBoolean,
CallAnyReceiver,
CallProperty,
CallProperty0,
CallProperty1,
CallProperty2,
CallUndefinedReceiver0,
CallUndefinedReceiver1,
CallUndefinedReceiver2,
CallWithSpread,
CallRuntime,
CallRuntimeForPair,
CallJSRuntime,
InvokeIntrinsic,
CallDirectEval,
TailCall,
Construct,
ConstructWithSpread,
ConstructForwardAllArgs,
GetIterator,
GetAsyncIterator,
IteratorNext,
IteratorClose,
CopyDataProperties,
JumpLoop,
Jump,
JumpConstant,
JumpIfTrue,
JumpIfTrueConstant,
JumpIfFalse,
JumpIfFalseConstant,
JumpIfNull,
JumpIfNotNull,
JumpIfUndefined,
JumpIfNotUndefined,
JumpIfUndefinedOrNull,
JumpIfJSReceiver,
JumpIfForInDone,
JumpIfToBooleanTrue,
JumpIfToBooleanFalse,
JumpIfToBooleanTrueConstant,
JumpIfToBooleanFalseConstant,
JumpIfNullConstant,
JumpIfNotNullConstant,
JumpIfUndefinedConstant,
JumpIfNotUndefinedConstant,
JumpIfUndefinedOrNullConstant,
JumpIfJSReceiverConstant,
Return,
ThrowReferenceErrorIfHole,
ThrowSuperNotCalledIfHole,
ThrowSuperAlreadyCalledIfNotHole,
ThrowIfNullOrUndefined,
Throw,
ReThrow,
SetPendingMessage,
Debugger,
CreateClosure,
CreateBlockContext,
CreateCatchContext,
CreateFunctionContext,
CreateEvalContext,
CreateWithContext,
CreateMappedArguments,
CreateUnmappedArguments,
CreateRestParameter,
CreateRegExpLiteral,
CreateArrayLiteral,
CreateArrayFromIterable,
CreateEmptyArrayLiteral,
CreateObjectLiteral,
CreateEmptyObjectLiteral,
CreateObjectFromIterable,
PushContext,
PopContext,
ForInEnumerate,
ForInPrepare,
ForInNext,
ForInStep,
GetTemplateObject,
StackCheck,
SetExpressionPosition,
SetExpressionPositionFromEnd,
ResumeGenerator,
GetGeneratorState,
SuspendGenerator,
SetGeneratorState,
SwitchOnGeneratorState,
CreateClass,
TestPrivateBrand,
DefinePrivateBrand,
DefineClassNamedOwnProperty,
DefineClassGetterProperty,
DefineClassSetterProperty,
DefineClassKeyedOwnProperty,
DefineClassKeyedGetterProperty,
DefineClassKeyedSetterProperty,
LdaModuleVariable,
StaModuleVariable,
LdaImportMeta,
LdaNewTarget,
GetModuleNamespace,
Wide,
ExtraWide,
Illegal,
LdarAddStar,
LdarSubStar,
LdarMulStar,
TestLessThanJump,
TestGreaterThanJump,
TestEqualJump,
TestNotEqualJump,
TestEqualStrictJump,
TestLessThanOrEqualJump,
TestGreaterThanOrEqualJump,
AddSmiStar,
SubSmiStar,
MulSmiStar,
IncStar,
LdaSmiStar,
LdaGlobalStar,
Nop,
}
const MAX_OPCODE: u8 = Opcode::Nop as u8;
impl Opcode {
pub fn try_from_u8(byte: u8) -> StatorResult<Self> {
if byte > MAX_OPCODE {
return Err(StatorError::Internal(format!(
"unknown opcode byte: 0x{byte:02x}"
)));
}
Ok(unsafe { std::mem::transmute::<u8, Opcode>(byte) })
}
pub fn operand_types(self) -> &'static [OperandType] {
use OperandType::*;
match self {
Opcode::LdaZero => &[],
Opcode::LdaSmi => &[Immediate],
Opcode::LdaUndefined => &[],
Opcode::LdaTheHole => &[],
Opcode::LdaNull => &[],
Opcode::LdaTrue => &[],
Opcode::LdaFalse => &[],
Opcode::LdaConstant => &[ConstantPoolIdx],
Opcode::LdaGlobal => &[ConstantPoolIdx, FeedbackSlot],
Opcode::LdaGlobalInsideTypeof => &[ConstantPoolIdx, FeedbackSlot],
Opcode::StaGlobal => &[ConstantPoolIdx, FeedbackSlot],
Opcode::LdaContextSlot => &[Register, ConstantPoolIdx, Immediate],
Opcode::LdaImmutableContextSlot => &[Register, ConstantPoolIdx, Immediate],
Opcode::LdaCurrentContextSlot => &[ConstantPoolIdx],
Opcode::LdaImmutableCurrentContextSlot => &[ConstantPoolIdx],
Opcode::StaContextSlot => &[Register, ConstantPoolIdx, Immediate],
Opcode::StaCurrentContextSlot => &[ConstantPoolIdx],
Opcode::LdaLookupSlot => &[ConstantPoolIdx],
Opcode::LdaLookupContextSlot => &[ConstantPoolIdx, ConstantPoolIdx, Immediate],
Opcode::LdaLookupGlobalSlot => &[ConstantPoolIdx, FeedbackSlot, Immediate],
Opcode::LdaLookupSlotInsideTypeof => &[ConstantPoolIdx],
Opcode::LdaLookupContextSlotInsideTypeof => {
&[ConstantPoolIdx, ConstantPoolIdx, Immediate]
}
Opcode::LdaLookupGlobalSlotInsideTypeof => &[ConstantPoolIdx, FeedbackSlot, Immediate],
Opcode::StaLookupSlot => &[ConstantPoolIdx, Flag],
Opcode::DeleteLookupSlot => &[ConstantPoolIdx],
Opcode::Ldar => &[Register],
Opcode::Star => &[Register],
Opcode::Mov => &[Register, Register],
Opcode::LdaNamedProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::LdaNamedPropertyFromSuper => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::LdaKeyedProperty => &[Register, FeedbackSlot],
Opcode::LdaEnumeratedKeyedProperty => &[Register, Register, Register],
Opcode::StaNamedProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::StaNamedOwnProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::StaKeyedProperty => &[Register, Register, FeedbackSlot],
Opcode::DefineNamedOwnProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::DefineKeyedOwnProperty => &[Register, Register, Flag, FeedbackSlot],
Opcode::StaInArrayLiteral => &[Register, Register, FeedbackSlot],
Opcode::DefineKeyedOwnPropertyInLiteral => &[Register, Register, Flag, FeedbackSlot],
Opcode::SetLiteralPrototype => &[Register],
Opcode::DefineGetterProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::DefineSetterProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::DefineKeyedGetterProperty => &[Register, Register, FeedbackSlot],
Opcode::DefineKeyedSetterProperty => &[Register, Register, FeedbackSlot],
Opcode::CollectTypeProfile => &[Immediate],
Opcode::Add => &[Register, FeedbackSlot],
Opcode::Sub => &[Register, FeedbackSlot],
Opcode::Mul => &[Register, FeedbackSlot],
Opcode::Div => &[Register, FeedbackSlot],
Opcode::Mod => &[Register, FeedbackSlot],
Opcode::Exp => &[Register, FeedbackSlot],
Opcode::BitwiseOr => &[Register, FeedbackSlot],
Opcode::BitwiseXor => &[Register, FeedbackSlot],
Opcode::BitwiseAnd => &[Register, FeedbackSlot],
Opcode::ShiftLeft => &[Register, FeedbackSlot],
Opcode::ShiftRight => &[Register, FeedbackSlot],
Opcode::ShiftRightLogical => &[Register, FeedbackSlot],
Opcode::AddSmi => &[Immediate, FeedbackSlot],
Opcode::SubSmi => &[Immediate, FeedbackSlot],
Opcode::MulSmi => &[Immediate, FeedbackSlot],
Opcode::DivSmi => &[Immediate, FeedbackSlot],
Opcode::ModSmi => &[Immediate, FeedbackSlot],
Opcode::ExpSmi => &[Immediate, FeedbackSlot],
Opcode::BitwiseOrSmi => &[Immediate, FeedbackSlot],
Opcode::BitwiseXorSmi => &[Immediate, FeedbackSlot],
Opcode::BitwiseAndSmi => &[Immediate, FeedbackSlot],
Opcode::ShiftLeftSmi => &[Immediate, FeedbackSlot],
Opcode::ShiftRightSmi => &[Immediate, FeedbackSlot],
Opcode::ShiftRightLogicalSmi => &[Immediate, FeedbackSlot],
Opcode::Inc => &[FeedbackSlot],
Opcode::Dec => &[FeedbackSlot],
Opcode::Negate => &[FeedbackSlot],
Opcode::BitwiseNot => &[FeedbackSlot],
Opcode::ToBooleanLogicalNot => &[],
Opcode::LogicalNot => &[],
Opcode::TypeOf => &[FeedbackSlot],
Opcode::DeletePropertyStrict => &[Register],
Opcode::DeletePropertySloppy => &[Register],
Opcode::TestEqual => &[Register, FeedbackSlot],
Opcode::TestNotEqual => &[Register, FeedbackSlot],
Opcode::TestEqualStrict => &[Register, FeedbackSlot],
Opcode::TestLessThan => &[Register, FeedbackSlot],
Opcode::TestGreaterThan => &[Register, FeedbackSlot],
Opcode::TestLessThanOrEqual => &[Register, FeedbackSlot],
Opcode::TestGreaterThanOrEqual => &[Register, FeedbackSlot],
Opcode::TestReferenceEqual => &[Register],
Opcode::TestInstanceOf => &[Register, FeedbackSlot],
Opcode::TestIn => &[Register, FeedbackSlot],
Opcode::TestUndetectable => &[],
Opcode::TestNull => &[],
Opcode::TestUndefined => &[],
Opcode::TestTypeOf => &[Flag],
Opcode::ToName => &[Register],
Opcode::ToNumber => &[FeedbackSlot],
Opcode::ToNumeric => &[FeedbackSlot],
Opcode::ToObject => &[Register],
Opcode::ToString => &[],
Opcode::ToBoolean => &[FeedbackSlot],
Opcode::CallAnyReceiver => &[Register, Register, RegisterCount, FeedbackSlot],
Opcode::CallProperty => &[Register, Register, RegisterCount, FeedbackSlot],
Opcode::CallProperty0 => &[Register, Register, FeedbackSlot],
Opcode::CallProperty1 => &[Register, Register, Register, FeedbackSlot],
Opcode::CallProperty2 => &[Register, Register, Register, Register, FeedbackSlot],
Opcode::CallUndefinedReceiver0 => &[Register, FeedbackSlot],
Opcode::CallUndefinedReceiver1 => &[Register, Register, FeedbackSlot],
Opcode::CallUndefinedReceiver2 => &[Register, Register, Register, FeedbackSlot],
Opcode::CallWithSpread => &[Register, Register, RegisterCount, FeedbackSlot],
Opcode::CallRuntime => &[RuntimeId, Register, RegisterCount],
Opcode::CallRuntimeForPair => &[RuntimeId, Register, RegisterCount, Register],
Opcode::CallJSRuntime => &[ConstantPoolIdx, Register, RegisterCount],
Opcode::InvokeIntrinsic => &[RuntimeId, Register, RegisterCount],
Opcode::CallDirectEval => &[Register, Register, RegisterCount, FeedbackSlot],
Opcode::TailCall => &[Register, Register, RegisterCount, FeedbackSlot],
Opcode::Construct => &[Register, Register, RegisterCount, FeedbackSlot],
Opcode::ConstructWithSpread => &[Register, Register, RegisterCount, FeedbackSlot],
Opcode::ConstructForwardAllArgs => &[Register, FeedbackSlot],
Opcode::GetIterator => &[Register, FeedbackSlot, FeedbackSlot],
Opcode::GetAsyncIterator => &[Register, FeedbackSlot, FeedbackSlot],
Opcode::IteratorNext => &[Register, Register],
Opcode::IteratorClose => &[Register],
Opcode::CopyDataProperties => &[Register, Register],
Opcode::JumpLoop => &[JumpOffset, Immediate, FeedbackSlot],
Opcode::Jump => &[JumpOffset],
Opcode::JumpConstant => &[ConstantPoolIdx],
Opcode::JumpIfTrue => &[JumpOffset],
Opcode::JumpIfTrueConstant => &[ConstantPoolIdx],
Opcode::JumpIfFalse => &[JumpOffset],
Opcode::JumpIfFalseConstant => &[ConstantPoolIdx],
Opcode::JumpIfNull => &[JumpOffset],
Opcode::JumpIfNotNull => &[JumpOffset],
Opcode::JumpIfUndefined => &[JumpOffset],
Opcode::JumpIfNotUndefined => &[JumpOffset],
Opcode::JumpIfUndefinedOrNull => &[JumpOffset],
Opcode::JumpIfJSReceiver => &[JumpOffset],
Opcode::JumpIfForInDone => &[JumpOffset, Register, Register],
Opcode::JumpIfToBooleanTrue => &[JumpOffset],
Opcode::JumpIfToBooleanFalse => &[JumpOffset],
Opcode::JumpIfToBooleanTrueConstant => &[ConstantPoolIdx],
Opcode::JumpIfToBooleanFalseConstant => &[ConstantPoolIdx],
Opcode::JumpIfNullConstant => &[ConstantPoolIdx],
Opcode::JumpIfNotNullConstant => &[ConstantPoolIdx],
Opcode::JumpIfUndefinedConstant => &[ConstantPoolIdx],
Opcode::JumpIfNotUndefinedConstant => &[ConstantPoolIdx],
Opcode::JumpIfUndefinedOrNullConstant => &[ConstantPoolIdx],
Opcode::JumpIfJSReceiverConstant => &[ConstantPoolIdx],
Opcode::Return => &[],
Opcode::ThrowReferenceErrorIfHole => &[ConstantPoolIdx],
Opcode::ThrowSuperNotCalledIfHole => &[],
Opcode::ThrowSuperAlreadyCalledIfNotHole => &[],
Opcode::ThrowIfNullOrUndefined => &[],
Opcode::Throw => &[],
Opcode::ReThrow => &[],
Opcode::SetPendingMessage => &[],
Opcode::Debugger => &[],
Opcode::CreateClosure => &[ConstantPoolIdx, FeedbackSlot, Flag],
Opcode::CreateBlockContext => &[ConstantPoolIdx],
Opcode::CreateCatchContext => &[Register, ConstantPoolIdx],
Opcode::CreateFunctionContext => &[ConstantPoolIdx, Immediate],
Opcode::CreateEvalContext => &[ConstantPoolIdx, Immediate],
Opcode::CreateWithContext => &[Register, ConstantPoolIdx],
Opcode::CreateMappedArguments => &[],
Opcode::CreateUnmappedArguments => &[],
Opcode::CreateRestParameter => &[],
Opcode::CreateRegExpLiteral => &[ConstantPoolIdx, FeedbackSlot, Flag],
Opcode::CreateArrayLiteral => &[ConstantPoolIdx, FeedbackSlot, Flag],
Opcode::CreateArrayFromIterable => &[],
Opcode::CreateEmptyArrayLiteral => &[FeedbackSlot],
Opcode::CreateObjectLiteral => &[ConstantPoolIdx, FeedbackSlot, Flag],
Opcode::CreateEmptyObjectLiteral => &[],
Opcode::CreateObjectFromIterable => &[],
Opcode::PushContext => &[Register],
Opcode::PopContext => &[Register],
Opcode::ForInEnumerate => &[Register],
Opcode::ForInPrepare => &[Register, FeedbackSlot],
Opcode::ForInNext => &[Register, Register, Register, FeedbackSlot],
Opcode::ForInStep => &[Register],
Opcode::GetTemplateObject => &[ConstantPoolIdx, FeedbackSlot],
Opcode::StackCheck => &[],
Opcode::SetExpressionPosition => &[Immediate],
Opcode::SetExpressionPositionFromEnd => &[Immediate],
Opcode::ResumeGenerator => &[Register, Register, RegisterCount],
Opcode::GetGeneratorState => &[Register],
Opcode::SuspendGenerator => &[Register, Register, RegisterCount, Immediate],
Opcode::SetGeneratorState => &[Register],
Opcode::SwitchOnGeneratorState => &[Register],
Opcode::CreateClass => &[ConstantPoolIdx, Register, FeedbackSlot],
Opcode::TestPrivateBrand => &[Register, Register],
Opcode::DefinePrivateBrand => &[Register],
Opcode::DefineClassNamedOwnProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::DefineClassGetterProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::DefineClassSetterProperty => &[Register, ConstantPoolIdx, FeedbackSlot],
Opcode::DefineClassKeyedOwnProperty => &[Register, Register, Flag, FeedbackSlot],
Opcode::DefineClassKeyedGetterProperty => &[Register, Register, FeedbackSlot],
Opcode::DefineClassKeyedSetterProperty => &[Register, Register, FeedbackSlot],
Opcode::LdaModuleVariable => &[ConstantPoolIdx, Immediate],
Opcode::StaModuleVariable => &[ConstantPoolIdx, Immediate],
Opcode::LdaImportMeta => &[],
Opcode::LdaNewTarget => &[],
Opcode::GetModuleNamespace => &[ConstantPoolIdx],
Opcode::Wide | Opcode::ExtraWide | Opcode::Illegal => &[],
Opcode::LdarAddStar => &[Register, Register, Register, FeedbackSlot],
Opcode::LdarSubStar => &[Register, Register, Register, FeedbackSlot],
Opcode::LdarMulStar => &[Register, Register, Register, FeedbackSlot],
Opcode::TestLessThanJump => &[Register, FeedbackSlot, JumpOffset, Flag],
Opcode::TestGreaterThanJump => &[Register, FeedbackSlot, JumpOffset, Flag],
Opcode::TestEqualJump => &[Register, FeedbackSlot, JumpOffset, Flag],
Opcode::TestNotEqualJump => &[Register, FeedbackSlot, JumpOffset, Flag],
Opcode::TestEqualStrictJump => &[Register, FeedbackSlot, JumpOffset, Flag],
Opcode::TestLessThanOrEqualJump => &[Register, FeedbackSlot, JumpOffset, Flag],
Opcode::TestGreaterThanOrEqualJump => &[Register, FeedbackSlot, JumpOffset, Flag],
Opcode::AddSmiStar => &[Immediate, FeedbackSlot, Register],
Opcode::SubSmiStar => &[Immediate, FeedbackSlot, Register],
Opcode::MulSmiStar => &[Immediate, FeedbackSlot, Register],
Opcode::IncStar => &[FeedbackSlot, Register],
Opcode::LdaSmiStar => &[Immediate, Register],
Opcode::LdaGlobalStar => &[ConstantPoolIdx, FeedbackSlot, Register],
Opcode::Nop => &[],
}
}
}
fn required_width(operands: &[Operand]) -> OperandWidth {
operands
.iter()
.fold(OperandWidth::Narrow, |w, op| w.max(op.required_width()))
}
fn write_operand(out: &mut Vec<u8>, operand: Operand, width: OperandWidth) {
match width {
OperandWidth::Narrow => {
let byte = match operand {
Operand::Register(v)
| Operand::RegisterCount(v)
| Operand::ConstantPoolIdx(v)
| Operand::FeedbackSlot(v)
| Operand::RuntimeId(v) => v as u8,
Operand::Immediate(v) | Operand::JumpOffset(v) => v as i8 as u8,
Operand::Flag(v) => v,
};
out.push(byte);
}
OperandWidth::Wide => {
let halfword: u16 = match operand {
Operand::Register(v)
| Operand::RegisterCount(v)
| Operand::ConstantPoolIdx(v)
| Operand::FeedbackSlot(v)
| Operand::RuntimeId(v) => v as u16,
Operand::Immediate(v) | Operand::JumpOffset(v) => v as i16 as u16,
Operand::Flag(v) => v as u16,
};
out.extend_from_slice(&halfword.to_le_bytes());
}
OperandWidth::ExtraWide => {
let word: u32 = match operand {
Operand::Register(v)
| Operand::RegisterCount(v)
| Operand::ConstantPoolIdx(v)
| Operand::FeedbackSlot(v)
| Operand::RuntimeId(v) => v,
Operand::Immediate(v) | Operand::JumpOffset(v) => v as u32,
Operand::Flag(v) => v as u32,
};
out.extend_from_slice(&word.to_le_bytes());
}
}
}
fn read_operand(
bytes: &[u8],
pos: &mut usize,
op_type: OperandType,
width: OperandWidth,
) -> StatorResult<Operand> {
let w = width as usize;
let end = *pos + w;
if end > bytes.len() {
return Err(StatorError::Internal(format!(
"truncated bytecode: need {w} byte(s) at offset {pos}",
pos = *pos
)));
}
let raw_u32 = match width {
OperandWidth::Narrow => bytes[*pos] as u32,
OperandWidth::Wide => {
let arr: [u8; 2] = bytes[*pos..*pos + 2].try_into().unwrap();
u16::from_le_bytes(arr) as u32
}
OperandWidth::ExtraWide => {
let arr: [u8; 4] = bytes[*pos..*pos + 4].try_into().unwrap();
u32::from_le_bytes(arr)
}
};
*pos = end;
let operand = match op_type {
OperandType::Register => Operand::Register(raw_u32),
OperandType::RegisterCount => Operand::RegisterCount(raw_u32),
OperandType::ConstantPoolIdx => Operand::ConstantPoolIdx(raw_u32),
OperandType::FeedbackSlot => Operand::FeedbackSlot(raw_u32),
OperandType::RuntimeId => Operand::RuntimeId(raw_u32),
OperandType::Immediate => {
let signed = match width {
OperandWidth::Narrow => raw_u32 as i8 as i32,
OperandWidth::Wide => raw_u32 as i16 as i32,
OperandWidth::ExtraWide => raw_u32 as i32,
};
Operand::Immediate(signed)
}
OperandType::JumpOffset => {
let signed = match width {
OperandWidth::Narrow => raw_u32 as i8 as i32,
OperandWidth::Wide => raw_u32 as i16 as i32,
OperandWidth::ExtraWide => raw_u32 as i32,
};
Operand::JumpOffset(signed)
}
OperandType::Flag => Operand::Flag(raw_u32 as u8),
};
Ok(operand)
}
pub fn encode(instructions: &[Instruction]) -> Vec<u8> {
let mut out = Vec::new();
for instr in instructions {
let width = required_width(instr.operands());
match width {
OperandWidth::Wide => out.push(Opcode::Wide as u8),
OperandWidth::ExtraWide => out.push(Opcode::ExtraWide as u8),
OperandWidth::Narrow => {}
}
out.push(instr.opcode as u8);
for &operand in instr.operands() {
write_operand(&mut out, operand, width);
}
}
out
}
pub fn decode_with_byte_offsets(bytes: &[u8]) -> StatorResult<(Vec<Instruction>, Vec<usize>)> {
let mut instructions = Vec::new();
let mut byte_offsets = Vec::new();
let mut pos = 0usize;
while pos < bytes.len() {
let instr_start = pos;
let width = match Opcode::try_from_u8(bytes[pos])? {
Opcode::Wide => {
pos += 1;
OperandWidth::Wide
}
Opcode::ExtraWide => {
pos += 1;
OperandWidth::ExtraWide
}
_ => OperandWidth::Narrow,
};
if pos >= bytes.len() {
return Err(StatorError::Internal(
"bytecode ends with a width prefix and no following opcode".into(),
));
}
let opcode = Opcode::try_from_u8(bytes[pos])?;
pos += 1;
if matches!(opcode, Opcode::Wide | Opcode::ExtraWide) {
return Err(StatorError::Internal(
"unexpected width-prefix opcode in operand position".into(),
));
}
let op_types = opcode.operand_types();
let mut operands = [EMPTY_OPERAND; MAX_INSTRUCTION_OPERANDS];
for (operand_index, &op_type) in op_types.iter().enumerate() {
if operand_index == MAX_INSTRUCTION_OPERANDS {
return Err(StatorError::Internal(format!(
"{opcode:?} exceeds inline operand capacity of {MAX_INSTRUCTION_OPERANDS}"
)));
}
operands[operand_index] = read_operand(bytes, &mut pos, op_type, width)?;
}
byte_offsets.push(instr_start);
instructions.push(Instruction {
opcode,
operand_count: op_types.len() as u8,
operands,
});
}
byte_offsets.push(pos); Ok((instructions, byte_offsets))
}
pub fn decode(bytes: &[u8]) -> StatorResult<Vec<Instruction>> {
Ok(decode_with_byte_offsets(bytes)?.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(instructions: Vec<Instruction>) {
let bytes = encode(&instructions);
let decoded = decode(&bytes).expect("decode should succeed");
assert_eq!(decoded, instructions, "round-trip mismatch");
}
#[test]
fn test_round_trip_lda_zero() {
round_trip(vec![Instruction::new_unchecked(Opcode::LdaZero, vec![])]);
}
#[test]
fn test_round_trip_return() {
round_trip(vec![Instruction::new_unchecked(Opcode::Return, vec![])]);
}
#[test]
fn test_round_trip_lda_null_undefined_true_false() {
round_trip(vec![
Instruction::new_unchecked(Opcode::LdaNull, vec![]),
Instruction::new_unchecked(Opcode::LdaUndefined, vec![]),
Instruction::new_unchecked(Opcode::LdaTrue, vec![]),
Instruction::new_unchecked(Opcode::LdaFalse, vec![]),
]);
}
#[test]
fn test_round_trip_lda_smi_narrow() {
round_trip(vec![Instruction::new_unchecked(
Opcode::LdaSmi,
vec![Operand::Immediate(42)],
)]);
}
#[test]
fn test_round_trip_lda_smi_negative_narrow() {
round_trip(vec![Instruction::new_unchecked(
Opcode::LdaSmi,
vec![Operand::Immediate(-1)],
)]);
}
#[test]
fn test_round_trip_ldar_star() {
round_trip(vec![
Instruction::new_unchecked(Opcode::Ldar, vec![Operand::Register(5)]),
Instruction::new_unchecked(Opcode::Star, vec![Operand::Register(3)]),
]);
}
#[test]
fn test_round_trip_mov() {
round_trip(vec![Instruction::new_unchecked(
Opcode::Mov,
vec![Operand::Register(1), Operand::Register(2)],
)]);
}
#[test]
fn test_round_trip_add() {
round_trip(vec![Instruction::new_unchecked(
Opcode::Add,
vec![Operand::Register(0), Operand::FeedbackSlot(1)],
)]);
}
#[test]
fn test_round_trip_add_smi() {
round_trip(vec![Instruction::new_unchecked(
Opcode::AddSmi,
vec![Operand::Immediate(10), Operand::FeedbackSlot(0)],
)]);
}
#[test]
fn test_round_trip_inc_dec() {
round_trip(vec![
Instruction::new_unchecked(Opcode::Inc, vec![Operand::FeedbackSlot(2)]),
Instruction::new_unchecked(Opcode::Dec, vec![Operand::FeedbackSlot(3)]),
]);
}
#[test]
fn test_round_trip_bitwise_ops() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::BitwiseOr,
vec![Operand::Register(1), Operand::FeedbackSlot(0)],
),
Instruction::new_unchecked(
Opcode::BitwiseAnd,
vec![Operand::Register(2), Operand::FeedbackSlot(1)],
),
Instruction::new_unchecked(
Opcode::BitwiseXor,
vec![Operand::Register(3), Operand::FeedbackSlot(2)],
),
Instruction::new_unchecked(
Opcode::ShiftLeft,
vec![Operand::Register(4), Operand::FeedbackSlot(3)],
),
Instruction::new_unchecked(
Opcode::ShiftRight,
vec![Operand::Register(5), Operand::FeedbackSlot(4)],
),
Instruction::new_unchecked(
Opcode::ShiftRightLogical,
vec![Operand::Register(6), Operand::FeedbackSlot(5)],
),
]);
}
#[test]
fn test_round_trip_comparisons() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::TestEqual,
vec![Operand::Register(0), Operand::FeedbackSlot(1)],
),
Instruction::new_unchecked(
Opcode::TestEqualStrict,
vec![Operand::Register(1), Operand::FeedbackSlot(2)],
),
Instruction::new_unchecked(
Opcode::TestLessThan,
vec![Operand::Register(2), Operand::FeedbackSlot(3)],
),
Instruction::new_unchecked(
Opcode::TestGreaterThan,
vec![Operand::Register(3), Operand::FeedbackSlot(4)],
),
Instruction::new_unchecked(Opcode::TestNull, vec![]),
Instruction::new_unchecked(Opcode::TestUndefined, vec![]),
]);
}
#[test]
fn test_round_trip_named_property() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::LdaNamedProperty,
vec![
Operand::Register(0),
Operand::ConstantPoolIdx(5),
Operand::FeedbackSlot(2),
],
),
Instruction::new_unchecked(
Opcode::StaNamedProperty,
vec![
Operand::Register(1),
Operand::ConstantPoolIdx(6),
Operand::FeedbackSlot(3),
],
),
]);
}
#[test]
fn test_round_trip_keyed_property() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::LdaKeyedProperty,
vec![Operand::Register(0), Operand::FeedbackSlot(1)],
),
Instruction::new_unchecked(
Opcode::StaKeyedProperty,
vec![
Operand::Register(1),
Operand::Register(2),
Operand::FeedbackSlot(3),
],
),
]);
}
#[test]
fn test_round_trip_call_property() {
round_trip(vec![Instruction::new_unchecked(
Opcode::CallProperty,
vec![
Operand::Register(0),
Operand::Register(1),
Operand::RegisterCount(2),
Operand::FeedbackSlot(4),
],
)]);
}
#[test]
fn test_round_trip_call_runtime() {
round_trip(vec![Instruction::new_unchecked(
Opcode::CallRuntime,
vec![
Operand::RuntimeId(42),
Operand::Register(0),
Operand::RegisterCount(3),
],
)]);
}
#[test]
fn test_round_trip_construct() {
round_trip(vec![Instruction::new_unchecked(
Opcode::Construct,
vec![
Operand::Register(0),
Operand::Register(1),
Operand::RegisterCount(2),
Operand::FeedbackSlot(5),
],
)]);
}
#[test]
fn test_round_trip_jump() {
round_trip(vec![Instruction::new_unchecked(
Opcode::Jump,
vec![Operand::JumpOffset(8)],
)]);
}
#[test]
fn test_round_trip_jump_if_true_false() {
round_trip(vec![
Instruction::new_unchecked(Opcode::JumpIfTrue, vec![Operand::JumpOffset(4)]),
Instruction::new_unchecked(Opcode::JumpIfFalse, vec![Operand::JumpOffset(-4)]),
]);
}
#[test]
fn test_round_trip_jump_loop() {
round_trip(vec![Instruction::new_unchecked(
Opcode::JumpLoop,
vec![
Operand::JumpOffset(-20),
Operand::Immediate(1),
Operand::FeedbackSlot(0),
],
)]);
}
#[test]
fn test_round_trip_superinstructions() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::LdarAddStar,
vec![
Operand::Register(1),
Operand::Register(2),
Operand::Register(3),
Operand::FeedbackSlot(4),
],
),
Instruction::new_unchecked(
Opcode::LdarSubStar,
vec![
Operand::Register(5),
Operand::Register(6),
Operand::Register(7),
Operand::FeedbackSlot(8),
],
),
Instruction::new_unchecked(
Opcode::LdarMulStar,
vec![
Operand::Register(9),
Operand::Register(10),
Operand::Register(11),
Operand::FeedbackSlot(12),
],
),
Instruction::new_unchecked(
Opcode::TestLessThanJump,
vec![
Operand::Register(13),
Operand::FeedbackSlot(14),
Operand::JumpOffset(-15),
Operand::Flag(1),
],
),
Instruction::new_unchecked(
Opcode::TestNotEqualJump,
vec![
Operand::Register(16),
Operand::FeedbackSlot(17),
Operand::JumpOffset(18),
Operand::Flag(0),
],
),
Instruction::new_unchecked(
Opcode::TestLessThanOrEqualJump,
vec![
Operand::Register(19),
Operand::FeedbackSlot(20),
Operand::JumpOffset(-21),
Operand::Flag(1),
],
),
Instruction::new_unchecked(
Opcode::TestGreaterThanOrEqualJump,
vec![
Operand::Register(22),
Operand::FeedbackSlot(23),
Operand::JumpOffset(24),
Operand::Flag(0),
],
),
Instruction::new_unchecked(
Opcode::AddSmiStar,
vec![
Operand::Immediate(25),
Operand::FeedbackSlot(26),
Operand::Register(27),
],
),
Instruction::new_unchecked(
Opcode::SubSmiStar,
vec![
Operand::Immediate(-28),
Operand::FeedbackSlot(29),
Operand::Register(30),
],
),
Instruction::new_unchecked(
Opcode::MulSmiStar,
vec![
Operand::Immediate(31),
Operand::FeedbackSlot(32),
Operand::Register(33),
],
),
Instruction::new_unchecked(
Opcode::IncStar,
vec![Operand::FeedbackSlot(34), Operand::Register(35)],
),
Instruction::new_unchecked(
Opcode::LdaSmiStar,
vec![Operand::Immediate(-36), Operand::Register(37)],
),
Instruction::new_unchecked(
Opcode::LdaGlobalStar,
vec![
Operand::ConstantPoolIdx(38),
Operand::FeedbackSlot(39),
Operand::Register(40),
],
),
]);
}
#[test]
fn test_round_trip_nop() {
round_trip(vec![Instruction::new_unchecked(Opcode::Nop, vec![])]);
}
#[test]
fn test_round_trip_context_slot() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::LdaContextSlot,
vec![
Operand::Register(0),
Operand::ConstantPoolIdx(3),
Operand::Immediate(1),
],
),
Instruction::new_unchecked(
Opcode::StaContextSlot,
vec![
Operand::Register(0),
Operand::ConstantPoolIdx(4),
Operand::Immediate(0),
],
),
]);
}
#[test]
fn test_round_trip_push_pop_context() {
round_trip(vec![
Instruction::new_unchecked(Opcode::PushContext, vec![Operand::Register(2)]),
Instruction::new_unchecked(Opcode::PopContext, vec![Operand::Register(2)]),
]);
}
#[test]
fn test_round_trip_throw_rethrow() {
round_trip(vec![
Instruction::new_unchecked(Opcode::Throw, vec![]),
Instruction::new_unchecked(Opcode::ReThrow, vec![]),
]);
}
#[test]
fn test_round_trip_throw_reference_error_if_hole() {
round_trip(vec![Instruction::new_unchecked(
Opcode::ThrowReferenceErrorIfHole,
vec![Operand::ConstantPoolIdx(7)],
)]);
}
#[test]
fn test_round_trip_suspend_resume_generator() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::SuspendGenerator,
vec![
Operand::Register(0),
Operand::Register(1),
Operand::RegisterCount(4),
Operand::Immediate(0),
],
),
Instruction::new_unchecked(
Opcode::ResumeGenerator,
vec![
Operand::Register(0),
Operand::Register(1),
Operand::RegisterCount(4),
],
),
]);
}
#[test]
fn test_round_trip_for_in() {
round_trip(vec![
Instruction::new_unchecked(Opcode::ForInEnumerate, vec![Operand::Register(1)]),
Instruction::new_unchecked(
Opcode::ForInPrepare,
vec![Operand::Register(2), Operand::FeedbackSlot(0)],
),
Instruction::new_unchecked(
Opcode::ForInNext,
vec![
Operand::Register(1),
Operand::Register(2),
Operand::Register(3),
Operand::FeedbackSlot(0),
],
),
Instruction::new_unchecked(Opcode::ForInStep, vec![Operand::Register(4)]),
]);
}
#[test]
fn test_round_trip_create_closure() {
round_trip(vec![Instruction::new_unchecked(
Opcode::CreateClosure,
vec![
Operand::ConstantPoolIdx(0),
Operand::FeedbackSlot(1),
Operand::Flag(0),
],
)]);
}
#[test]
fn test_round_trip_create_array_object_literals() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::CreateArrayLiteral,
vec![
Operand::ConstantPoolIdx(2),
Operand::FeedbackSlot(0),
Operand::Flag(1),
],
),
Instruction::new_unchecked(
Opcode::CreateObjectLiteral,
vec![
Operand::ConstantPoolIdx(3),
Operand::FeedbackSlot(1),
Operand::Flag(0),
],
),
Instruction::new_unchecked(Opcode::CreateEmptyObjectLiteral, vec![]),
Instruction::new_unchecked(
Opcode::CreateEmptyArrayLiteral,
vec![Operand::FeedbackSlot(2)],
),
]);
}
#[test]
fn test_round_trip_lda_sta_module_variable() {
round_trip(vec![
Instruction::new_unchecked(
Opcode::LdaModuleVariable,
vec![Operand::ConstantPoolIdx(0), Operand::Immediate(1)],
),
Instruction::new_unchecked(
Opcode::StaModuleVariable,
vec![Operand::ConstantPoolIdx(0), Operand::Immediate(2)],
),
]);
}
#[test]
fn test_round_trip_lda_import_meta() {
round_trip(vec![Instruction::new_unchecked(
Opcode::LdaImportMeta,
vec![],
)]);
}
#[test]
fn test_round_trip_get_module_namespace() {
round_trip(vec![Instruction::new_unchecked(
Opcode::GetModuleNamespace,
vec![Operand::ConstantPoolIdx(5)],
)]);
}
#[test]
fn test_round_trip_wide_register() {
round_trip(vec![Instruction::new_unchecked(
Opcode::Ldar,
vec![Operand::Register(256)],
)]);
}
#[test]
fn test_round_trip_wide_immediate() {
round_trip(vec![Instruction::new_unchecked(
Opcode::LdaSmi,
vec![Operand::Immediate(200)],
)]);
}
#[test]
fn test_round_trip_wide_jump_offset() {
round_trip(vec![Instruction::new_unchecked(
Opcode::Jump,
vec![Operand::JumpOffset(1000)],
)]);
}
#[test]
fn test_round_trip_wide_constant_pool_idx() {
round_trip(vec![Instruction::new_unchecked(
Opcode::LdaConstant,
vec![Operand::ConstantPoolIdx(300)],
)]);
}
#[test]
fn test_round_trip_extra_wide_register() {
round_trip(vec![Instruction::new_unchecked(
Opcode::Star,
vec![Operand::Register(70_000)],
)]);
}
#[test]
fn test_round_trip_extra_wide_immediate() {
round_trip(vec![Instruction::new_unchecked(
Opcode::LdaSmi,
vec![Operand::Immediate(100_000)],
)]);
}
#[test]
fn test_round_trip_mixed_widths() {
round_trip(vec![
Instruction::new_unchecked(Opcode::LdaZero, vec![]),
Instruction::new_unchecked(Opcode::LdaSmi, vec![Operand::Immediate(1)]),
Instruction::new_unchecked(Opcode::LdaSmi, vec![Operand::Immediate(200)]),
Instruction::new_unchecked(Opcode::LdaSmi, vec![Operand::Immediate(100_000)]),
Instruction::new_unchecked(Opcode::Star, vec![Operand::Register(0)]),
Instruction::new_unchecked(Opcode::Star, vec![Operand::Register(300)]),
Instruction::new_unchecked(Opcode::Return, vec![]),
]);
}
#[test]
fn test_opcode_try_from_u8_roundtrip() {
for byte in 0..=MAX_OPCODE {
let op = Opcode::try_from_u8(byte).expect("should decode");
assert_eq!(op as u8, byte);
}
}
#[test]
fn test_opcode_try_from_u8_out_of_range() {
assert!(Opcode::try_from_u8(MAX_OPCODE + 1).is_err());
assert!(Opcode::try_from_u8(255).is_err());
}
#[test]
fn test_instruction_new_correct_operand_count() {
let instr = Instruction::new(Opcode::LdaSmi, vec![Operand::Immediate(7)]);
assert!(instr.is_ok());
}
#[test]
fn test_instruction_new_wrong_operand_count() {
let instr = Instruction::new(Opcode::LdaSmi, vec![]);
assert!(instr.is_err());
}
#[test]
fn test_decode_empty() {
assert_eq!(decode(&[]).unwrap(), vec![]);
}
#[test]
fn test_decode_truncated_operand() {
let bytes = vec![Opcode::LdaSmi as u8];
assert!(decode(&bytes).is_err());
}
#[test]
fn test_decode_dangling_wide_prefix() {
let bytes = vec![Opcode::Wide as u8];
assert!(decode(&bytes).is_err());
}
}