Skip to content

Commit

Permalink
Add support of LDC for Strings, including adding trivial string pool
Browse files Browse the repository at this point in the history
  • Loading branch information
hextriclosan committed Oct 14, 2024
1 parent f8f5d7c commit 15dd6e7
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 10 deletions.
18 changes: 18 additions & 0 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,24 @@ fn should_do_trivial_strings() {
assert_eq!(8, get_int(last_frame_value))
}

#[test]
fn should_do_trivial_strings_cpool() {
let mut vm = VM::new("std");
let last_frame_value = vm
.run("samples.javacore.strings.cpool.trivial.TrivialStringsCPool")
.unwrap();
assert_eq!(8, get_int(last_frame_value))
}

#[test]
fn should_do_strings_cpool_advanced() {
let mut vm = VM::new("std");
let last_frame_value = vm
.run("samples.javacore.strings.cpool.advanced.StringPoolAdvanced")
.unwrap();
assert_eq!(29, get_int(last_frame_value))
}

#[test]
fn should_do_trivial_util_arrays() {
let mut vm = VM::new("std");
Expand Down
55 changes: 55 additions & 0 deletions tests/test_data/StringPoolAdvanced.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package samples.javacore.strings.cpool.advanced;

public class StringPoolAdvanced {
public static void main(String[] args) {
// Example 0: String literals of same class are stored in the string pool
String str1 = "Hello";
String str2 = "Hello";
// Both str1 and str2 refer to the same string literal from the pool
int bit0 = str1 == str2 ? 1 : 0;

// Example 1: Creating a string with new keyword
String str3 = new String("Hello");
// str3 is created in the heap, not in the string pool
int bit1 = str1 == str3 ? 1 : 0;

// Example 2: Comparing strings with equals method
int bit2 = str1.equals(str3) ? 1 : 0;

// Example 3: Concatenation with literals at compile-time
String str5 = "Hel" + "lo"; // Compiler optimizes this to "Hello"
int bit3 = str1 == str5 ? 1 : 0;

// Example 4: Creating a string in another class
String str4 = new AnotherClass().getAnotherString();
int bit4 = str1 == str4 ? 1 : 0;

// Example X: Concatenation with non-literals at runtime
//String part1 = "Hel";
//String part2 = "lo";
//String str6 = part1 + part2; // New object is created in the heap
//System.out.println("str1 == str6 : " + (str1 == str6)); // false

// Example X: Interning the string (forcing it into the pool)
//String str4 = str3.intern();
// Now str4 refers to the string from the pool
//System.out.println("str1 == str4 : " + (str1 == str4)); // true

int result = 0;
result = setBit(result, 0, bit0);
result = setBit(result, 1, bit1);
result = setBit(result, 2, bit2);
result = setBit(result, 3, bit3);
result = setBit(result, 4, bit4);
}

private static int setBit(int num, int position, int value) {
return value == 0 ? num & ~(1 << position) : num | (1 << position);
}
}

class AnotherClass {
public String getAnotherString() {
return "Hello";
}
}
10 changes: 10 additions & 0 deletions tests/test_data/TrivialStringsCPool.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package samples.javacore.strings.cpool.trivial;

public class TrivialStringsCPool {
public static void main(String[] args) {
String text = "Java is flexible";
String search = new String("flexible");

int index = text.indexOf(search);
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
38 changes: 35 additions & 3 deletions vm/src/execution_engine/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ 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};
use crate::method_area::instance_checker::InstanceChecker;
use crate::method_area::java_method::JavaMethod;
use crate::method_area::method_area::with_method_area;
use crate::stack::stack_frame::{i32toi64, StackFrame};
use jdescriptor::get_length;
Expand All @@ -24,9 +23,9 @@ impl Engine {

pub(crate) fn execute(
&mut self,
method: &JavaMethod,
stack_frame: StackFrame,
) -> crate::error::Result<Option<Vec<i32>>> {
let mut stack_frames = vec![method.new_stack_frame()?];
let mut stack_frames = vec![stack_frame];
let mut last_value: Option<Vec<i32>> = None;
let mut current_class_name: String;

Expand Down Expand Up @@ -673,6 +672,29 @@ impl Engine {
stack_frame.incr_pc();
println!("LREM -> {a} % {b} = {result}");
}
ISHL => {
let b = stack_frame.pop();
let a = stack_frame.pop();

let b_trunc = b & 0b00011111;
let result = a << b_trunc;
stack_frame.push(result);

stack_frame.incr_pc();
println!("ISHL -> {a} << {b} = {result}");
}
ISHR => {
// todo: recheck spec
let b = stack_frame.pop() as u32;
let a = stack_frame.pop();

let b_trunc = b & 0b00011111u32;
let result = a >> b_trunc;
stack_frame.push(result);

stack_frame.incr_pc();
println!("ISHR -> {a} >> {b} = {result}");
}
IUSHR => {
let b = stack_frame.pop() as u32;
let a = stack_frame.pop() as u32;
Expand All @@ -694,6 +716,16 @@ impl Engine {
stack_frame.incr_pc();
println!("IAND -> {a} & {b} = {result}");
}
IOR => {
let b = stack_frame.pop();
let a = stack_frame.pop();

let result = a | b;
stack_frame.push(result);

stack_frame.incr_pc();
println!("IOR -> {a} | {b} = {result}");
}
IXOR => {
let b = stack_frame.pop();
let a = stack_frame.pop();
Expand Down
8 changes: 3 additions & 5 deletions vm/src/execution_engine/ldc_resolution_manager.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::Error;
use crate::execution_engine::string_pool_helper::StringPoolHelper;
use crate::method_area::method_area::with_method_area;
use std::collections::HashMap;
use std::sync::RwLock;
Expand Down Expand Up @@ -39,11 +40,8 @@ impl LdcResolutionManager {
value
} else if let Some(value) = cpool_helper.get_float(cpoolindex) {
Self::float_to_int(value)
} else if let Some(_value) = cpool_helper.get_string(cpoolindex) {
todo!(
"should return reference to string (in heap) cpoolindex={}",
cpoolindex
);
} else if let Some(value) = cpool_helper.get_string(cpoolindex) {
StringPoolHelper::get_string(value)?
} else if let Some(class_name) = cpool_helper.get_class(cpoolindex) {
let class = with_method_area(|method_area| method_area.get(&class_name))?;

Expand Down
1 change: 1 addition & 0 deletions vm/src/execution_engine/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub(crate) mod engine;
pub(crate) mod ldc_resolution_manager;
pub(crate) mod opcode;
mod string_pool_helper;
mod system_native_table;
55 changes: 55 additions & 0 deletions vm/src/execution_engine/string_pool_helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::error::Error;
use crate::execution_engine::engine::Engine;
use crate::heap::heap::{with_heap_read_lock, with_heap_write_lock};
use crate::method_area::method_area::with_method_area;

pub struct StringPoolHelper {}

impl StringPoolHelper {
pub(crate) fn get_string(cpool_string: String) -> crate::error::Result<i32> {
if let Some(value) = with_heap_read_lock(|heap| heap.get_const_string_ref(&cpool_string)) {
return Ok(value);
}
// todo: possible race condition here
let string_ref = Self::create_string(&cpool_string)?;
with_heap_write_lock(|heap| heap.put_const_string_ref(&cpool_string, string_ref));
Ok(string_ref)
}

fn create_string(string: &str) -> crate::error::Result<i32> {
let codepoints = Self::string_to_codepoints(&string);

let array_ref = with_heap_write_lock(|heap| heap.create_array_with_values(&codepoints));
let string_class_name = "java/lang/String".to_string();
let instance_with_default_fields = with_method_area(|method_area| {
method_area.create_instance_with_default_fields(&string_class_name)
});

let string_instance_ref =
with_heap_write_lock(|heap| heap.create_instance(instance_with_default_fields));

let full_signature = "<init>:([III)V";
let rc = with_method_area(|method_area| method_area.get(string_class_name.as_str()))?;
let special_method = rc
.methods
.method_by_signature
.get(full_signature)
.ok_or_else(|| Error::new_constant_pool(&format!("Error getting JavaMethod by class name {string_class_name} and full signature {full_signature} invoking special")))?;

let mut engine = Engine::new();
let mut stack_frame = special_method.new_stack_frame()?;
stack_frame.set_local(0, string_instance_ref);
stack_frame.set_local(1, array_ref);
stack_frame.set_local(2, 0);
stack_frame.set_local(3, codepoints.len() as i32);

engine.execute(stack_frame)?;

// todo: ensure that array_ref is collected by GC
Ok(string_instance_ref)
}

fn string_to_codepoints(s: &str) -> Vec<i32> {
s.chars().map(|c| c as i32).collect()
}
}
20 changes: 20 additions & 0 deletions vm/src/heap/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ where
pub(crate) struct Heap {
data: HashMap<i32, HeapValue>,
next_id: i32,
ref_by_stringvalue: HashMap<String, i32>,
}

impl Heap {
fn new() -> Self {
Self {
data: HashMap::new(),
next_id: 0,
ref_by_stringvalue: HashMap::new(),
}
}

Expand Down Expand Up @@ -93,6 +95,15 @@ impl Heap {
self.next_id
}

pub(crate) fn create_array_with_values(&mut self, array: &[i32]) -> i32 {
self.next_id = self.next_id + 1; //todo: make me atomic

self.data
.insert(self.next_id, Arr(Array::new_with_values(array)));

self.next_id
}

pub(crate) fn get_array_value(
&self,
arrayref: i32,
Expand Down Expand Up @@ -125,4 +136,13 @@ impl Heap {
Err(Error::new_execution("error getting array length"))
}
}

pub(crate) fn get_const_string_ref(&self, string: &str) -> Option<i32> {
self.ref_by_stringvalue.get(string).map(|v| *v)
}

pub(crate) fn put_const_string_ref(&mut self, string: &str, reference: i32) -> Option<i32> {
self.ref_by_stringvalue
.insert(string.to_string(), reference)
}
}
8 changes: 8 additions & 0 deletions vm/src/heap/java_instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ impl Array {
}
}

pub fn new_with_values(array: &[i32]) -> Self {
//todo: is not suitable for long and double
let converted_arr = array.iter().map(|item| vec![*item]).collect();
Self {
data: converted_arr,
}
}

pub fn get_value(&self, index: i32) -> crate::error::Result<&Vec<i32>> {
if let Some(arr_value) = self.data.get(index as usize) {
Ok(arr_value)
Expand Down
2 changes: 1 addition & 1 deletion vm/src/method_area/java_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl JavaClass {
Self::STATIC_INIT_METHOD
);
let mut engine = Engine::new();
engine.execute(static_init_method)?;
engine.execute(static_init_method.new_stack_frame()?)?;
println!(
"<RETURN> -> {}.{}",
self.this_class_name,
Expand Down
2 changes: 1 addition & 1 deletion vm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ impl VM {

let mut engine = Engine::new();

engine.execute(java_method)
engine.execute(java_method.new_stack_frame()?)
}
}

0 comments on commit 15dd6e7

Please sign in to comment.