537 lines
16 KiB
Rust
537 lines
16 KiB
Rust
use crate::opcode::OpCode;
|
|
|
|
/// Specification for a single OpCode.
|
|
/// All JMP/JMP_IF_* immediate are u32 absolute offsets from function start.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct OpcodeSpec {
|
|
pub name: &'static str,
|
|
pub imm_bytes: u8, // immediate payload size (decode)
|
|
pub pops: u16, // slots popped
|
|
pub pushes: u16, // slots pushed
|
|
pub is_branch: bool, // has a control-flow target
|
|
pub is_terminator: bool, // ends basic block: JMP/RET/TRAP/HALT
|
|
pub may_trap: bool, // runtime trap possible
|
|
/// Marks this opcode as a VM safepoint. Used by GC/scheduler policies.
|
|
pub is_safepoint: bool,
|
|
}
|
|
|
|
pub trait OpCodeSpecExt {
|
|
fn spec(&self) -> OpcodeSpec;
|
|
}
|
|
|
|
impl OpCodeSpecExt for OpCode {
|
|
fn spec(&self) -> OpcodeSpec {
|
|
match self {
|
|
OpCode::Nop => OpcodeSpec {
|
|
name: "NOP",
|
|
imm_bytes: 0,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Halt => OpcodeSpec {
|
|
name: "HALT",
|
|
imm_bytes: 0,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: true,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Jmp => OpcodeSpec {
|
|
name: "JMP",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: true,
|
|
is_terminator: true,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::JmpIfFalse => OpcodeSpec {
|
|
name: "JMP_IF_FALSE",
|
|
imm_bytes: 4,
|
|
pops: 1,
|
|
pushes: 0,
|
|
is_branch: true,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::JmpIfTrue => OpcodeSpec {
|
|
name: "JMP_IF_TRUE",
|
|
imm_bytes: 4,
|
|
pops: 1,
|
|
pushes: 0,
|
|
is_branch: true,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Trap => OpcodeSpec {
|
|
name: "TRAP",
|
|
imm_bytes: 0,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: true,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PushConst => OpcodeSpec {
|
|
name: "PUSH_CONST",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Pop => OpcodeSpec {
|
|
name: "POP",
|
|
imm_bytes: 0,
|
|
pops: 1,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PopN => OpcodeSpec {
|
|
name: "POP_N",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Dup => OpcodeSpec {
|
|
name: "DUP",
|
|
imm_bytes: 0,
|
|
pops: 1,
|
|
pushes: 2,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Swap => OpcodeSpec {
|
|
name: "SWAP",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 2,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PushI64 => OpcodeSpec {
|
|
name: "PUSH_I64",
|
|
imm_bytes: 8,
|
|
pops: 0,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PushF64 => OpcodeSpec {
|
|
name: "PUSH_F64",
|
|
imm_bytes: 8,
|
|
pops: 0,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PushBool => OpcodeSpec {
|
|
name: "PUSH_BOOL",
|
|
imm_bytes: 1,
|
|
pops: 0,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PushI32 => OpcodeSpec {
|
|
name: "PUSH_I32",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PushBounded => OpcodeSpec {
|
|
name: "PUSH_BOUNDED",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Add => OpcodeSpec {
|
|
name: "ADD",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Sub => OpcodeSpec {
|
|
name: "SUB",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Mul => OpcodeSpec {
|
|
name: "MUL",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Div => OpcodeSpec {
|
|
name: "DIV",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Mod => OpcodeSpec {
|
|
name: "MOD",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::BoundToInt => OpcodeSpec {
|
|
name: "BOUND_TO_INT",
|
|
imm_bytes: 0,
|
|
pops: 1,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::IntToBoundChecked => OpcodeSpec {
|
|
name: "INT_TO_BOUND_CHECKED",
|
|
imm_bytes: 0,
|
|
pops: 1,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Eq => OpcodeSpec {
|
|
name: "EQ",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Neq => OpcodeSpec {
|
|
name: "NEQ",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Lt => OpcodeSpec {
|
|
name: "LT",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Gt => OpcodeSpec {
|
|
name: "GT",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::And => OpcodeSpec {
|
|
name: "AND",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Or => OpcodeSpec {
|
|
name: "OR",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Not => OpcodeSpec {
|
|
name: "NOT",
|
|
imm_bytes: 0,
|
|
pops: 1,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::BitAnd => OpcodeSpec {
|
|
name: "BIT_AND",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::BitOr => OpcodeSpec {
|
|
name: "BIT_OR",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::BitXor => OpcodeSpec {
|
|
name: "BIT_XOR",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Shl => OpcodeSpec {
|
|
name: "SHL",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Shr => OpcodeSpec {
|
|
name: "SHR",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Lte => OpcodeSpec {
|
|
name: "LTE",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Gte => OpcodeSpec {
|
|
name: "GTE",
|
|
imm_bytes: 0,
|
|
pops: 2,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Neg => OpcodeSpec {
|
|
name: "NEG",
|
|
imm_bytes: 0,
|
|
pops: 1,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::GetGlobal => OpcodeSpec {
|
|
name: "GET_GLOBAL",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::SetGlobal => OpcodeSpec {
|
|
name: "SET_GLOBAL",
|
|
imm_bytes: 4,
|
|
pops: 1,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::GetLocal => OpcodeSpec {
|
|
name: "GET_LOCAL",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 1,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::SetLocal => OpcodeSpec {
|
|
name: "SET_LOCAL",
|
|
imm_bytes: 4,
|
|
pops: 1,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Call => OpcodeSpec {
|
|
name: "CALL",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Ret => OpcodeSpec {
|
|
name: "RET",
|
|
imm_bytes: 0,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: true,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PushScope => OpcodeSpec {
|
|
name: "PUSH_SCOPE",
|
|
imm_bytes: 0,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::PopScope => OpcodeSpec {
|
|
name: "POP_SCOPE",
|
|
imm_bytes: 0,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::Syscall => OpcodeSpec {
|
|
name: "SYSCALL",
|
|
imm_bytes: 4,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: true,
|
|
is_safepoint: false,
|
|
},
|
|
OpCode::FrameSync => OpcodeSpec {
|
|
name: "FRAME_SYNC",
|
|
imm_bytes: 0,
|
|
pops: 0,
|
|
pushes: 0,
|
|
is_branch: false,
|
|
is_terminator: false,
|
|
may_trap: false,
|
|
is_safepoint: true,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
// Infer the numeric range from the TryFrom<u16> mapping used in opcode.rs tests
|
|
// by scanning a plausible range (0..1024) and keeping all successful decodes.
|
|
#[test]
|
|
fn every_opcode_has_spec_and_imm_defined() {
|
|
let mut count = 0usize;
|
|
for val in 0u16..=1023u16 {
|
|
if let Ok(op) = OpCode::try_from(val) {
|
|
let spec = op.spec();
|
|
// Access all fields to ensure they are present and not optimized away
|
|
let _ = (
|
|
spec.name,
|
|
spec.imm_bytes,
|
|
spec.pops,
|
|
spec.pushes,
|
|
spec.is_branch,
|
|
spec.is_terminator,
|
|
spec.may_trap,
|
|
);
|
|
count += 1;
|
|
}
|
|
}
|
|
assert!(count > 0, "No opcodes were found via OpCode::try_from");
|
|
}
|
|
}
|