Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trivial reflection #38

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,15 @@ fn should_do_native_call_on_system() {
assert!((expected_millis..expected_millis + 2000).contains(&actual_millis))
}

#[test]
fn should_do_trivial_reflection() {
let mut vm = VM::new("std");
let last_frame_value = vm
.run("samples.reflection.trivial.TrivialReflection")
.unwrap();
assert_eq!(2578, get_int(last_frame_value))
}

fn get_int(locals: Option<Vec<i32>>) -> i32 {
*locals.unwrap().last().unwrap()
}
Expand Down
22 changes: 22 additions & 0 deletions tests/test_data/TrivialReflection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package samples.reflection.trivial;

public class TrivialReflection {
public static void main(String[] args) {
Class<Class> clazz1 = Class.class;
Class<Object> clazz2 = Object.class;
Class<Examinee> clazz3 = Examinee.class;
Class<ExamineeInterface> clazz4 = ExamineeInterface.class;
int modifiers1 = clazz1.getModifiers();
int modifiers2 = clazz2.getModifiers();
int modifiers3 = clazz3.getModifiers();
int modifiers4 = clazz4.getModifiers();

int result = modifiers1 + modifiers2 + modifiers3 + modifiers4;
}
}

abstract class Examinee {
}

interface ExamineeInterface {
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added tests/test_data/std/java/lang/Class.class
Binary file not shown.
7 changes: 7 additions & 0 deletions tests/test_data/std/java/lang/Class.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// javac --patch-module java.base=. Class.java

package java.lang;

public final class Class<T> {
public native int getModifiers();
}
47 changes: 28 additions & 19 deletions vm/src/execution_engine/engine.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::Error;
use crate::execution_engine::ldc_resolution_manager::LdcResolutionManager;
use crate::execution_engine::opcode::*;
use crate::execution_engine::system_native_table::invoke_native_method;
use crate::heap::heap::{with_heap_read_lock, with_heap_write_lock};
Expand All @@ -7,11 +8,15 @@ use crate::method_area::method_area::with_method_area;
use crate::stack::stack_frame::{i32toi64, StackFrame};
use jdescriptor::get_length;

pub(crate) struct Engine {}
pub(crate) struct Engine {
ldc_resolution_manager: LdcResolutionManager,
}

impl Engine {
pub fn new() -> Self {
Self {}
Self {
ldc_resolution_manager: LdcResolutionManager::new(),
}
}

pub(crate) fn execute(
Expand Down Expand Up @@ -92,17 +97,9 @@ impl Engine {
stack_frame.incr_pc();
let cpoolindex = stack_frame.get_bytecode_byte() as u16;

let java_class = with_method_area(|method_area| {
method_area.get(current_class_name.as_str())
})?;
let cpool_helper = java_class.cpool_helper();

// todo add support of other types
let value = cpool_helper.get_integer(cpoolindex).ok_or_else(|| {
Error::new_constant_pool(&format!(
"Error getting value as Integer by index {cpoolindex}"
))
})?;
let value = self
.ldc_resolution_manager
.resolve_ldc(&current_class_name, cpoolindex)?;

stack_frame.push(value);

Expand Down Expand Up @@ -926,14 +923,26 @@ impl Engine {
.ok_or_else(|| Error::new_constant_pool(&format!("Error getting instance type JavaMethod by class name {instance_type_class_name} and full signature {full_signature} invoking virtual")))
})?;

let mut next_frame = virtual_method.new_stack_frame()?;
if virtual_method.is_native() {
let full_native_signature =
format!("{instance_type_class_name}:{full_signature}");
println!(
"<Calling native method> -> {full_native_signature} ({method_args:?})"
);

let result = invoke_native_method(&full_native_signature, &method_args)?;

method_args
.iter()
.enumerate()
.for_each(|(index, val)| next_frame.set_local(index, *val));
result.iter().rev().for_each(|x| stack_frame.push(*x));
} else {
let mut next_frame = virtual_method.new_stack_frame()?;

stack_frames.push(next_frame);
method_args
.iter()
.enumerate()
.for_each(|(index, val)| next_frame.set_local(index, *val));

stack_frames.push(next_frame);
}
println!(
"INVOKEVIRTUAL -> {instance_type_class_name}.{method_name}({method_args:?})"
);
Expand Down
65 changes: 65 additions & 0 deletions vm/src/execution_engine/ldc_resolution_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::error::Error;
use crate::method_area::method_area::with_method_area;
use std::collections::HashMap;
use std::sync::RwLock;

type CPoolIndex = u16;
type Value = i32;

pub struct LdcResolutionManager {
cache: RwLock<HashMap<CPoolIndex, Value>>,
}

impl LdcResolutionManager {
pub fn new() -> Self {
Self {
cache: RwLock::new(HashMap::new()),
}
}

pub fn resolve_ldc(
&self,
current_class_name: &str,
cpoolindex: u16,
) -> crate::error::Result<i32> {
if let Some(value) = self
.cache
.read()
.expect("error getting cache lock")
.get(&cpoolindex)
{
return Ok(*value);
}

let java_class = with_method_area(|method_area| method_area.get(current_class_name))?;
let cpool_helper = java_class.cpool_helper();

let result = if let Some(value) = cpool_helper.get_integer(cpoolindex) {
value
} else if let Some(value) = cpool_helper.get_float(cpoolindex) {
Self::float_to_int(value)
} else if let Some(_value) = cpool_helper.get_utf8(cpoolindex) {
todo!("should return reference to string (in heap)");
} else if let Some(class_name) = cpool_helper.get_class(cpoolindex) {
let class = with_method_area(|method_area| method_area.get(&class_name))?;

class.reflection_ref()
} else {
return Err(Error::new_constant_pool(&format!(
"Error resolving ldc: {}",
cpoolindex
)));
};

self.cache
.write()
.expect("error getting cache write lock")
.insert(cpoolindex, result);

Ok(result)
}

fn float_to_int(value: f32) -> i32 {
value.to_bits() as i32
}
}
1 change: 1 addition & 0 deletions vm/src/execution_engine/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) mod engine;
pub(crate) mod ldc_resolution_manager;
pub(crate) mod opcode;
mod system_native_table;
5 changes: 5 additions & 0 deletions vm/src/execution_engine/system_native_table.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::Error;
use crate::system_native::class::get_modifiers_wrp;
use crate::system_native::system::current_time_millis_wrp;
use once_cell::sync::Lazy;
use std::collections::HashMap;
Expand All @@ -10,6 +11,10 @@ static SYSTEM_NATIVE_TABLE: Lazy<HashMap<&'static str, fn(&[i32]) -> Vec<i32>>>
"java/lang/System:currentTimeMillis:()J",
current_time_millis_wrp as fn(&[i32]) -> Vec<i32>,
);
table.insert(
"java/lang/Class:getModifiers:()I",
get_modifiers_wrp as fn(&[i32]) -> Vec<i32>,
);

table
});
Expand Down
42 changes: 41 additions & 1 deletion vm/src/method_area/cpool_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ impl CPoolHelper {
}
}

