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 support for basic system native calls #35

Merged
merged 3 commits into from
Oct 5, 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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ctor::ctor;
use std::env;
use std::sync::Once;
use std::time::{SystemTime, UNIX_EPOCH};
use vm::vm::VM;

static INIT: Once = Once::new();
Expand Down Expand Up @@ -254,6 +255,23 @@ fn should_do_composite_pattern() {
assert_eq!(700, get_int(last_frame_value))
}

#[test]
fn should_do_native_call_on_system() {
let start = SystemTime::now();
let since_the_epoch = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
let expected_millis = since_the_epoch.as_millis() as i64;

let mut vm = VM::new("std");
let last_frame_value = vm
.run("samples.nativecall.system.NativeCallSystem")
.unwrap();

let actual_millis = get_long(last_frame_value);
assert!((expected_millis..expected_millis + 2000).contains(&actual_millis))
}

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

public class NativeCallSystem {
public static void main(String[] args) {
long currentTimeMillis = System.currentTimeMillis();
}
}
Binary file not shown.
Binary file added tests/test_data/std/java/lang/System.class
Binary file not shown.
9 changes: 9 additions & 0 deletions tests/test_data/std/java/lang/System.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// javac --patch-module java.base=. System.java
package java.lang;

public final class System {
private System() {
}

public static native long currentTimeMillis();
}
1 change: 1 addition & 0 deletions vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ edition = "2021"
jclass = {path = "../jclass"}
jdescriptor = {path = "../jdescriptor"}
indexmap = "2.5.0"
once_cell = "1.20.1"
8 changes: 7 additions & 1 deletion vm/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::error::ErrorKind::{ClassFile, ConstantPool, Execution, Io};
use crate::error::ErrorKind::{ClassFile, ConstantPool, Execution, Io, Native};
use std::fmt::{Debug, Display, Formatter};
use std::{io, result};

Expand All @@ -20,6 +20,10 @@ impl Error {
Self::new(Execution(String::from(descr)))
}

pub(crate) fn new_native(descr: &String) -> Error {
Self::new(Native(String::from(descr)))
}

