use crate::class_file::ClassFile;
use crate::error::{JvmError, RuntimeError};
use crate::memory::{StackFrame, Value};
use super::Interpreter;
pub(crate) type OpcodeHandler =
fn(&mut Interpreter, &ClassFile, &[u8], &mut StackFrame, u8) -> Result<bool, JvmError>;
impl Interpreter {
#[inline(always)]
fn handle_nop(
&mut self,
_class: &ClassFile,
_code: &[u8],
_frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
Ok(true)
}
#[inline(always)]
fn handle_aconst_null(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.push(Value::Null)?;
Ok(true)
}
#[inline(always)]
fn handle_iconst_m1(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.push(Value::Int(-1))?;
Ok(true)
}
#[inline(always)]
fn handle_iconst_0(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.push(Value::Int(0))?;
Ok(true)
}
#[inline(always)]
fn handle_iconst_1(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.push(Value::Int(1))?;
Ok(true)
}
#[inline(always)]
fn handle_iconst_2(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.push(Value::Int(2))?;
Ok(true)
}
#[inline(always)]
fn handle_iconst_3(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.push(Value::Int(3))?;
Ok(true)
}
#[inline(always)]
fn handle_iconst_4(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.push(Value::Int(4))?;
Ok(true)
}
#[inline(always)]
fn handle_iconst_5(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.push(Value::Int(5))?;
Ok(true)
}
#[inline(always)]
fn handle_iload_n(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
opcode: u8,
) -> Result<bool, JvmError> {
let idx = (opcode - 0x1a) as usize;
let v = frame.load_local(idx)?;
frame.push(v)?;
Ok(true)
}
#[inline(always)]
fn handle_aload_n(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
opcode: u8,
) -> Result<bool, JvmError> {
let idx = (opcode - 0x2a) as usize;
let v = frame.load_local(idx)?;
frame.push(v)?;
Ok(true)
}
#[inline(always)]
fn handle_istore_n(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
opcode: u8,
) -> Result<bool, JvmError> {
let idx = (opcode - 0x3b) as usize;
let v = frame.pop()?;
frame.store_local(idx, v)?;
Ok(true)
}
#[inline(always)]
fn handle_astore_n(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
opcode: u8,
) -> Result<bool, JvmError> {
let idx = (opcode - 0x4b) as usize;
let v = frame.pop()?;
frame.store_local(idx, v)?;
Ok(true)
}
#[inline(always)]
fn handle_pop(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
frame.pop()?;
Ok(true)
}
#[inline(always)]
fn handle_dup(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
let v = frame.peek()?.clone();
frame.push(v)?;
Ok(true)
}
#[inline(always)]
fn handle_swap(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
let v1 = frame.pop()?;
let v2 = frame.pop()?;
frame.push(v1)?;
frame.push(v2)?;
Ok(true)
}
#[inline(always)]
fn handle_iadd(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
let b = frame.pop()?.as_int();
let a = frame.pop()?.as_int();
frame.push(Value::Int(a.wrapping_add(b)))?;
Ok(true)
}
#[inline(always)]
fn handle_isub(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
let b = frame.pop()?.as_int();
let a = frame.pop()?.as_int();
frame.push(Value::Int(a.wrapping_sub(b)))?;
Ok(true)
}
#[inline(always)]
fn handle_imul(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
let b = frame.pop()?.as_int();
let a = frame.pop()?.as_int();
frame.push(Value::Int(a.wrapping_mul(b)))?;
Ok(true)
}
#[inline(always)]
fn handle_idiv(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
let b = frame.pop()?.as_int();
let a = frame.pop()?.as_int();
if b == 0 {
return Err(crate::error::to_runtime_error_enum(
RuntimeError::DivisionByZero,
));
}
frame.push(Value::Int(a.wrapping_div(b)))?;
Ok(true)
}
#[inline(always)]
fn handle_return(
&mut self,
_class: &ClassFile,
_code: &[u8],
_frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
Ok(false) }
#[inline(always)]
fn handle_ireturn(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
if !frame.stack.is_empty() {
let _ = frame.pop()?;
}
Ok(false) }
#[inline(always)]
fn handle_areturn(
&mut self,
_class: &ClassFile,
_code: &[u8],
frame: &mut StackFrame,
_opcode: u8,
) -> Result<bool, JvmError> {
if !frame.stack.is_empty() {
let _ = frame.pop()?;
}
Ok(false) }
#[inline(never)]
fn handle_fallback(
&mut self,
class: &ClassFile,
code: &[u8],
frame: &mut StackFrame,
opcode: u8,
) -> Result<bool, JvmError> {
self.dispatch_instruction(class, code, frame, opcode)
}
}
pub(crate) const DISPATCH_TABLE: [OpcodeHandler; 256] = {
let mut table: [OpcodeHandler; 256] = [Interpreter::handle_fallback; 256];
table[0x00] = Interpreter::handle_nop;
table[0x01] = Interpreter::handle_aconst_null;
table[0x02] = Interpreter::handle_iconst_m1;
table[0x03] = Interpreter::handle_iconst_0;
table[0x04] = Interpreter::handle_iconst_1;
table[0x05] = Interpreter::handle_iconst_2;
table[0x06] = Interpreter::handle_iconst_3;
table[0x07] = Interpreter::handle_iconst_4;
table[0x08] = Interpreter::handle_iconst_5;
table[0x1a] = Interpreter::handle_iload_n;
table[0x1b] = Interpreter::handle_iload_n;
table[0x1c] = Interpreter::handle_iload_n;
table[0x1d] = Interpreter::handle_iload_n;
table[0x2a] = Interpreter::handle_aload_n;
table[0x2b] = Interpreter::handle_aload_n;
table[0x2c] = Interpreter::handle_aload_n;
table[0x2d] = Interpreter::handle_aload_n;
table[0x3b] = Interpreter::handle_istore_n;
table[0x3c] = Interpreter::handle_istore_n;
table[0x3d] = Interpreter::handle_istore_n;
table[0x3e] = Interpreter::handle_istore_n;
table[0x4b] = Interpreter::handle_astore_n;
table[0x4c] = Interpreter::handle_astore_n;
table[0x4d] = Interpreter::handle_astore_n;
table[0x4e] = Interpreter::handle_astore_n;
table[0x57] = Interpreter::handle_pop;
table[0x59] = Interpreter::handle_dup;
table[0x5f] = Interpreter::handle_swap;
table[0x60] = Interpreter::handle_iadd;
table[0x64] = Interpreter::handle_isub;
table[0x68] = Interpreter::handle_imul;
table[0x6c] = Interpreter::handle_idiv;
table
};
#[cfg(test)]
mod tests {
use crate::class_file::ClassFile;
use crate::interpreter::Interpreter;
use crate::memory::{StackFrame, Value};
fn run_opcode(
opcode: u8,
locals: Vec<Value>,
stack: Vec<Value>,
) -> Result<Vec<Value>, super::JvmError> {
let mut interp = Interpreter::new();
let class = ClassFile {
magic: 0xCAFEBABE,
minor_version: 0,
major_version: 52,
constant_pool: vec![],
access_flags: 0,
this_class: 0,
super_class: 0,
interfaces: vec![],
fields: vec![],
methods: vec![],
attributes: vec![],
};
let mut frame = StackFrame::new(10, 10, "test".to_string());
frame.locals = locals;
for v in stack {
frame.push(v).unwrap();
}
let code: &[u8] = &[opcode];
let _ = super::DISPATCH_TABLE[opcode as usize](&mut interp, &class, code, &mut frame, opcode)?;
Ok(frame.stack)
}
#[test]
fn test_table_nop() {
let stack = run_opcode(0x00, vec![], vec![]).unwrap();
assert!(stack.is_empty());
}
#[test]
fn test_table_aconst_null() {
let stack = run_opcode(0x01, vec![], vec![]).unwrap();
assert_eq!(stack.len(), 1);
assert_eq!(stack[0], Value::Null);
}
#[test]
fn test_table_iconst_0() {
let stack = run_opcode(0x03, vec![], vec![]).unwrap();
assert_eq!(stack, vec![Value::Int(0)]);
}
#[test]
fn test_table_iconst_5() {
let stack = run_opcode(0x08, vec![], vec![]).unwrap();
assert_eq!(stack, vec![Value::Int(5)]);
}
#[test]
fn test_table_iload_0() {
let stack = run_opcode(0x1a, vec![Value::Int(42)], vec![]).unwrap();
assert_eq!(stack, vec![Value::Int(42)]);
}
#[test]
fn test_table_aload_0() {
let stack = run_opcode(0x2a, vec![Value::Reference(7)], vec![]).unwrap();
assert_eq!(stack, vec![Value::Reference(7)]);
}
#[test]
fn test_table_istore_0() {
let stack = run_opcode(0x3b, vec![Value::Int(0); 2], vec![Value::Int(99)]).unwrap();
assert!(stack.is_empty());
}
#[test]
fn test_table_pop() {
let stack = run_opcode(0x57, vec![], vec![Value::Int(1), Value::Int(2)]).unwrap();
assert_eq!(stack, vec![Value::Int(1)]);
}
#[test]
fn test_table_dup() {
let stack = run_opcode(0x59, vec![], vec![Value::Int(42)]).unwrap();
assert_eq!(stack, vec![Value::Int(42), Value::Int(42)]);
}
#[test]
fn test_table_swap() {
let stack = run_opcode(0x5f, vec![], vec![Value::Int(1), Value::Int(2)]).unwrap();
assert_eq!(stack, vec![Value::Int(2), Value::Int(1)]);
}
#[test]
fn test_table_iadd() {
let stack = run_opcode(0x60, vec![], vec![Value::Int(3), Value::Int(4)]).unwrap();
assert_eq!(stack, vec![Value::Int(7)]);
}
#[test]
fn test_table_isub() {
let stack = run_opcode(0x64, vec![], vec![Value::Int(10), Value::Int(3)]).unwrap();
assert_eq!(stack, vec![Value::Int(7)]);
}
#[test]
fn test_table_imul() {
let stack = run_opcode(0x68, vec![], vec![Value::Int(6), Value::Int(7)]).unwrap();
assert_eq!(stack, vec![Value::Int(42)]);
}
#[test]
fn test_table_idiv() {
let stack = run_opcode(0x6c, vec![], vec![Value::Int(20), Value::Int(4)]).unwrap();
assert_eq!(stack, vec![Value::Int(5)]);
}
#[test]
fn test_table_idiv_by_zero() {
let result = run_opcode(0x6c, vec![], vec![Value::Int(5), Value::Int(0)]);
assert!(result.is_err());
}
#[test]
fn test_table_return_breaks() {
let mut interp = Interpreter::new();
let class = ClassFile {
magic: 0xCAFEBABE,
minor_version: 0,
major_version: 52,
constant_pool: vec![],
access_flags: 0,
this_class: 0,
super_class: 0,
interfaces: vec![],
fields: vec![],
methods: vec![],
attributes: vec![],
};
let mut frame = StackFrame::new(2, 2, "test".to_string());
let code: &[u8] = &[0xb1];
let cont = super::DISPATCH_TABLE[0xb1 as usize](&mut interp, &class, code, &mut frame, 0xb1).unwrap();
assert!(!cont, "return should signal break");
}
}