Implement thread management, and improve VM initialization

Initphase 1 supported
This commit is contained in:
james 2025-12-20 22:15:20 +10:30
parent 94f43066f9
commit f00cfadb76
No known key found for this signature in database
GPG Key ID: E1FFBA228F4CAD87
15 changed files with 400 additions and 198 deletions

View File

@ -21,7 +21,7 @@ roast-vm-core = { path = "crates/core" }
colored = "3.0.0"
parking_lot = "0.12"
cesu8 = "1.1.0"
windows = { version = "0.62.0", features = ["Win32_System_Diagnostics_Debug", "Win32_System_Kernel"] }
[profile.dev-opt]
inherits = "dev"
opt-level = 2 # 0=none, 1=basic, 2=good, 3=aggressive

View File

@ -18,6 +18,7 @@ itertools = { workspace = true }
colored = { workspace = true }
parking_lot = { workspace = true }
cesu8 = { workspace = true }
windows = { workspace = true }

View File

@ -15,6 +15,7 @@
//! - [`MethodDescriptor`] - Method signature information
//! - [`FieldType`] - Field type information
use crate::class_file::MethodRef;
use crate::class_file::constant_pool::ConstantPoolExt;
use crate::class_file::constant_pool::ConstantPoolGet;
pub use crate::thread::VmThread;
@ -24,10 +25,12 @@ use frame::Frame;
use itertools::Itertools;
use log::error;
use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt::{Debug, Display, Formatter};
use std::io::Read;
use std::ops::{BitAnd, Deref};
use value::Value;
mod attributes;
mod bimage;
mod class;
@ -127,13 +130,13 @@ pub struct MethodDescriptor {
}
impl MethodDescriptor {
fn void() -> Self {
pub fn void() -> Self {
Self {
parameters: vec![],
return_type: None,
}
}
fn psvm() -> Self {
pub fn psvm() -> Self {
MethodDescriptor::parse("([Ljava/lang/String;)V").unwrap()
}
@ -250,3 +253,58 @@ impl FieldType {
}
}
}
pub fn generate_jni_method_name(method_ref: &MethodRef, with_type: bool) -> String {
if with_type {
generate_jni_long_name(
&method_ref.class,
&method_ref.name,
&method_ref.desc.param_string(),
)
} else {
generate_jni_short_name(&method_ref.class, &method_ref.name)
}
}
pub fn generate_jni_long_name(class: &str, method: &str, params: &str) -> String {
let mut name = generate_jni_short_name(class, method);
let params = jni_escape(params);
name.push_str(&format!("__{params}"));
name
}
pub fn generate_jni_short_name(class: &str, method: &str) -> String {
let class_name = jni_escape(class);
let method_name = jni_escape(method);
format!("Java_{class_name}_{method_name}")
}
pub fn jni_escape(s: &str) -> String {
s.chars().map(jni_escape_char).join("")
}
pub fn jni_escape_char(c: char) -> String {
match c {
'/' => "_".to_string(),
'_' => "_1".to_string(),
';' => "_2".to_string(),
'[' => "_3".to_string(),
c if c.is_ascii_alphanumeric() => c.to_string(),
c => format!("_0{:04x}", c as u32),
}
}
thread_local! {
pub static LAST_NATIVE_SYMBOL: RefCell<String> = RefCell::new(String::new());
}
fn set_last_native(name: &str) {
LAST_NATIVE_SYMBOL.with(|cell| {
let mut s = cell.borrow_mut();
s.clear();
s.push_str(name);
});
}
pub fn get_last_native() -> String {
LAST_NATIVE_SYMBOL.with(|cell| cell.borrow().clone())
}

View File

@ -2,8 +2,8 @@ use colored::Colorize;
use libloading::Library;
use log::{LevelFilter, error};
use roast_vm_core::error::VmError;
use roast_vm_core::stack_used;
use roast_vm_core::vm::Vm;
use roast_vm_core::vm::{LIB_RESOLVE_COUNTS, Vm};
use roast_vm_core::{get_last_native, stack_used};
use std::path::PathBuf;
use std::time::Instant;
@ -28,6 +28,7 @@ fn run() {
let mut vm = Vm::new();
fetch_libs(&vm);
let start = Instant::now();
install_crash_handler();
match vm.run("org/example/Main") {
Ok(_) => {
println!("took {:?}", start.elapsed());
@ -39,6 +40,9 @@ fn run() {
println!("took {:?}", start.elapsed());
let access = vm.loader.lock().access_time();
println!("total module access time was {:?}", access);
for entry in LIB_RESOLVE_COUNTS.iter() {
println!("{}: {}", entry.key(), entry.value());
}
let thread = vm.threads.get(&vm.main_thread_id).unwrap();
let objs = thread
.gc
@ -99,7 +103,7 @@ fn os_lib_name(name: &str) -> String {
format!("lib{name}.so")
}
fn fetch_libs(mut vm: &Vm) {
fn fetch_libs(vm: &Vm) {
let exe_path = std::env::current_exe().expect("get exe path");
let roast_name = os_lib_name("roast_vm");
let roast_path = exe_path.parent().unwrap().join(roast_name.clone());
@ -146,3 +150,19 @@ fn format_bytes(bytes: usize) -> String {
format!(" {:.2} {}", size, unit)
}
use windows::Win32::Foundation::EXCEPTION_ACCESS_VIOLATION;
use windows::Win32::System::Diagnostics::Debug::*;
unsafe extern "system" fn crash_handler(info: *mut EXCEPTION_POINTERS) -> i32 {
let record = (*info).ExceptionRecord;
if (*record).ExceptionCode == EXCEPTION_ACCESS_VIOLATION {
eprintln!("ACCESS VIOLATION during native call: {}", get_last_native());
}
EXCEPTION_CONTINUE_SEARCH
}
// Call once at VM startup:
fn install_crash_handler() {
unsafe {
AddVectoredExceptionHandler(1, Some(crash_handler));
}
}

View File

@ -7,7 +7,7 @@ use crate::objects::object::{ObjectReference, ReferenceKind};
use crate::prim::jboolean;
use crate::thread::VmThread;
use crate::value::{Primitive, Value};
use crate::{BaseType, FieldType, MethodDescriptor};
use crate::{BaseType, FieldType, MethodDescriptor, generate_jni_short_name};
use itertools::Itertools;
use jni::sys::*;
use jni::sys::{JNIEnv, jstring};
@ -380,13 +380,11 @@ unsafe extern "system" fn register_natives(
) -> jint {
trace!("register_natives");
let thread = &*get_thread(env);
let gc = thread.gc.read();
let class_id = clazz as u32;
let ReferenceKind::ObjectReference(class_ref) = gc.get(class_id) else {
let Some(runtime_class) = thread.loader.lock().class_from_mirror_id(class_id) else {
return JNI_ERR;
};
let class_name = class_ref.lock().class.this_class.clone();
let class_name = class_name.replace("/", "_");
let class_name = runtime_class.this_class.clone();
// let boop = JClass::from_raw(clazz);
for i in 0..n_methods as usize {
@ -397,7 +395,7 @@ unsafe extern "system" fn register_natives(
let signature = CStr::from_ptr(native_method.signature).to_str().unwrap();
let fn_ptr = native_method.fnPtr;
let full_name = format!("Java_{class_name}_{name}");
let full_name = generate_jni_short_name(&class_name, name);
if thread.vm.native_methods.contains_key(&full_name) {
warn!("Native method already registered {full_name}")
@ -408,7 +406,18 @@ unsafe extern "system" fn register_natives(
continue;
}
if full_name.contains("getDeclaredConstructors0") {
const DISALLOWED_METHODS: &[&str] = &[
"getDeclaredConstructors0",
"Java_java_lang_Thread_currentThread",
"Java_java_lang_Thread_getNextThreadIdOffset",
"Java_java_lang_Thread_setPriority0",
"Java_java_lang_Thread_start0",
];
if DISALLOWED_METHODS
.iter()
.any(|&method| full_name.contains(method))
{
continue;
}
@ -420,7 +429,8 @@ unsafe extern "system" fn register_natives(
trace!(
"name:{name}, signature:{signature}, fn_ptr{}",
fn_ptr.is_null()
)
);
println!("JNI register: {full_name}");
}
JNI_OK
}

View File

@ -1,9 +1,6 @@
// unsafe_support.rs or similar
use crate::prim::jlong;
use std::collections::HashMap;
use std::sync::atomic::{AtomicI64, Ordering};
use crate::prim::jlong;
#[derive(Default)]
pub struct UnsafeSupport {
@ -27,15 +24,17 @@ const FIELD_OFFSET_BASE: jlong = 0x1_0000_0000;
impl UnsafeSupport {
pub fn register_field_offset(&mut self, class: &str, field: &str, is_static: bool) -> jlong {
let offset = self.next_field_offset.fetch_add(1, Ordering::Relaxed);
self.field_offsets.insert(offset, FieldKey {
class_name: class.to_string(),
field_name: field.to_string(),
is_static,
});
self.field_offsets.insert(
offset,
FieldKey {
class_name: class.to_string(),
field_name: field.to_string(),
is_static,
},
);
offset
}
pub fn resolve_field(&self, offset: jlong) -> Option<FieldKey> {
self.field_offsets.get(&offset).cloned()
}
@ -45,3 +44,6 @@ pub enum OffsetKind {
ArrayIndex(jlong),
Field(FieldKey),
}
// SAFETY: All access is through RwLock<UnsafeSupport> in Vm
unsafe impl Send for UnsafeSupport {}
unsafe impl Sync for UnsafeSupport {}

View File

@ -325,6 +325,69 @@ impl ObjectManager {
Ok(())
}
pub fn wait(
&self,
thread_id: ThreadId,
obj_id: u32,
timeout_millis: jlong,
) -> Result<(), VmError> {
let monitor = self
.monitors
.get(&obj_id)
.ok_or_else(|| VmError::InvariantError("Monitor not found".into()))?
.clone();
let mut inner = monitor.inner.lock();
if inner.owner != Some(thread_id) {
return Err(VmError::InvariantError(
"IllegalMonitorStateException".into(),
));
}
// Save entry count, release monitor
let saved_count = inner.entry_count;
inner.entry_count = 0;
inner.owner = None;
inner.wait_count += 1;
// Wake a thread waiting to enter
monitor.condvar.notify_one();
// Wait for notify
if timeout_millis > 0 {
monitor.wait_condvar.wait_for(
&mut inner,
std::time::Duration::from_millis(timeout_millis as u64),
);
} else {
monitor.wait_condvar.wait(&mut inner);
}
inner.wait_count -= 1;
// Reacquire monitor
while inner.owner.is_some() {
monitor.condvar.wait(&mut inner);
}
inner.owner = Some(thread_id);
inner.entry_count = saved_count;
Ok(())
}
pub fn notify_one(&self, obj_id: u32) {
if let Some(monitor) = self.monitors.get(&obj_id) {
monitor.wait_condvar.notify_one();
}
}
pub fn notify_all(&self, obj_id: u32) {
if let Some(monitor) = self.monitors.get(&obj_id) {
monitor.wait_condvar.notify_all();
}
}
pub fn clone_object(&mut self, source: &ReferenceKind) -> Result<ReferenceKind, VmError> {
match source {
@ -454,11 +517,13 @@ impl ObjectManager {
pub struct Monitor {
inner: Mutex<MonitorInner>,
condvar: Condvar,
wait_condvar: Condvar,
}
struct MonitorInner {
owner: Option<ThreadId>,
entry_count: u32,
wait_count: u32,
}
impl Monitor {
@ -467,8 +532,10 @@ impl Monitor {
inner: Mutex::new(MonitorInner {
owner: None,
entry_count: 0,
wait_count: 0,
}),
condvar: Condvar::new(),
wait_condvar: Condvar::new(),
}
}
}

View File

@ -8,7 +8,10 @@ use crate::objects::object::{ObjectReference, ReferenceKind};
use crate::objects::object_manager::ObjectManager;
use crate::value::{Primitive, Value};
use crate::vm::Vm;
use crate::{BaseType, FieldType, MethodDescriptor, ThreadId, stack_used};
use crate::{
BaseType, FieldType, MethodDescriptor, ThreadId, generate_jni_method_name, set_last_native,
stack_used,
};
use deku::DekuError::Incomplete;
use itertools::Itertools;
use jni::sys::{JNIEnv, jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jobject, jshort};
@ -16,7 +19,7 @@ use libffi::low::call;
use libffi::middle::*;
use log::{LevelFilter, info, trace, warn};
use parking_lot::{Mutex, Once, ReentrantMutex, RwLock};
use parking_lot::{Condvar, Mutex, Once, ReentrantMutex, RwLock};
use std::any::Any;
use std::cell::RefCell;
use std::collections::VecDeque;
@ -47,6 +50,7 @@ pub struct VmThread {
pub gc: Arc<RwLock<ObjectManager>>,
pub jni_env: JNIEnv,
pub mirror: OnceLock<u32>,
init_condvar: Condvar,
}
impl VmThread {
@ -65,6 +69,7 @@ impl VmThread {
gc,
jni_env,
mirror: Default::default(),
init_condvar: Default::default(),
}
})
}
@ -198,6 +203,9 @@ impl VmThread {
pub fn ensure_initialised(&self, class: &Arc<RuntimeClass>) -> Result<(), VmError> {
let current_thread = thread::current().id();
if class.this_class.contains("Thread") {
println!("Yep");
}
{
let mut state = class.init_state.lock();
@ -205,10 +213,21 @@ impl VmThread {
InitState::Initialized => return Ok(()),
InitState::Initializing(tid) if *tid == current_thread => return Ok(()),
InitState::Initializing(_) => {
return Err(VmError::LoaderError(format!(
"Class {} is being initialized by another thread",
class.this_class
)));
// Wait for the other thread to finish
while matches!(&*state, InitState::Initializing(_)) {
self.init_condvar.wait(&mut state);
}
// Re-check state after waking
match &*state {
InitState::Initialized => return Ok(()),
InitState::Error(msg) => {
return Err(VmError::LoaderError(format!(
"Class {} initialization previously failed: {}",
class.this_class, msg
)));
}
_ => unreachable!(),
}
}
InitState::Error(msg) => {
return Err(VmError::LoaderError(format!(
@ -236,6 +255,9 @@ impl VmThread {
}
if let Some(method) = class.methods.iter().find(|m| m.name == "<clinit>") {
if class.this_class.contains("Thread") {
println!("executing init for: {}", class.this_class);
}
self.execute_method(class, method, vec![])?;
}
Ok(())
@ -287,7 +309,7 @@ impl VmThread {
}
pub fn invoke(&self, method_reference: MethodRef, args: Vec<Value>) -> MethodCallResult {
if self.gc.read().objects.len() > 2235 {
if self.gc.read().objects.len() > 2387 {
INIT_LOGGER.call_once(|| {
env_logger::Builder::from_default_env()
.filter_level(LevelFilter::Trace)
@ -321,10 +343,11 @@ impl VmThread {
pub fn invoke_native(&self, method: &MethodRef, mut args: Vec<Value>) -> MethodCallResult {
let symbol_name = generate_jni_method_name(method, false);
if symbol_name.contains("Java_jdk_internal_misc_Unsafe_getLongVolatile") {
return Err(VmError::Debug(
"RoastVM specific implementation required for Java_jdk_internal_misc_Unsafe_getLongVolatile",
));
if symbol_name.contains("Java_java_lang_Thread_start0") {
// return Err(VmError::Debug(
// "RoastVM specific implementation required for Java_java_lang_Thread_start0",
// ));
println!()
}
if symbol_name.contains("Java_java_lang_Class_desiredAssertionStatus0") {
@ -356,7 +379,7 @@ impl VmThread {
&self.jni_env as *const _ as *mut JNIEnv,
);
let cif = method.build_cif();
set_last_native(&symbol_name);
match &method.desc.return_type {
None => {
cif.call::<()>(cp, built_args.as_ref());
@ -521,33 +544,6 @@ fn build_args<'a>(
storage.iter().map(|boxed| arg(&**boxed)).collect()
}
pub fn generate_jni_method_name(method_ref: &MethodRef, with_type: bool) -> String {
let class_name = jni_escape(&method_ref.class);
let method_name = jni_escape(&method_ref.name);
let mut name = format!("Java_{class_name}_{method_name}");
if with_type {
let params = jni_escape(&method_ref.desc.param_string());
let str = format!("__{}", params);
name.push_str(&str)
}
name
}
pub fn jni_escape_char(c: char) -> String {
match c {
'/' => "_".to_string(),
'_' => "_1".to_string(),
';' => "_2".to_string(),
'[' => "_3".to_string(),
c if c.is_ascii_alphanumeric() => c.to_string(),
c => format!("_0{:04x}", c as u32),
}
}
pub fn jni_escape(s: &str) -> String {
s.chars().map(jni_escape_char).join("")
}
impl From<FieldType> for Type {
fn from(value: FieldType) -> Self {
match value {
@ -625,3 +621,8 @@ impl VmThread {
self.mirror.set(thread.lock().id).unwrap();
}
}
// SAFETY:
// - jni_env pointer is owned by this VmThread and only used by its OS thread
// - Frame stack is behind Mutex
unsafe impl Send for VmThread {}
unsafe impl Sync for VmThread {}

View File

@ -20,11 +20,12 @@ use dashmap::DashMap;
use imp::Library;
use log::trace;
use parking_lot::{Mutex, RwLock};
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::{Arc, LazyLock};
type NativeLibraries = Arc<RwLock<Vec<(String, Library)>>>;
pub static LIB_RESOLVE_COUNTS: LazyLock<DashMap<String, u32>> = LazyLock::new(DashMap::new);
// struct AbstractObject<'a> {}
pub struct Vm {
// Thread registry - maps ThreadId to VmThread
@ -78,9 +79,10 @@ impl Vm {
let res = unsafe { lib.get::<unsafe extern "system" fn()>(name.as_bytes()) };
if res.is_ok() {
println!(
"Invoke native for native symbol: {} out of: {}",
"Register native for native symbol: {} out of: {}",
&name, lib_name
);
*LIB_RESOLVE_COUNTS.entry(lib_name.clone()).or_insert(0) += 1;
let symbol = res.unwrap();
let ptr = *symbol as *const c_void;
self.native_methods.insert(name.to_string(), ptr);
@ -168,7 +170,7 @@ impl Vm {
.unwrap()
.bootstrap_mirror();
thread.invoke(phase1ref, Vec::new())?;
panic!("HOLY FUCKING SHIT, DID WE PHASE 1!?");
// panic!("HOLY FUCKING SHIT, DID WE PHASE 1!?");
Ok(())
}
@ -179,3 +181,9 @@ impl Vm {
thread.invoke_main(what)
}
}
// SAFETY:
// - native_methods contains function pointers from loaded libraries, safe to share
// - All mutable state is behind DashMap/RwLock/Mutex
unsafe impl Send for Vm {}
unsafe impl Sync for Vm {}

View File

@ -9,6 +9,7 @@ mod runtime;
mod scoped_memory_access;
mod signal;
mod system;
mod system_props;
mod thread;
use jni::objects::{JClass, JObject, JString};
@ -41,52 +42,6 @@ pub extern "system" fn Java_org_example_MockIO_print<'local>(
// println!("Yeetus bageetus! Im printing from rust!")
}
/*#[unsafe(no_mangle)]
pub extern "system" fn Java_org_example_MockIO_registerNatives<'local>(
mut env: JNIEnv<'local>,
jclass: JClass<'local>,
) {
let system_methods = vec![NativeMethod {
name: JNIString::from("currentTimeMillis"),
sig: JNIString::from("()J"),
fn_ptr: current_time_millis as *mut c_void,
}];
let system_class = env
.find_class("java/lang/System")
.expect("Failed to find system class");
env.register_native_methods(system_class, &system_methods)
.expect("failed to register method");
let library_methods = vec![NativeMethod {
name: JNIString::from("findEntry0"),
sig: JNIString::from("(JLjava/lang/String;)J"),
fn_ptr: findEntry0 as *mut c_void,
}];
let library_class = env
.find_class("jdk/internal/loader/NativeLibrary")
.expect("Failed to find system class");
// env.register_native_methods(library_class, &library_methods)
// .expect("failed to register method");
}*/
#[unsafe(no_mangle)]
pub extern "system" fn findEntry0<'local>(
mut env: JNIEnv<'local>,
jclass: JClass<'local>,
handle: jlong,
name: JString<'local>,
) -> jlong {
let name: String = env
.get_string(&name)
.expect("Couldn't get java string!")
.into();
println!("Name: {}, Handle: {}", name, handle);
0i64
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_org_example_Main_getTime<'local>(
mut env: JNIEnv<'local>,
@ -111,38 +66,6 @@ pub extern "system" fn Java_org_example_MockIO_println<'local>(
println!("{input}")
}
// #[unsafe(no_mangle)]
// pub extern "system" fn Java_java_lang_Class_registerNatives<'local>(
// mut env: JNIEnv<'local>,
// jclass: JClass<'local>,
// ) {
// println!("Java_java_lang_Class_registerNatives is NOP")
// }
//
// #[unsafe(no_mangle)]
// pub extern "system" fn Java_java_lang_Object_getClass<'local>(
// mut env: JNIEnv<'local>,
// this: JObject<'local>,
// ) -> JClass<'local> {
// unsafe {
// if this.is_null() {
// env.throw_new(
// "java/lang/NullPointerException",
// "Cannot invoke getClass() on null",
// )
// .unwrap();
// return JClass::from_raw(std::ptr::null_mut());
// }
// }
// match env.get_object_class(this) {
// Ok(c) => c,
// Err(e) => {
// eprintln!("get_object_class failed: {:?}", e);
// panic!("debug");
// }
// }
// }
unsafe fn get_thread(env: *mut jni::sys::JNIEnv) -> *const VmThread {
let thread = (**env).reserved0 as *const VmThread;
VmThread::set_current((*thread).id);
@ -171,50 +94,3 @@ fn resolve_reference(thread: &VmThread, obj: jobject) -> Option<ReferenceKind> {
let gc = thread.gc.read();
Some(gc.get(obj as u32).clone())
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_jdk_internal_util_SystemProps_00024Raw_vmProperties<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,
) -> jobjectArray {
let props = [
"java.vm.specification.name",
"Java Virtual Machine Specification",
"java.vm.specification.vendor",
"Oracle Corporation",
"java.vm.specification.version",
"25",
"java.vm.name",
"RoastVM",
"java.vm.vendor",
"infernap12",
"java.vm.version",
"0.1.0",
"java.vm.info",
"interpreted mode",
"jdk.debug",
"release",
"java.home",
"C:\\Program Files\\Java\\jdk-25", // adjust
"java.library.path",
"",
"sun.boot.library.path",
"",
"java.class.path",
".",
];
let string_class = env.find_class("java/lang/String").unwrap();
// +1 for null terminator
let arr = env
.new_object_array((props.len() + 1) as i32, string_class, JObject::null())
.unwrap();
for (i, s) in props.iter().enumerate() {
let jstr = env.new_string(s).unwrap();
env.set_object_array_element(&arr, i as i32, jstr).unwrap();
}
// Last element stays null (terminator)
arr.into_raw()
}

View File

@ -1,3 +1,4 @@
use crate::thread::THREAD_NEXT_ID_SENTINEL;
use crate::{get_thread, resolve_array, resolve_object};
use jni::sys::{JNI_FALSE, JNI_TRUE, JNIEnv, jboolean, jclass, jint, jlong, jobject, jstring};
use log::warn;
@ -6,6 +7,7 @@ use roast_vm_core::objects::ReferenceKind;
use roast_vm_core::objects::array::ArrayReference;
use roast_vm_core::objects::object::string_from_bytes;
use roast_vm_core::value::Value;
use std::sync::atomic::Ordering;
#[unsafe(no_mangle)]
pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_registerNatives(
@ -240,6 +242,17 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_compareAndSetLong(
new_val: jlong,
) -> jboolean {
let thread = &*get_thread(env);
if offset == THREAD_NEXT_ID_SENTINEL {
return match thread.vm.NEXT_ID.compare_exchange(
expected as u64,
new_val as u64,
Ordering::SeqCst,
Ordering::Relaxed,
) {
Ok(_) => JNI_TRUE,
Err(_) => JNI_FALSE,
};
}
let reference = thread.gc.read().get(obj as u32);
match reference {
@ -580,6 +593,9 @@ pub unsafe extern "system" fn Java_jdk_internal_misc_Unsafe_getLongVolatile(
offset: jlong,
) -> jlong {
let thread = &*get_thread(env);
if obj.is_null() && offset == THREAD_NEXT_ID_SENTINEL {
return thread.vm.NEXT_ID.load(Ordering::SeqCst) as jlong;
}
let reference = thread.gc.read().get(obj as u32);
match reference {

View File

@ -1,5 +1,5 @@
use crate::get_thread;
use jni::sys::{jint, jobject, JNIEnv};
use jni::sys::{JNIEnv, jint, jlong, jobject};
#[unsafe(no_mangle)]
pub unsafe extern "system" fn Java_java_lang_Object_hashCode(
@ -27,3 +27,32 @@ pub unsafe extern "system" fn Java_java_lang_Object_clone(
}
}
}
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_java_lang_Object_notifyAll(env: *mut JNIEnv, this: jobject) {
// Wake all threads waiting on this object's monitor
let thread = &*get_thread(env);
thread.gc.read().notify_all(this as u32);
}
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_java_lang_Object_notify(env: *mut JNIEnv, this: jobject) {
// Wake one thread waiting on this object's monitor
let thread = &*get_thread(env);
thread.gc.read().notify_one(this as u32);
}
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_java_lang_Object_wait(
env: *mut JNIEnv,
this: jobject,
timeout_millis: jlong,
) {
let thread = &*get_thread(env);
let thread_id = thread.id;
thread
.gc
.read()
.wait(thread_id, this as u32, timeout_millis)
.unwrap();
}

View File

@ -1,6 +1,6 @@
use crate::{get_thread, resolve_array, resolve_object};
use jni::objects::JClass;
use jni::sys::{jclass, jint, jlong, jobject, JNIEnv};
use jni::sys::{JNIEnv, jclass, jint, jlong, jobject};
use log::warn;
use std::time::{SystemTime, UNIX_EPOCH};
@ -80,3 +80,11 @@ pub extern "system" fn Java_java_lang_System_nanoTime<'local>(
.unwrap()
.as_nanos() as jlong
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_java_lang_System_registerNatives<'local>(
mut env: jni::JNIEnv<'local>,
jclass: JClass<'local>,
) {
// No-op: native methods are handled by roast
}

View File

@ -0,0 +1,50 @@
use jni::JNIEnv;
use jni::objects::{JClass, JObject};
use jni::sys::jobjectArray;
#[unsafe(no_mangle)]
pub extern "system" fn Java_jdk_internal_util_SystemProps_00024Raw_vmProperties<'local>(
mut env: JNIEnv<'local>,
_class: JClass<'local>,
) -> jobjectArray {
let props = [
"java.vm.specification.name",
"Java Virtual Machine Specification",
"java.vm.specification.vendor",
"Oracle Corporation",
"java.vm.specification.version",
"25",
"java.vm.name",
"RoastVM",
"java.vm.vendor",
"infernap12",
"java.vm.version",
"0.1.0",
"java.vm.info",
"interpreted mode",
"jdk.debug",
"release",
"java.home",
"C:\\Program Files\\Java\\jdk-25", // adjust
"java.library.path",
"",
"sun.boot.library.path",
"",
"java.class.path",
".",
];
let string_class = env.find_class("java/lang/String").unwrap();
// +1 for null terminator
let arr = env
.new_object_array((props.len() + 1) as i32, string_class, JObject::null())
.unwrap();
for (i, s) in props.iter().enumerate() {
let jstr = env.new_string(s).unwrap();
env.set_object_array_element(&arr, i as i32, jstr).unwrap();
}
// Last element stays null (terminator)
arr.into_raw()
}

View File

@ -1,6 +1,8 @@
use crate::get_thread;
use jni::sys::{JNIEnv, jclass, jint, jlong, jobject};
use std::sync::atomic::Ordering;
use roast_vm_core::class_file::MethodRef;
use roast_vm_core::value::Value;
use roast_vm_core::{MethodDescriptor, VmThread};
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_java_lang_Thread_currentThread(
@ -12,11 +14,65 @@ unsafe extern "system" fn Java_java_lang_Thread_currentThread(
*thread_id_obj as jobject
}
pub const THREAD_NEXT_ID_SENTINEL: jlong = 0xFFFFFFFF_FFFFFFFFu64 as i64;
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_java_lang_Thread_getNextThreadIdOffset(
env: *mut JNIEnv,
_env: *mut JNIEnv,
_cls: jclass,
) -> jlong {
let thread = &*get_thread(env);
thread.vm.NEXT_ID.fetch_add(1, Ordering::Relaxed) as jlong
THREAD_NEXT_ID_SENTINEL
}
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_java_lang_Thread_setPriority0(
env: *mut JNIEnv,
_this: jobject,
priority: jint,
) {
// Get the current thread and update its priority
// let thread = &*get_thread(env);
// Store priority in thread state (implementation depends on thread structure)
// For now, this is a no-op placeholder as the actual thread priority field
// would need to be added to the thread structure
}
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_java_lang_Thread_start0(env: *mut JNIEnv, this: jobject) {
let parent_thread = &*get_thread(env);
let vm = parent_thread.vm.clone();
let thread_obj_id = this as u32;
std::thread::spawn(move || {
// Create new VmThread
let new_thread = VmThread::new(vm.clone(), None);
let thread_id = new_thread.id;
// Set mirror to the java.lang.Thread object passed in
new_thread.mirror.set(thread_obj_id).unwrap();
// Register with VM
vm.threads.insert(thread_id, new_thread.clone());
// Set as current thread for this OS thread
VmThread::set_current(thread_id);
// Call this.run()
let thread_class = new_thread.get_class("java/lang/Thread").unwrap();
let run_ref = MethodRef {
class: "java/lang/Thread".to_string(),
name: "run".to_string(),
desc: MethodDescriptor::void(),
};
// Get the Thread object as a Value
let thread_value = Value::Reference(Some(new_thread.gc.read().get(thread_obj_id)));
if let Err(e) = new_thread.invoke(run_ref, vec![thread_value]) {
eprintln!("Thread {} died with: {:?}", thread_id.0, e);
}
// Cleanup: remove from VM thread registry
vm.threads.remove(&thread_id);
});
}