pub fn get_float(&self, index: u16) -> Option<f32> {
match self.get(CPoolType::Float, index)? {
ConstantPool::Float { value } => Some(*value),
_ => None,
}
}

pub fn get_class(&self, index: u16) -> Option<String> {
let name_index = match self.get(CPoolType::Class, index)? {
ConstantPool::Class { name_index } => Some(name_index),
_ => None,
}?;

self.get_utf8(*name_index)
}

pub fn get_long(&self, index: u16) -> Option<i64> {
match self.get(CPoolType::Long, index)? {
ConstantPool::Long { value } => Some(*value),
Expand Down Expand Up @@ -169,7 +185,8 @@ impl CPoolHelper {
mod tests {
use super::*;
use jclass::constant_pool::ConstantPool::{
Class, Empty, Fieldref, Integer, InterfaceMethodref, Long, Methodref, NameAndType, Utf8,
Class, Empty, Fieldref, Float, Integer, InterfaceMethodref, Long, Methodref, NameAndType,
Utf8,
};

#[test]
Expand Down Expand Up @@ -519,4 +536,27 @@ mod tests {
let actual = resolver.get_long(2);
assert_eq!(Some(9_000_000_000), actual)
}

#[test]
fn should_return_float() {
let resolver =
CPoolHelper::new(&vec![Empty, Class { name_index: 2 }, Float { value: 3.14 }]);

let actual = resolver.get_float(2);
assert_eq!(Some(3.14), actual)
}

#[test]
fn should_return_class_as_string() {
let resolver = CPoolHelper::new(&vec![
Empty,
Class { name_index: 2 },
Utf8 {
value: "java/lang/Byte".to_string(),
},
]);

let actual = resolver.get_class(1);
assert_eq!(Some("java/lang/Byte".to_string()), actual)
}
}
35 changes: 35 additions & 0 deletions vm/src/method_area/java_class.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::error::Error;
use crate::execution_engine::engine::Engine;
use crate::heap::heap::with_heap_write_lock;
use crate::heap::java_instance::FieldNameType;
use crate::method_area::cpool_helper::CPoolHelper;
use crate::method_area::field::Field;
use crate::method_area::java_method::JavaMethod;
use crate::method_area::method_area::with_method_area;
use jdescriptor::TypeDescriptor;
use once_cell::sync::OnceCell;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
Expand All @@ -18,8 +21,11 @@ pub(crate) struct JavaClass {
this_class_name: String,
parent: Option<String>,
_interfaces: Vec<String>,
access_flags: u16,

static_fields_initialized: AtomicBool,

reflection_ref: OnceCell<i32>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -60,6 +66,7 @@ impl JavaClass {
this_class_name: String,
parent: Option<String>,
interfaces: Vec<String>,
access_flags: u16,
) -> Self {
Self {
methods,
Expand All @@ -69,10 +76,27 @@ impl JavaClass {
this_class_name,
parent,
_interfaces: interfaces,
access_flags,
static_fields_initialized: AtomicBool::new(false),
reflection_ref: OnceCell::new(),
}
}

fn create_reflection_instance(&self) -> i32 {
let reflection_instance = with_method_area(|method_area| {
method_area.create_instance_with_default_fields("java/lang/Class")
});

let reflection_reference =
with_heap_write_lock(|heap| heap.create_instance(reflection_instance));

with_method_area(|method_area| {
method_area.put_to_reflection_table(reflection_reference, &self.this_class_name)
});

reflection_reference
}

pub fn cpool_helper(&self) -> &CPoolHelper {
&self.cpool_helper
}
Expand Down Expand Up @@ -145,6 +169,17 @@ impl JavaClass {
pub fn this_class_name(&self) -> &str {
&self.this_class_name
}

pub fn reflection_ref(&self) -> i32 {
let class_ref = self
.reflection_ref
.get_or_init(|| self.create_reflection_instance());
*class_ref
}

pub fn access_flags(&self) -> u16 {
self.access_flags
}
}

impl Methods {
Expand Down
Loading