pub fn kind(&self) -> &ErrorKind {
&self.0
}
Expand All @@ -36,6 +40,7 @@ impl Display for Error {
ClassFile(err) => write!(f, "ClassFile Error: {err}"),
ConstantPool(descr) => write!(f, "ConstantPool Error: {descr}"),
Execution(descr) => write!(f, "Execution Error: {descr}"),
Native(descr) => write!(f, "Native Call Error: {descr}"),

ErrorKind::__Nonexhaustive => unreachable!(),
}
Expand All @@ -54,6 +59,7 @@ pub enum ErrorKind {
ClassFile(String),
ConstantPool(String),
Execution(String),
Native(String),

#[doc(hidden)]
__Nonexhaustive,
Expand Down
35 changes: 24 additions & 11 deletions vm/src/execution_engine/engine.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::error::Error;
use crate::execution_engine::opcode::*;
use crate::execution_engine::system_native_table::invoke_native_method;
use crate::heap::heap::Heap;
use crate::method_area::java_method::JavaMethod;
use crate::method_area::method_area::MethodArea;
Expand Down Expand Up @@ -487,7 +488,6 @@ impl Engine {
stack_frame.push(value2);
stack_frame.push(value1);


stack_frame.incr_pc();
println!("DUP_X1 -> value1={value1}, value2={value2}, value1={value1}");
}
Expand Down Expand Up @@ -594,7 +594,7 @@ impl Engine {
IUSHR => {
let b = stack_frame.pop() as u32;
let a = stack_frame.pop() as u32;

let b_trunc = b & 0b00011111u32;
let result = a >> b_trunc;
stack_frame.push(result as i32);
Expand Down Expand Up @@ -981,21 +981,33 @@ impl Engine {
// all static fields of the class should be initialized
// at this point

let mut next_frame = static_method.new_stack_frame()?;
let arg_num = static_method.get_method_descriptor().arguments_length();

let method_args: Vec<i32> = (0..arg_num)
.map(|_| stack_frame.pop())
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect();
method_args
.iter()
.enumerate()
.for_each(|(index, val)| next_frame.set_local(index, *val));

stack_frames.push(next_frame);
if static_method.is_native() {
let full_native_signature = format!("{class_name}:{full_signature}");
println!(
"<Calling native method> -> {full_native_signature} ({method_args:?})"
);

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

result.iter().rev().for_each(|x| stack_frame.push(*x));
} else {
let mut next_frame = static_method.new_stack_frame()?;

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

stack_frames.push(next_frame);
}
println!("INVOKESTATIC -> {class_name}.{method_name}({method_args:?})");
}
INVOKEINTERFACE => {
Expand Down Expand Up @@ -1121,11 +1133,12 @@ impl Engine {
let objectref = stack_frame.pop();
// todo: implementation here
stack_frame.push(objectref);

println!("CHECKCAST -> class_constpool_index={class_constpool_index}, objectref={objectref}");
todo!("add implementation");
}
IFNULL => { //todo: this one is opposite to IFNE ops code
IFNULL => {
//todo: this one is opposite to IFNE ops code
let value = stack_frame.pop();
let offset = Self::get_two_bytes_ahead(stack_frame);
stack_frame.advance_pc(if value == 0 { offset } else { 3 });
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,2 +1,3 @@
pub(crate) mod engine;
pub(crate) mod opcode;
mod system_native_table;
28 changes: 28 additions & 0 deletions vm/src/execution_engine/system_native_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use crate::error::Error;
use crate::system_native::system::current_time_millis_wrp;
use once_cell::sync::Lazy;
use std::collections::HashMap;

static SYSTEM_NATIVE_TABLE: Lazy<HashMap<&'static str, fn(&[i32]) -> Vec<i32>>> =
Lazy::new(|| {
let mut table = HashMap::new();
table.insert(
"java/lang/System:currentTimeMillis:()J",
current_time_millis_wrp as fn(&[i32]) -> Vec<i32>,
);

table
});

pub(crate) fn invoke_native_method(
method_signature: &str,
args: &[i32],
) -> crate::error::Result<Vec<i32>> {
let native_method = SYSTEM_NATIVE_TABLE.get(method_signature).ok_or_else(|| {
Error::new_native(&format!("Native method {method_signature} not found"))
})?;

let result = native_method(args);

Ok(result)
}
1 change: 1 addition & 0 deletions vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub mod vm;
mod execution_engine;
mod heap;
mod method_area;
mod system_native;
7 changes: 7 additions & 0 deletions vm/src/method_area/java_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub(crate) struct JavaMethod {
class_name: String,
name_signature: String,
code_context: Option<CodeContext>,
native: bool,
}

#[derive(Debug)]
Expand Down Expand Up @@ -45,12 +46,14 @@ impl JavaMethod {
class_name: &str,
name_signature: &str,
code_context: Option<CodeContext>,
native: bool,
) -> Self {
Self {
method_descriptor,
class_name: class_name.to_string(),
name_signature: name_signature.to_string(),
code_context,
native,
}
}

Expand All @@ -72,4 +75,8 @@ impl JavaMethod {
pub fn get_method_descriptor(&self) -> &MethodDescriptor {
&self.method_descriptor
}

pub fn is_native(&self) -> bool {
self.native
}
}
5 changes: 4 additions & 1 deletion vm/src/method_area/method_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl MethodArea {

let code_context = if !method_info
.access_flags()
.intersects(MethodFlags::ACC_ABSTRACT|MethodFlags::ACC_NATIVE)
.intersects(MethodFlags::ACC_ABSTRACT | MethodFlags::ACC_NATIVE)
{
AttributesHelper::new(method_info.attributes())
.get_code()
Expand All @@ -203,13 +203,16 @@ impl MethodArea {
))
})?;

let native = method_info.access_flags().contains(MethodFlags::ACC_NATIVE);

method_by_signature.insert(
key.clone(),
Rc::new(JavaMethod::new(
method_descriptor,
class_name,
&key,
code_context,
native,
)),
);
}
Expand Down
1 change: 1 addition & 0 deletions vm/src/system_native/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod system;
16 changes: 16 additions & 0 deletions vm/src/system_native/strict_math.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// use crate::stack::stack_frame::i32toi64;
//
// pub(crate) fn subtract_exact_wrp(args: &[i32]) -> Vec<i32> {
// let i = i32toi64(args[1], args[0]);
// let j = i32toi64(args[3], args[2]);
//
// let millis = subtract_exact(i, j);
//
// let low = millis as i32;
// let high = (millis >> 32) as i32;
//
// vec![high, low]
// }
// fn subtract_exact(first: i64, second: i64) -> i64 {
// first.checked_sub(second).expect("Subtraction overflow")
// }
15 changes: 15 additions & 0 deletions vm/src/system_native/system.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pub(crate) fn current_time_millis_wrp(_args: &[i32]) -> Vec<i32> {
let millis = current_time_millis();

let low = millis as i32;
let high = (millis >> 32) as i32;

vec![high, low]
}
fn current_time_millis() -> i64 {
let now = std::time::SystemTime::now();
let since_the_epoch = now
.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards");
since_the_epoch.as_millis() as i64
}