rusty-jvm 0.5.0

An implementation of a Java Virtual Machine (JVM).
Documentation
use crate::vm::error::{Error, Result};
use crate::vm::execution_engine::common::last_frame_mut;
use crate::vm::execution_engine::opcode::*;
use crate::vm::method_area::method_area::{with_method_area, MethodArea};
use crate::vm::stack::stack_frame::{StackFrame, StackFrames};
use crate::vm::stack::stack_value::StackValue;
use std::fmt::Display;
use tracing::trace;

pub(crate) fn process(
    code: u8,
    current_class_name: &str,
    stack_frames: &mut StackFrames,
) -> Result<()> {
    let stack_frame = last_frame_mut(stack_frames)?;
    match code {
        NOP => {
            stack_frame.incr_pc();
            trace!("NOP");
            Ok(())
        }
        ACONST_NULL => push_constant(stack_frame, 0i32, "ACONST_NULL"),
        ICONST_M1 => push_constant(stack_frame, -1i32, "ICONST_M1"),
        ICONST_0 => push_constant(stack_frame, 0i32, "ICONST_0"),
        ICONST_1 => push_constant(stack_frame, 1i32, "ICONST_1"),
        ICONST_2 => push_constant(stack_frame, 2i32, "ICONST_2"),
        ICONST_3 => push_constant(stack_frame, 3i32, "ICONST_3"),
        ICONST_4 => push_constant(stack_frame, 4i32, "ICONST_4"),
        ICONST_5 => push_constant(stack_frame, 5i32, "ICONST_5"),
        LCONST_0 => push_constant(stack_frame, 0i64, "LCONST_0"),
        LCONST_1 => push_constant(stack_frame, 1i64, "LCONST_1"),
        FCONST_0 => push_constant(stack_frame, 0.0f32, "FCONST_0"),
        FCONST_1 => push_constant(stack_frame, 1.0f32, "FCONST_1"),
        FCONST_2 => push_constant(stack_frame, 2.0f32, "FCONST_2"),
        DCONST_0 => push_constant(stack_frame, 0.0f64, "DCONST_0"),
        DCONST_1 => push_constant(stack_frame, 1.0f64, "DCONST_1"),
        BIPUSH => handle_push(
            stack_frame,
            |stack_frame| stack_frame.extract_one_byte() as i8 as i32,
            "BIPUSH",
        ),
        SIPUSH => handle_push(
            stack_frame,
            |stack_frame| stack_frame.extract_two_bytes() as i32,
            "SIPUSH",
        ),
        LDC | LDC_W | LDC2_W => handle_ldc_cases(stack_frame, code, &current_class_name),
        _ => Err(Error::new_execution(&format!(
            "Unknown constant opcode: {}",
            code
        ))),
    }
}

fn push_constant<T: StackValue>(stack_frame: &mut StackFrame, value: T, name: &str) -> Result<()> {
    stack_frame.push(value)?;
    stack_frame.incr_pc();
    trace!("{name}");
    Ok(())
}

fn handle_push<T: StackValue + Display + Copy>(
    stack_frame: &mut StackFrame,
    extractor: impl FnOnce(&mut StackFrame) -> T,
    name: &str,
) -> Result<()> {
    let value = extractor(stack_frame);
    stack_frame.push(value)?;
    stack_frame.incr_pc();
    trace!("{name} -> value={value}");
    Ok(())
}

fn handle_ldc_cases(
    stack_frame: &mut StackFrame,
    code: u8,
    current_class_name: &str,
) -> Result<()> {
    match code {
        LDC | LDC_W => {
            let extract_func = if code == LDC {
                |sf: &mut StackFrame| sf.extract_one_byte() as u16

            } else {
                |sf: &mut StackFrame| sf.extract_two_bytes() as u16

            };

            handle_ldc_generic(
                stack_frame,
                extract_func,
                |method_area, class_name, index| method_area.resolve_ldc(class_name, index),
                current_class_name,
                if code == LDC { "LDC" } else { "LDC_W" },
            )
        }
        LDC2_W => handle_ldc_generic(
            stack_frame,
            |sf| sf.extract_two_bytes() as u16,
            |method_area, class_name, index| method_area.resolve_ldc2_w(class_name, index),
            current_class_name,
            "LDC2_W",
        ),
        _ => unreachable!(),
    }
}

fn handle_ldc_generic<T: StackValue + Display + Copy>(
    stack_frame: &mut StackFrame,
    extractor: impl FnOnce(&mut StackFrame) -> u16,
    resolver: impl FnOnce(&MethodArea, &str, u16) -> Result<T>,
    current_class_name: &str,
    name: &str,
) -> Result<()> {
    let cpoolindex = extractor(stack_frame);

    let value =
        with_method_area(|method_area| resolver(method_area, current_class_name, cpoolindex))?;

    stack_frame.push(value)?;

    stack_frame.incr_pc();
    trace!("{name} -> cpoolindex={cpoolindex}, value={value}");

    Ok(())
}