#![expect(
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
reason = "JIT FFI functions intentionally cast between JVM i32 indices and Rust usize"
)]
#![allow(unsafe_code)]
use crate::assignable::Assignable;
use crate::Error::{InternalError, JavaError};
use crate::JavaError::{
ArrayIndexOutOfBoundsException, ArrayStoreException, ClassCastException,
NegativeArraySizeException, NullPointerException,
};
use crate::instruction::convert_error_to_throwable;
use crate::{Result, Thread, VM};
use portable_atomic::AtomicI64;
use ristretto_classfile::BaseType;
use ristretto_classloader::{Class, Object, Reference, Value};
use ristretto_gc::sync::{Mutex, RwLock};
use ristretto_gc::{GarbageCollector, Gc};
use std::future::Future;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::runtime::Handle;
struct ResolvedField {
field_class: Arc<Class>,
field_name: Arc<str>,
descriptor: Arc<str>,
initialized: std::sync::atomic::AtomicBool,
}
#[derive(Clone)]
struct CachedResolutionError {
message: Arc<str>,
}
impl CachedResolutionError {
fn from_error(error: &crate::Error) -> Self {
Self {
message: Arc::from(error.to_string()),
}
}
fn to_error(&self) -> crate::Error {
InternalError(format!("cached resolution failure: {}", self.message))
}
}
#[repr(C)]
pub(crate) struct RuntimeContext {
gc: *const u8,
gc_owner: Arc<GarbageCollector>,
vm: Arc<VM>,
thread: Arc<Thread>,
class: Arc<Class>,
throwable_class: Arc<Class>,
sentinel_throwable: i64,
sentinel_throwable_root: AtomicUsize,
field_cache: RwLock<
ahash::AHashMap<u16, std::result::Result<Arc<ResolvedField>, CachedResolutionError>>,
>,
class_cache:
RwLock<ahash::AHashMap<u16, std::result::Result<Arc<Class>, CachedResolutionError>>>,
pending_exception: AtomicI64,
pending_exception_root: AtomicUsize,
transient_roots: Mutex<Vec<usize>>,
current_bci: std::sync::atomic::AtomicI32,
}
unsafe impl Send for RuntimeContext {}
unsafe impl Sync for RuntimeContext {}
impl RuntimeContext {
pub fn new(
gc: &Arc<GarbageCollector>,
vm: &Arc<VM>,
thread: &Arc<Thread>,
class: &Arc<Class>,
) -> Result<Self> {
let throwable_class = run_async(async { thread.class("java/lang/Throwable").await })?;
let sentinel_class =
run_async(async { thread.class("java/lang/VirtualMachineError").await })?;
let sentinel_object = Object::new(sentinel_class)?;
let sentinel_guard = Gc::new(gc, RwLock::new(Reference::Object(sentinel_object)));
let sentinel_gc = sentinel_guard.clone_gc();
let sentinel_ptr = sentinel_gc.as_ptr_i64();
let sentinel_root = gc.add_root(&sentinel_gc);
drop(sentinel_guard);
Ok(Self {
gc: std::ptr::from_ref::<GarbageCollector>(gc).cast::<u8>(),
gc_owner: Arc::clone(gc),
vm: Arc::clone(vm),
thread: Arc::clone(thread),
class: Arc::clone(class),
throwable_class,
sentinel_throwable: sentinel_ptr,
sentinel_throwable_root: AtomicUsize::new(sentinel_root),
field_cache: RwLock::new(ahash::AHashMap::new()),
class_cache: RwLock::new(ahash::AHashMap::new()),
pending_exception: AtomicI64::new(0),
pending_exception_root: AtomicUsize::new(0),
transient_roots: Mutex::new(Vec::new()),
current_bci: std::sync::atomic::AtomicI32::new(-1),
})
}
pub fn as_ptr(&self) -> *const u8 {
std::ptr::from_ref::<Self>(self).cast::<u8>()
}
fn garbage_collector(&self) -> &Arc<GarbageCollector> {
&self.gc_owner
}
pub fn pending_exception(&self) -> i64 {
self.pending_exception.load(Ordering::Acquire)
}
fn set_pending_exception(&self, gc_ptr: i64) {
let new_root_id = if gc_ptr != 0 {
match Gc::<RwLock<Reference>>::from_raw_i64(gc_ptr) {
Ok(gc_ref) => gc_from_context(self.as_ptr()).add_root(&gc_ref),
Err(error) => {
tracing::error!("invalid pending exception pointer: {error}");
return;
}
}
} else {
0
};
let prior_root = self
.pending_exception_root
.swap(new_root_id, Ordering::AcqRel);
self.pending_exception.store(gc_ptr, Ordering::Release);
if prior_root != 0 {
gc_from_context(self.as_ptr()).remove_root_by_id(prior_root);
}
}
pub fn take_pending_exception(&self) -> i64 {
let value = self.pending_exception.swap(0, Ordering::AcqRel);
let root_id = self.pending_exception_root.swap(0, Ordering::AcqRel);
if root_id != 0 {
gc_from_context(self.as_ptr()).remove_root_by_id(root_id);
}
value
}
pub fn take_pending_exception_into<R>(&self, consumer: impl FnOnce(i64) -> R) -> R {
let value = self.pending_exception.swap(0, Ordering::AcqRel);
let root_id = self.pending_exception_root.swap(0, Ordering::AcqRel);
let result = consumer(value);
if root_id != 0 {
gc_from_context(self.as_ptr()).remove_root_by_id(root_id);
}
result
}
fn add_transient_root(&self, gc_ref: &Gc<RwLock<Reference>>) {
let root_id = gc_from_context(self.as_ptr()).add_root(gc_ref);
self.transient_roots.lock().push(root_id);
}
fn add_transient_root_ptr(&self, gc_ptr: i64) -> Result<()> {
if gc_ptr == 0 {
return Ok(());
}
let gc_ref = Gc::<RwLock<Reference>>::from_raw_i64(gc_ptr)?;
self.add_transient_root(&gc_ref);
Ok(())
}
}
impl Drop for RuntimeContext {
fn drop(&mut self) {
let transient_roots = std::mem::take(self.transient_roots.get_mut());
for root_id in transient_roots {
if root_id != 0 {
gc_from_context(self.as_ptr()).remove_root_by_id(root_id);
}
}
let root_id = self.pending_exception_root.load(Ordering::Acquire);
if root_id != 0 {
gc_from_context(self.as_ptr()).remove_root_by_id(root_id);
}
let sentinel_root = self.sentinel_throwable_root.load(Ordering::Acquire);
if sentinel_root != 0 {
gc_from_context(self.as_ptr()).remove_root_by_id(sentinel_root);
}
}
}
pub(crate) fn symbols() -> [(&'static str, *const u8); 55] {
[
("jit_new_bool_array", jit_new_bool_array as *const u8),
("jit_new_byte_array", jit_new_byte_array as *const u8),
("jit_new_char_array", jit_new_char_array as *const u8),
("jit_new_short_array", jit_new_short_array as *const u8),
("jit_new_int_array", jit_new_int_array as *const u8),
("jit_new_long_array", jit_new_long_array as *const u8),
("jit_new_float_array", jit_new_float_array as *const u8),
("jit_new_double_array", jit_new_double_array as *const u8),
("jit_arraylength", jit_arraylength as *const u8),
("jit_baload", jit_baload as *const u8),
("jit_bastore", jit_bastore as *const u8),
("jit_caload", jit_caload as *const u8),
("jit_castore", jit_castore as *const u8),
("jit_saload", jit_saload as *const u8),
("jit_sastore", jit_sastore as *const u8),
("jit_iaload", jit_iaload as *const u8),
("jit_iastore", jit_iastore as *const u8),
("jit_laload", jit_laload as *const u8),
("jit_lastore", jit_lastore as *const u8),
("jit_faload", jit_faload as *const u8),
("jit_fastore", jit_fastore as *const u8),
("jit_daload", jit_daload as *const u8),
("jit_dastore", jit_dastore as *const u8),
("jit_aaload", jit_aaload as *const u8),
("jit_aastore", jit_aastore as *const u8),
("jit_new", jit_new as *const u8),
("jit_anewarray", jit_anewarray as *const u8),
("jit_multianewarray", jit_multianewarray as *const u8),
("jit_getstatic_int", jit_getstatic_int as *const u8),
("jit_getstatic_long", jit_getstatic_long as *const u8),
("jit_getstatic_float", jit_getstatic_float as *const u8),
("jit_getstatic_double", jit_getstatic_double as *const u8),
("jit_getstatic_object", jit_getstatic_object as *const u8),
("jit_putstatic_int", jit_putstatic_int as *const u8),
("jit_putstatic_long", jit_putstatic_long as *const u8),
("jit_putstatic_float", jit_putstatic_float as *const u8),
("jit_putstatic_double", jit_putstatic_double as *const u8),
("jit_putstatic_object", jit_putstatic_object as *const u8),
("jit_getfield_int", jit_getfield_int as *const u8),
("jit_getfield_long", jit_getfield_long as *const u8),
("jit_getfield_float", jit_getfield_float as *const u8),
("jit_getfield_double", jit_getfield_double as *const u8),
("jit_getfield_object", jit_getfield_object as *const u8),
("jit_putfield_int", jit_putfield_int as *const u8),
("jit_putfield_long", jit_putfield_long as *const u8),
("jit_putfield_float", jit_putfield_float as *const u8),
("jit_putfield_double", jit_putfield_double as *const u8),
("jit_putfield_object", jit_putfield_object as *const u8),
("jit_checkcast", jit_checkcast as *const u8),
("jit_instanceof", jit_instanceof as *const u8),
("jit_athrow", jit_athrow as *const u8),
("jit_pending_exception", jit_pending_exception as *const u8),
(
"jit_take_pending_exception",
jit_take_pending_exception as *const u8,
),
("jit_exception_matches", jit_exception_matches as *const u8),
("jit_throw_npe", jit_throw_npe as *const u8),
]
}
fn gc_from_context(context: *const u8) -> &'static GarbageCollector {
GarbageCollector::from_context_struct_ptr(context)
}
fn alloc_reference(ctx: &RuntimeContext, reference: Reference) -> i64 {
let guard = Gc::new(ctx.garbage_collector(), RwLock::new(reference));
let gc_ref = guard.clone_gc();
ctx.add_transient_root(&gc_ref);
gc_ref.as_ptr_i64()
}
fn gc_ref_from_ptr(ptr: i64) -> Result<Gc<RwLock<Reference>>> {
Ok(Gc::from_raw_i64(ptr)?)
}
fn gc_ref_from_ptr_or_pending(ctx: &RuntimeContext, ptr: i64) -> Option<Gc<RwLock<Reference>>> {
match gc_ref_from_ptr(ptr) {
Ok(gc_ref) => Some(gc_ref),
Err(error) => {
store_pending_error(ctx, error);
None
}
}
}
fn array_index_or_pending(ctx: &RuntimeContext, index: i32, length: usize) -> Option<usize> {
let Ok(index_usize) = usize::try_from(index) else {
store_pending_error(
ctx,
JavaError(ArrayIndexOutOfBoundsException { index, length }),
);
return None;
};
if index_usize >= length {
store_pending_error(
ctx,
JavaError(ArrayIndexOutOfBoundsException { index, length }),
);
return None;
}
Some(index_usize)
}
fn array_element_or_pending<'a, T>(
ctx: &RuntimeContext,
values: &'a [T],
index: i32,
) -> Option<&'a T> {
let index = array_index_or_pending(ctx, index, values.len())?;
values.get(index)
}
fn array_element_mut_or_pending<'a, T>(
ctx: &RuntimeContext,
values: &'a mut [T],
index: i32,
) -> Option<&'a mut T> {
let index = array_index_or_pending(ctx, index, values.len())?;
values.get_mut(index)
}
fn guard_helper<R, F>(name: &str, f: F) -> R
where
F: FnOnce() -> R + std::panic::UnwindSafe,
{
match std::panic::catch_unwind(f) {
Ok(value) => value,
Err(payload) => {
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"<non-string panic payload>".to_string()
};
eprintln!("ristretto_vm jit helper `{name}` panicked: {msg}");
std::process::abort();
}
}
}
macro_rules! guarded {
($name:literal, $body:expr) => {
$crate::jit_runtime_helpers::guard_helper($name, ::std::panic::AssertUnwindSafe(|| $body))
};
}
extern "C" fn jit_new_bool_array(context: *const u8, count: i32) -> i64 {
let ctx = ctx_from_ptr(context);
let count = count.max(0) as usize;
alloc_reference(
ctx,
Reference::BooleanArray(vec![0i8; count].into_boxed_slice()),
)
}
extern "C" fn jit_new_byte_array(context: *const u8, count: i32) -> i64 {
let ctx = ctx_from_ptr(context);
let count = count.max(0) as usize;
alloc_reference(
ctx,
Reference::ByteArray(vec![0i8; count].into_boxed_slice()),
)
}
extern "C" fn jit_new_char_array(context: *const u8, count: i32) -> i64 {
let ctx = ctx_from_ptr(context);
let count = count.max(0) as usize;
alloc_reference(
ctx,
Reference::CharArray(vec![0u16; count].into_boxed_slice()),
)
}
extern "C" fn jit_new_short_array(context: *const u8, count: i32) -> i64 {
let ctx = ctx_from_ptr(context);
let count = count.max(0) as usize;
alloc_reference(
ctx,
Reference::ShortArray(vec![0i16; count].into_boxed_slice()),
)
}
extern "C" fn jit_new_int_array(context: *const u8, count: i32) -> i64 {
let ctx = ctx_from_ptr(context);
let count = count.max(0) as usize;
alloc_reference(
ctx,
Reference::IntArray(vec![0i32; count].into_boxed_slice()),
)
}
extern "C" fn jit_new_long_array(context: *const u8, count: i32) -> i64 {
let ctx = ctx_from_ptr(context);
let count = count.max(0) as usize;
alloc_reference(
ctx,
Reference::LongArray(vec![0i64; count].into_boxed_slice()),
)
}
extern "C" fn jit_new_float_array(context: *const u8, count: i32) -> i64 {
let ctx = ctx_from_ptr(context);
let count = count.max(0) as usize;
alloc_reference(
ctx,
Reference::FloatArray(vec![0.0f32; count].into_boxed_slice()),
)
}
extern "C" fn jit_new_double_array(context: *const u8, count: i32) -> i64 {
let ctx = ctx_from_ptr(context);
let count = count.max(0) as usize;
alloc_reference(
ctx,
Reference::DoubleArray(vec![0.0f64; count].into_boxed_slice()),
)
}
extern "C" fn jit_arraylength(context: *const u8, bci: i32, array_ptr: i64) -> i32 {
guard_helper(
"jit_arraylength",
std::panic::AssertUnwindSafe(|| jit_arraylength_inner(context, bci, array_ptr)),
)
}
fn jit_arraylength_inner(context: *const u8, bci: i32, array_ptr: i64) -> i32 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::BooleanArray(a) | Reference::ByteArray(a) => a.len() as i32,
Reference::CharArray(a) => a.len() as i32,
Reference::ShortArray(a) => a.len() as i32,
Reference::IntArray(a) => a.len() as i32,
Reference::LongArray(a) => a.len() as i32,
Reference::FloatArray(a) => a.len() as i32,
Reference::DoubleArray(a) => a.len() as i32,
Reference::Array(a) => a.elements.len() as i32,
Reference::Object(_) => {
store_pending_error(
ctx,
InternalError(format!(
"arraylength called on non-array reference (ptr={:#x})",
array_ptr as u64
)),
);
0
}
}
}
extern "C" fn jit_baload(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i32 {
guard_helper(
"jit_baload",
std::panic::AssertUnwindSafe(|| jit_baload_inner(context, bci, array_ptr, index)),
)
}
fn jit_baload_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i32 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::ByteArray(a) | Reference::BooleanArray(a) => {
array_element_or_pending(ctx, a, index)
.copied()
.map(i32::from)
.unwrap_or_default()
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"baload: expected byte/boolean array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
0
}
}
}
extern "C" fn jit_bastore(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i32) {
guard_helper(
"jit_bastore",
std::panic::AssertUnwindSafe(|| jit_bastore_inner(context, bci, array_ptr, index, value)),
);
}
fn jit_bastore_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i32) {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let mut reference = gc_ref.write();
match &mut *reference {
Reference::ByteArray(a) | Reference::BooleanArray(a) => {
if let Some(element) = array_element_mut_or_pending(ctx, a, index) {
*element = value as i8;
}
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"bastore: expected byte/boolean array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
}
}
}
extern "C" fn jit_caload(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i32 {
guard_helper(
"jit_caload",
std::panic::AssertUnwindSafe(|| jit_caload_inner(context, bci, array_ptr, index)),
)
}
fn jit_caload_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i32 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::CharArray(a) => array_element_or_pending(ctx, a, index)
.copied()
.map(i32::from)
.unwrap_or_default(),
other => {
store_pending_error(
ctx,
InternalError(format!(
"caload: expected char array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
0
}
}
}
extern "C" fn jit_castore(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i32) {
guard_helper(
"jit_castore",
std::panic::AssertUnwindSafe(|| jit_castore_inner(context, bci, array_ptr, index, value)),
);
}
fn jit_castore_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i32) {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let mut reference = gc_ref.write();
match &mut *reference {
Reference::CharArray(a) => {
if let Some(element) = array_element_mut_or_pending(ctx, a, index) {
*element = value as u16;
}
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"castore: expected char array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
}
}
}
extern "C" fn jit_saload(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i32 {
guard_helper(
"jit_saload",
std::panic::AssertUnwindSafe(|| jit_saload_inner(context, bci, array_ptr, index)),
)
}
fn jit_saload_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i32 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::ShortArray(a) => array_element_or_pending(ctx, a, index)
.copied()
.map(i32::from)
.unwrap_or_default(),
other => {
store_pending_error(
ctx,
InternalError(format!(
"saload: expected short array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
0
}
}
}
extern "C" fn jit_sastore(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i32) {
guard_helper(
"jit_sastore",
std::panic::AssertUnwindSafe(|| jit_sastore_inner(context, bci, array_ptr, index, value)),
);
}
fn jit_sastore_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i32) {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let mut reference = gc_ref.write();
match &mut *reference {
Reference::ShortArray(a) => {
if let Some(element) = array_element_mut_or_pending(ctx, a, index) {
*element = value as i16;
}
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"sastore: expected short array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
}
}
}
extern "C" fn jit_iaload(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i32 {
guard_helper(
"jit_iaload",
std::panic::AssertUnwindSafe(|| jit_iaload_inner(context, bci, array_ptr, index)),
)
}
fn jit_iaload_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i32 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::IntArray(a) => array_element_or_pending(ctx, a, index)
.copied()
.unwrap_or_default(),
other => {
store_pending_error(
ctx,
InternalError(format!(
"iaload: expected int array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
0
}
}
}
extern "C" fn jit_iastore(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i32) {
guard_helper(
"jit_iastore",
std::panic::AssertUnwindSafe(|| jit_iastore_inner(context, bci, array_ptr, index, value)),
);
}
fn jit_iastore_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i32) {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let mut reference = gc_ref.write();
match &mut *reference {
Reference::IntArray(a) => {
if let Some(element) = array_element_mut_or_pending(ctx, a, index) {
*element = value;
}
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"iastore: expected int array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
}
}
}
extern "C" fn jit_laload(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i64 {
guard_helper(
"jit_laload",
std::panic::AssertUnwindSafe(|| jit_laload_inner(context, bci, array_ptr, index)),
)
}
fn jit_laload_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i64 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::LongArray(a) => array_element_or_pending(ctx, a, index)
.copied()
.unwrap_or_default(),
other => {
store_pending_error(
ctx,
InternalError(format!(
"laload: expected long array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
0
}
}
}
extern "C" fn jit_lastore(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i64) {
guard_helper(
"jit_lastore",
std::panic::AssertUnwindSafe(|| jit_lastore_inner(context, bci, array_ptr, index, value)),
);
}
fn jit_lastore_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i64) {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let mut reference = gc_ref.write();
match &mut *reference {
Reference::LongArray(a) => {
if let Some(element) = array_element_mut_or_pending(ctx, a, index) {
*element = value;
}
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"lastore: expected long array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
}
}
}
extern "C" fn jit_faload(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> f32 {
guard_helper(
"jit_faload",
std::panic::AssertUnwindSafe(|| jit_faload_inner(context, bci, array_ptr, index)),
)
}
fn jit_faload_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> f32 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::FloatArray(a) => array_element_or_pending(ctx, a, index)
.copied()
.unwrap_or_default(),
other => {
store_pending_error(
ctx,
InternalError(format!(
"faload: expected float array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
0.0
}
}
}
extern "C" fn jit_fastore(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: f32) {
guard_helper(
"jit_fastore",
std::panic::AssertUnwindSafe(|| jit_fastore_inner(context, bci, array_ptr, index, value)),
);
}
fn jit_fastore_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: f32) {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let mut reference = gc_ref.write();
match &mut *reference {
Reference::FloatArray(a) => {
if let Some(element) = array_element_mut_or_pending(ctx, a, index) {
*element = value;
}
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"fastore: expected float array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
}
}
}
extern "C" fn jit_daload(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> f64 {
guard_helper(
"jit_daload",
std::panic::AssertUnwindSafe(|| jit_daload_inner(context, bci, array_ptr, index)),
)
}
fn jit_daload_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> f64 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::DoubleArray(a) => array_element_or_pending(ctx, a, index)
.copied()
.unwrap_or_default(),
other => {
store_pending_error(
ctx,
InternalError(format!(
"daload: expected double array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
0.0
}
}
}
extern "C" fn jit_dastore(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: f64) {
guard_helper(
"jit_dastore",
std::panic::AssertUnwindSafe(|| jit_dastore_inner(context, bci, array_ptr, index, value)),
);
}
fn jit_dastore_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: f64) {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let mut reference = gc_ref.write();
match &mut *reference {
Reference::DoubleArray(a) => {
if let Some(element) = array_element_mut_or_pending(ctx, a, index) {
*element = value;
}
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"dastore: expected double array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
}
}
}
extern "C" fn jit_aaload(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i64 {
guard_helper(
"jit_aaload",
std::panic::AssertUnwindSafe(|| jit_aaload_inner(context, bci, array_ptr, index)),
)
}
fn jit_aaload_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32) -> i64 {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
let reference = gc_ref.read();
match &*reference {
Reference::Array(obj_array) => {
let Some(element) = array_element_or_pending(ctx, &obj_array.elements, index) else {
return 0;
};
match element {
Value::Object(None) => 0i64,
Value::Object(Some(gc)) => {
ctx.add_transient_root(gc);
gc.as_ptr_i64()
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"aaload: element is not an object reference (got value variant {:?}, ptr={:#x}, index={})",
std::mem::discriminant(other),
array_ptr as u64,
index
)),
);
0
}
}
}
other => {
store_pending_error(
ctx,
InternalError(format!(
"aaload: expected reference array (got reference variant {:?}, ptr={:#x})",
std::mem::discriminant(other),
array_ptr as u64
)),
);
0
}
}
}
extern "C" fn jit_aastore(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i64) {
guard_helper(
"jit_aastore",
std::panic::AssertUnwindSafe(|| jit_aastore_inner(context, bci, array_ptr, index, value)),
);
}
fn jit_aastore_inner(context: *const u8, bci: i32, array_ptr: i64, index: i32, value: i64) {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let Some(gc_ref) = gc_ref_from_ptr_or_pending(ctx, array_ptr) else {
return Default::default();
};
if value != 0 {
let component_name = {
let reference = gc_ref.read();
match &*reference {
Reference::Array(obj_array) => obj_array.class.array_component_type().to_string(),
_ => String::new(),
}
};
let needs_check = !component_name.is_empty();
if needs_check {
let Some(value_gc) = gc_ref_from_ptr_or_pending(ctx, value) else {
return;
};
let value_class_name = {
let val_ref = value_gc.read();
val_ref.class_name().ok()
};
if let Some(value_class_name) = value_class_name {
let thread = Arc::clone(&ctx.thread);
let target_name = strip_object_descriptor(&component_name);
let assignable = run_async(async {
let component_class = thread.class(&target_name).await?;
let value_class = thread.class(&value_class_name).await?;
component_class
.is_assignable_from(&thread, &value_class)
.await
});
match assignable {
Ok(true) => {}
Ok(false) => {
store_pending_error(ctx, JavaError(ArrayStoreException(value_class_name)));
return;
}
Err(error) => {
store_pending_error(ctx, error);
return;
}
}
}
}
}
let mut reference = gc_ref.write();
match &mut *reference {
Reference::Array(obj_array) => {
let Some(element) = array_element_mut_or_pending(ctx, &mut obj_array.elements, index)
else {
return;
};
if value == 0 {
*element = Value::Object(None);
} else {
let Some(gc) = gc_ref_from_ptr_or_pending(ctx, value) else {
return;
};
*element = Value::Object(Some(gc));
}
}
_ => store_pending_error(
ctx,
JavaError(ArrayStoreException(
"aastore target is not a reference array".to_string(),
)),
),
}
}
fn strip_object_descriptor(name: &str) -> String {
if let Some(stripped) = name.strip_prefix('L')
&& let Some(stripped) = stripped.strip_suffix(';')
{
return stripped.to_string();
}
name.to_string()
}
#[expect(
clippy::cast_ptr_alignment,
reason = "pointer originates from &RuntimeContext"
)]
fn ctx_from_ptr<'a>(context: *const u8) -> &'a RuntimeContext {
assert!(!context.is_null(), "runtime context pointer is null");
unsafe { &*context.cast::<RuntimeContext>() }
}
fn class_from_ctx(ctx: &RuntimeContext) -> &Class {
&ctx.class
}
fn thread_from_ctx(ctx: &RuntimeContext) -> &Thread {
&ctx.thread
}
#[cfg(all(
not(target_family = "wasm"),
target_endian = "little",
not(target_os = "dragonfly"),
not(any(target_arch = "mips", target_arch = "mips64"))
))]
fn run_async<F, T>(future: F) -> T
where
F: Future<Output = T> + Send,
T: Send,
{
debug_assert!(
matches!(
Handle::try_current().map(|h| h.runtime_flavor()),
Ok(tokio::runtime::RuntimeFlavor::MultiThread)
),
"JIT helpers require a multi-thread tokio runtime; VM::create_compiler should have disabled the JIT"
);
tokio::task::block_in_place(|| Handle::current().block_on(future))
}
#[cfg(any(
target_family = "wasm",
target_endian = "big",
target_os = "dragonfly",
target_arch = "mips",
target_arch = "mips64"
))]
fn run_async<F, T>(future: F) -> T
where
F: Future<Output = T>,
{
Handle::current().block_on(future)
}
fn store_pending_error(ctx: &RuntimeContext, error: crate::Error) {
let bci = ctx.current_bci.load(Ordering::Relaxed);
set_top_frame_pc(ctx, bci);
let thread = thread_from_ctx(ctx);
let throwable = run_async(async { convert_error_to_throwable(thread, error).await });
match throwable {
Ok(Value::Object(Some(gc_ref))) => {
ctx.set_pending_exception(gc_ref.as_ptr_i64());
}
Ok(other) => {
tracing::error!(
"ristretto_vm jit: convert_error_to_throwable produced non-object value: {other:?}; \
falling back to sentinel VirtualMachineError"
);
ctx.set_pending_exception(ctx.sentinel_throwable);
}
Err(error) => {
tracing::error!(
"ristretto_vm jit: convert_error_to_throwable failed: {error}; \
falling back to sentinel VirtualMachineError"
);
ctx.set_pending_exception(ctx.sentinel_throwable);
}
}
}
fn set_top_frame_pc(ctx: &RuntimeContext, bci: i32) {
if bci < 0 {
return;
}
let thread = thread_from_ctx(ctx);
run_async(async {
if let Ok(frame) = thread.current_frame() {
frame.set_program_counter(bci as usize);
}
});
}
fn resolve_class_ref(ctx: &RuntimeContext, cp_index: u16) -> Result<Arc<Class>> {
if let Some(entry) = ctx.class_cache.read().get(&cp_index).cloned() {
return entry.map_err(|cached| cached.to_error());
}
let class = class_from_ctx(ctx);
let constant_pool = class.constant_pool();
let class_name = constant_pool.try_get_class(cp_index)?;
let thread = thread_from_ctx(ctx);
match run_async(async { thread.class_java_str(class_name).await }) {
Ok(resolved) => {
ctx.class_cache
.write()
.insert(cp_index, Ok(Arc::clone(&resolved)));
Ok(resolved)
}
Err(error) => {
let cached = CachedResolutionError::from_error(&error);
ctx.class_cache
.write()
.insert(cp_index, Err(cached.clone()));
Err(error)
}
}
}
fn resolve_class_ref_no_init(ctx: &RuntimeContext, cp_index: u16) -> Result<Arc<Class>> {
if let Some(entry) = ctx.class_cache.read().get(&cp_index).cloned() {
return entry.map_err(|cached| cached.to_error());
}
let class = class_from_ctx(ctx);
let constant_pool = class.constant_pool();
let class_name = constant_pool.try_get_class(cp_index)?;
let thread = thread_from_ctx(ctx);
match run_async(async { thread.load_and_link_class(class_name).await }) {
Ok(resolved) => {
ctx.class_cache
.write()
.insert(cp_index, Ok(Arc::clone(&resolved)));
Ok(resolved)
}
Err(error) => {
let cached = CachedResolutionError::from_error(&error);
ctx.class_cache
.write()
.insert(cp_index, Err(cached.clone()));
Err(error)
}
}
}
fn resolve_field_ref(ctx: &RuntimeContext, cp_index: u16) -> Result<Arc<ResolvedField>> {
if let Some(entry) = ctx.field_cache.read().get(&cp_index).cloned() {
return entry.map_err(|cached| cached.to_error());
}
let class = class_from_ctx(ctx);
let constant_pool = class.constant_pool();
let result = (|| -> Result<Arc<ResolvedField>> {
let (class_index, name_and_type_index) = constant_pool.try_get_field_ref(cp_index)?;
let field_class_name = constant_pool.try_get_class(*class_index)?;
let (name_index, descriptor_index) =
constant_pool.try_get_name_and_type(*name_and_type_index)?;
let field_name: Arc<str> = Arc::from(constant_pool.try_get_utf8(*name_index)?.to_string());
let descriptor: Arc<str> =
Arc::from(constant_pool.try_get_utf8(*descriptor_index)?.to_string());
let thread = thread_from_ctx(ctx);
let field_class = run_async(async { thread.load_and_link_class(field_class_name).await })?;
Ok(Arc::new(ResolvedField {
field_class,
field_name,
descriptor,
initialized: std::sync::atomic::AtomicBool::new(false),
}))
})();
match result {
Ok(resolved) => {
ctx.field_cache
.write()
.insert(cp_index, Ok(Arc::clone(&resolved)));
Ok(resolved)
}
Err(error) => {
let cached = CachedResolutionError::from_error(&error);
ctx.field_cache
.write()
.insert(cp_index, Err(cached.clone()));
Err(error)
}
}
}
fn resolve_field_ref_static(ctx: &RuntimeContext, cp_index: u16) -> Result<Arc<ResolvedField>> {
let resolved = resolve_field_ref(ctx, cp_index)?;
if !resolved.initialized.load(Ordering::Acquire) {
let thread = thread_from_ctx(ctx);
let class_name = resolved.field_class.name().to_string();
run_async(async { thread.class(&class_name).await })?;
resolved.initialized.store(true, Ordering::Release);
}
Ok(resolved)
}
fn value_to_i32(value: &Value) -> Result<i32> {
match value {
Value::Int(v) => Ok(*v),
other => Err(InternalError(format!(
"expected Value::Int, found {other:?}"
))),
}
}
fn value_to_i64(value: &Value) -> Result<i64> {
match value {
Value::Long(v) => Ok(*v),
other => Err(InternalError(format!(
"expected Value::Long, found {other:?}"
))),
}
}
fn value_to_f32(value: &Value) -> Result<f32> {
match value {
Value::Float(v) => Ok(*v),
other => Err(InternalError(format!(
"expected Value::Float, found {other:?}"
))),
}
}
fn value_to_f64(value: &Value) -> Result<f64> {
match value {
Value::Double(v) => Ok(*v),
other => Err(InternalError(format!(
"expected Value::Double, found {other:?}"
))),
}
}
fn value_to_obj_ptr(ctx: &RuntimeContext, value: &Value) -> Result<i64> {
match value {
Value::Object(Some(gc)) => {
ctx.add_transient_root(gc);
Ok(gc.as_ptr_i64())
}
Value::Object(None) => Ok(0),
other => Err(InternalError(format!(
"expected Value::Object, found {other:?}"
))),
}
}
fn obj_ptr_to_value(ptr: i64) -> Result<Value> {
if ptr == 0 {
Ok(Value::Object(None))
} else {
let gc = Gc::from_raw_i64(ptr)?;
Ok(Value::Object(Some(gc)))
}
}
fn cp_index_from_i32(cp_index: i32, helper_name: &str) -> Result<u16> {
u16::try_from(cp_index).map_err(|_| {
InternalError(format!(
"{helper_name}: cp_index {cp_index} out of range for u16",
))
})
}
extern "C" fn jit_new(context: *const u8, bci: i32, cp_class_index: i32) -> i64 {
guarded!("jit_new", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_class_index = match cp_index_from_i32(cp_class_index, "jit_new") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match jit_new_impl(ctx, cp_class_index) {
Ok(ptr) => ptr,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
fn jit_new_impl(ctx: &RuntimeContext, cp_class_index: u16) -> Result<i64> {
let class = resolve_class_ref(ctx, cp_class_index)?;
let object = Object::new(class)?;
Ok(alloc_reference(ctx, Reference::Object(object)))
}
extern "C" fn jit_anewarray(context: *const u8, bci: i32, cp_class_index: i32, count: i32) -> i64 {
guarded!("jit_anewarray", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_class_index = match cp_index_from_i32(cp_class_index, "jit_anewarray") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match jit_anewarray_impl(ctx, cp_class_index, count) {
Ok(ptr) => ptr,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
fn jit_anewarray_impl(ctx: &RuntimeContext, cp_class_index: u16, count: i32) -> Result<i64> {
let class = class_from_ctx(ctx);
let constant_pool = class.constant_pool();
let class_name = constant_pool.try_get_class(cp_class_index)?;
let class_name_str = class_name.to_str_lossy();
let thread = thread_from_ctx(ctx);
if !class_name_str.starts_with('[') {
run_async(async { thread.class(class_name_str.as_ref()).await })?;
}
let array_class_name = if class_name_str.starts_with('[') {
format!("[{class_name_str}")
} else {
format!("[L{class_name_str};")
};
let array_class = run_async(async { thread.class(array_class_name.as_str()).await })?;
if count < 0 {
return Err(NegativeArraySizeException(count.to_string()).into());
}
let count = usize::try_from(count).map_err(|error| {
InternalError(format!("anewarray: count out of range for usize: {error}"))
})?;
let reference = Reference::try_from((array_class, vec![Value::Object(None); count]))?;
Ok(alloc_reference(ctx, reference))
}
extern "C" fn jit_multianewarray(
context: *const u8,
bci: i32,
cp_class_index: i32,
dims_ptr: *const u8,
dims_len: i32,
) -> i64 {
guarded!("jit_multianewarray", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_class_index = match cp_index_from_i32(cp_class_index, "jit_multianewarray") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match jit_multianewarray_impl(ctx, cp_class_index, dims_ptr, dims_len) {
Ok(ptr) => ptr,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
fn jit_multianewarray_impl(
ctx: &RuntimeContext,
cp_class_index: u16,
dims_ptr: *const u8,
dims_len: i32,
) -> Result<i64> {
if dims_len <= 0 {
return Err(InternalError(
"multianewarray: invalid dimension count".to_string(),
));
}
let class = resolve_class_ref(ctx, cp_class_index)?;
let dims_len_usize = dims_len as usize;
if !class.is_array() || class.array_dimensions() < dims_len_usize {
return Err(InternalError(format!(
"multianewarray: class {} has {} array dimensions but {dims_len_usize} were requested",
class.name(),
class.array_dimensions()
)));
}
#[expect(
clippy::cast_ptr_alignment,
reason = "JIT emits properly aligned i32 array"
)]
let dims_ptr_i32 = dims_ptr.cast::<i32>();
let mut dimension_sizes = Vec::with_capacity(dims_len_usize);
for i in 0..dims_len as isize {
let value = unsafe { *dims_ptr_i32.offset(i) };
if value < 0 {
return Err(NegativeArraySizeException(value.to_string()).into());
}
dimension_sizes.push(value as usize);
}
let thread = thread_from_ctx(ctx);
let array = run_async(async {
build_multianewarray(thread, class.clone(), &dimension_sizes, 0).await
})?;
match array {
Value::Object(Some(gc)) => {
ctx.add_transient_root(&gc);
Ok(gc.as_ptr_i64())
}
Value::Object(None) => Ok(0),
other => Err(InternalError(format!(
"multianewarray: expected object value, got {other:?}"
))),
}
}
#[cfg(not(target_family = "wasm"))]
type MultianewarrayFuture<'a> = std::pin::Pin<Box<dyn Future<Output = Result<Value>> + Send + 'a>>;
#[cfg(target_family = "wasm")]
type MultianewarrayFuture<'a> = std::pin::Pin<Box<dyn Future<Output = Result<Value>> + 'a>>;
fn build_multianewarray<'a>(
thread: &'a Thread,
class: Arc<Class>,
dimension_sizes: &'a [usize],
depth: usize,
) -> MultianewarrayFuture<'a> {
Box::pin(async move {
let current_size = *dimension_sizes.get(depth).ok_or_else(|| {
InternalError(format!("multianewarray: invalid dimension depth {depth}"))
})?;
let is_last_dim = depth.checked_add(1) == Some(dimension_sizes.len());
let vm = thread.vm()?;
let collector = vm.garbage_collector();
if is_last_dim {
let component = class.array_component_type();
if component.len() == 1 && !component.starts_with('[') {
let component_char = component.chars().next().ok_or_else(|| {
InternalError("multianewarray: empty component type".to_string())
})?;
let base_type = BaseType::parse(component_char)?;
let array = match base_type {
BaseType::Char => {
Value::new_object(collector, Reference::from(vec![0 as char; current_size]))
}
BaseType::Float => {
Value::new_object(collector, Reference::from(vec![0.0f32; current_size]))
}
BaseType::Double => {
Value::new_object(collector, Reference::from(vec![0.0f64; current_size]))
}
BaseType::Boolean => {
Value::new_object(collector, Reference::from(vec![false; current_size]))
}
BaseType::Byte => {
Value::new_object(collector, Reference::from(vec![0i8; current_size]))
}
BaseType::Short => {
Value::new_object(collector, Reference::from(vec![0i16; current_size]))
}
BaseType::Int => {
Value::new_object(collector, Reference::from(vec![0i32; current_size]))
}
BaseType::Long => {
Value::new_object(collector, Reference::from(vec![0i64; current_size]))
}
};
Ok(array)
} else {
let reference =
Reference::try_from((class, vec![Value::Object(None); current_size]))?;
Ok(Value::new_object(collector, reference))
}
} else {
let component_class = component_class_of(thread, &class).await?;
let mut elements = Vec::with_capacity(current_size);
for _ in 0..current_size {
let sub_array = build_multianewarray(
thread,
component_class.clone(),
dimension_sizes,
depth + 1,
)
.await?;
elements.push(sub_array);
}
let reference = Reference::try_from((class, elements))?;
Ok(Value::new_object(collector, reference))
}
})
}
async fn component_class_of(thread: &Thread, class: &Class) -> Result<Arc<Class>> {
let component = class.array_component_type();
if component.is_empty() {
return Err(InternalError(format!(
"multianewarray: class {} is not an array",
class.name()
)));
}
thread.class(component).await
}
fn getstatic(ctx: &RuntimeContext, cp_index: u16) -> Result<Value> {
let resolved = resolve_field_ref_static(ctx, cp_index)?;
Ok(resolved.field_class.static_value(&*resolved.field_name)?)
}
fn putstatic(ctx: &RuntimeContext, cp_index: u16, value: Value) -> Result<()> {
let resolved = resolve_field_ref_static(ctx, cp_index)?;
resolved
.field_class
.set_static_value(&*resolved.field_name, value)?;
Ok(())
}
fn int_to_field_value(descriptor: &str, value: i32) -> Value {
match descriptor.as_bytes().first().copied() {
Some(b'Z') => Value::from((value & 1) != 0),
Some(b'B') => Value::from(value as i8),
Some(b'C') => Value::from(value as u16),
Some(b'S') => Value::from(value as i16),
_ => Value::Int(value),
}
}
extern "C" fn jit_getstatic_int(context: *const u8, bci: i32, cp_index: i32) -> i32 {
guarded!("jit_getstatic_int", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getstatic_int") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getstatic(ctx, cp_index).and_then(|v| value_to_i32(&v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
extern "C" fn jit_getstatic_long(context: *const u8, bci: i32, cp_index: i32) -> i64 {
guarded!("jit_getstatic_long", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getstatic_long") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getstatic(ctx, cp_index).and_then(|v| value_to_i64(&v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
extern "C" fn jit_getstatic_float(context: *const u8, bci: i32, cp_index: i32) -> f32 {
guarded!("jit_getstatic_float", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getstatic_float") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getstatic(ctx, cp_index).and_then(|v| value_to_f32(&v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0.0
}
}
})
}
extern "C" fn jit_getstatic_double(context: *const u8, bci: i32, cp_index: i32) -> f64 {
guarded!("jit_getstatic_double", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getstatic_double") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getstatic(ctx, cp_index).and_then(|v| value_to_f64(&v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0.0
}
}
})
}
extern "C" fn jit_getstatic_object(context: *const u8, bci: i32, cp_index: i32) -> i64 {
guarded!("jit_getstatic_object", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getstatic_object") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getstatic(ctx, cp_index).and_then(|v| value_to_obj_ptr(ctx, &v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
extern "C" fn jit_putstatic_int(context: *const u8, bci: i32, cp_index: i32, value: i32) {
guarded!("jit_putstatic_int", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putstatic_int") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
let resolved = match resolve_field_ref_static(ctx, cp_index) {
Ok(resolved) => resolved,
Err(error) => {
store_pending_error(ctx, error);
return;
}
};
let boxed = int_to_field_value(&resolved.descriptor, value);
if let Err(error) = resolved
.field_class
.set_static_value(&*resolved.field_name, boxed)
{
store_pending_error(ctx, error.into());
}
});
}
extern "C" fn jit_putstatic_long(context: *const u8, bci: i32, cp_index: i32, value: i64) {
guarded!("jit_putstatic_long", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putstatic_long") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if let Err(error) = putstatic(ctx, cp_index, Value::Long(value)) {
store_pending_error(ctx, error);
}
});
}
extern "C" fn jit_putstatic_float(context: *const u8, bci: i32, cp_index: i32, value: f32) {
guarded!("jit_putstatic_float", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putstatic_float") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if let Err(error) = putstatic(ctx, cp_index, Value::Float(value)) {
store_pending_error(ctx, error);
}
});
}
extern "C" fn jit_putstatic_double(context: *const u8, bci: i32, cp_index: i32, value: f64) {
guarded!("jit_putstatic_double", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putstatic_double") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if let Err(error) = putstatic(ctx, cp_index, Value::Double(value)) {
store_pending_error(ctx, error);
}
});
}
extern "C" fn jit_putstatic_object(context: *const u8, bci: i32, cp_index: i32, value: i64) {
guarded!("jit_putstatic_object", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putstatic_object") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
let value = match obj_ptr_to_value(value) {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if let Err(error) = putstatic(ctx, cp_index, value) {
store_pending_error(ctx, error);
}
});
}
fn getfield_helper(ctx: &RuntimeContext, cp_index: u16, object_ptr: i64) -> Result<Value> {
if object_ptr == 0 {
return Err(JavaError(NullPointerException(None)));
}
let resolved = resolve_field_ref(ctx, cp_index)?;
let gc_ref: Gc<RwLock<Reference>> = Gc::from_raw_i64(object_ptr)?;
let reference = gc_ref.read();
match &*reference {
Reference::Object(object) => {
Ok(object.value_in_class(&resolved.field_class, &*resolved.field_name)?)
}
_ => Err(InternalError(
"getfield: expected object reference".to_string(),
)),
}
}
fn putfield_helper(
ctx: &RuntimeContext,
cp_index: u16,
object_ptr: i64,
value: Value,
) -> Result<()> {
if object_ptr == 0 {
return Err(JavaError(NullPointerException(None)));
}
let resolved = resolve_field_ref(ctx, cp_index)?;
let gc_ref: Gc<RwLock<Reference>> = Gc::from_raw_i64(object_ptr)?;
let mut reference = gc_ref.write();
match &mut *reference {
Reference::Object(object) => {
object.set_value_in_class(&resolved.field_class, &*resolved.field_name, value)?;
Ok(())
}
_ => Err(InternalError(
"putfield: expected object reference".to_string(),
)),
}
}
extern "C" fn jit_getfield_int(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
) -> i32 {
guarded!("jit_getfield_int", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getfield_int") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getfield_helper(ctx, cp_index, object_ptr).and_then(|v| value_to_i32(&v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
extern "C" fn jit_getfield_long(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
) -> i64 {
guarded!("jit_getfield_long", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getfield_long") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getfield_helper(ctx, cp_index, object_ptr).and_then(|v| value_to_i64(&v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
extern "C" fn jit_getfield_float(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
) -> f32 {
guarded!("jit_getfield_float", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getfield_float") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getfield_helper(ctx, cp_index, object_ptr).and_then(|v| value_to_f32(&v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0.0
}
}
})
}
extern "C" fn jit_getfield_double(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
) -> f64 {
guarded!("jit_getfield_double", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getfield_double") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getfield_helper(ctx, cp_index, object_ptr).and_then(|v| value_to_f64(&v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0.0
}
}
})
}
extern "C" fn jit_getfield_object(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
) -> i64 {
guarded!("jit_getfield_object", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_getfield_object") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
match getfield_helper(ctx, cp_index, object_ptr).and_then(|v| value_to_obj_ptr(ctx, &v)) {
Ok(v) => v,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
extern "C" fn jit_putfield_int(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
value: i32,
) {
guarded!("jit_putfield_int", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putfield_int") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
let descriptor = match resolve_field_ref(ctx, cp_index) {
Ok(resolved) => resolved.descriptor.clone(),
Err(error) => {
store_pending_error(ctx, error);
return;
}
};
let boxed = int_to_field_value(&descriptor, value);
if let Err(error) = putfield_helper(ctx, cp_index, object_ptr, boxed) {
store_pending_error(ctx, error);
}
});
}
extern "C" fn jit_putfield_long(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
value: i64,
) {
guarded!("jit_putfield_long", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putfield_long") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if let Err(error) = putfield_helper(ctx, cp_index, object_ptr, Value::Long(value)) {
store_pending_error(ctx, error);
}
});
}
extern "C" fn jit_putfield_float(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
value: f32,
) {
guarded!("jit_putfield_float", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putfield_float") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if let Err(error) = putfield_helper(ctx, cp_index, object_ptr, Value::Float(value)) {
store_pending_error(ctx, error);
}
});
}
extern "C" fn jit_putfield_double(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
value: f64,
) {
guarded!("jit_putfield_double", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putfield_double") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if let Err(error) = putfield_helper(ctx, cp_index, object_ptr, Value::Double(value)) {
store_pending_error(ctx, error);
}
});
}
extern "C" fn jit_putfield_object(
context: *const u8,
bci: i32,
cp_index: i32,
object_ptr: i64,
value: i64,
) {
guarded!("jit_putfield_object", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_index = match cp_index_from_i32(cp_index, "jit_putfield_object") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
let value = match obj_ptr_to_value(value) {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if let Err(error) = putfield_helper(ctx, cp_index, object_ptr, value) {
store_pending_error(ctx, error);
}
});
}
fn is_instance_of(
thread: &Thread,
gc_ref: &Gc<RwLock<Reference>>,
class: &Arc<Class>,
) -> Result<bool> {
let (name_opt, resolved_class) = {
let reference = gc_ref.read();
match &*reference {
Reference::BooleanArray(_)
| Reference::ByteArray(_)
| Reference::CharArray(_)
| Reference::ShortArray(_)
| Reference::IntArray(_)
| Reference::LongArray(_)
| Reference::FloatArray(_)
| Reference::DoubleArray(_) => (Some(reference.class_name()?.clone()), None),
Reference::Array(obj_array) => (None, Some(obj_array.class.clone())),
Reference::Object(object) => (None, Some(object.class().clone())),
}
};
let object_class = if let Some(name) = name_opt {
run_async(async { thread.class(&name).await })?
} else if let Some(class) = resolved_class {
class
} else {
return Ok(false);
};
run_async(async { class.is_assignable_from(thread, &object_class).await })
}
extern "C" fn jit_checkcast(
context: *const u8,
bci: i32,
object_ptr: i64,
cp_class_index: i32,
) -> i32 {
guarded!("jit_checkcast", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_class_index = match cp_index_from_i32(cp_class_index, "jit_checkcast") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if object_ptr == 0 {
return 1;
}
match jit_checkcast_impl(ctx, object_ptr, cp_class_index) {
Ok(true) => 1,
Ok(false) => {
let gc_ref = match gc_ref_from_ptr(object_ptr) {
Ok(gc_ref) => gc_ref,
Err(error) => {
store_pending_error(ctx, error);
return 0;
}
};
let (source, target) = {
let reference = gc_ref.read();
let source = reference
.class_name()
.map_or_else(|_| "?".to_string(), |s| s.replace('/', "."));
let class = class_from_ctx(ctx);
let target = class
.constant_pool()
.try_get_class(cp_class_index)
.map_or_else(
|_| "?".to_string(),
|s| s.to_rust_string().replace('/', "."),
);
(source, target)
};
store_pending_error(
ctx,
JavaError(ClassCastException {
source_class_name: source,
target_class_name: target,
}),
);
0
}
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
fn jit_checkcast_impl(ctx: &RuntimeContext, object_ptr: i64, cp_class_index: u16) -> Result<bool> {
let target_class = resolve_class_ref(ctx, cp_class_index)?;
let gc_ref: Gc<RwLock<Reference>> = Gc::from_raw_i64(object_ptr)?;
let thread = thread_from_ctx(ctx);
is_instance_of(thread, &gc_ref, &target_class)
}
extern "C" fn jit_instanceof(
context: *const u8,
bci: i32,
object_ptr: i64,
cp_class_index: i32,
) -> i32 {
guarded!("jit_instanceof", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
let cp_class_index = match cp_index_from_i32(cp_class_index, "jit_instanceof") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
if object_ptr == 0 {
return 0;
}
match jit_checkcast_impl(ctx, object_ptr, cp_class_index) {
Ok(true) => 1,
Ok(false) => 0,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
extern "C" fn jit_athrow(context: *const u8, bci: i32, exception_ptr: i64) {
guarded!("jit_athrow", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
if exception_ptr == 0 {
store_pending_error(
ctx,
JavaError(NullPointerException(Some("Cannot throw null".to_string()))),
);
return;
}
match is_athrow_operand_throwable(ctx, exception_ptr) {
Ok(true) => ctx.set_pending_exception(exception_ptr),
Ok(false) => store_pending_error(
ctx,
InternalError(
"athrow: operand is not an instance of java/lang/Throwable".to_string(),
),
),
Err(error) => store_pending_error(ctx, error),
}
});
}
fn is_athrow_operand_throwable(ctx: &RuntimeContext, exception_ptr: i64) -> Result<bool> {
let thread = thread_from_ctx(ctx);
let gc_ref: Gc<RwLock<Reference>> = Gc::from_raw_i64(exception_ptr)?;
is_instance_of(thread, &gc_ref, &ctx.throwable_class)
}
extern "C" fn jit_pending_exception(context: *const u8) -> i64 {
guarded!("jit_pending_exception", {
let ctx = ctx_from_ptr(context);
ctx.pending_exception()
})
}
extern "C" fn jit_throw_npe(context: *const u8, bci: i32) {
guarded!("jit_throw_npe", {
let ctx = ctx_from_ptr(context);
ctx.current_bci.store(bci, Ordering::Relaxed);
set_top_frame_pc(ctx, bci);
store_pending_error(ctx, JavaError(NullPointerException(None)));
});
}
extern "C" fn jit_take_pending_exception(context: *const u8) -> i64 {
guarded!("jit_take_pending_exception", {
let ctx = ctx_from_ptr(context);
ctx.take_pending_exception_into(|pending| {
if let Err(error) = ctx.add_transient_root_ptr(pending) {
tracing::error!("invalid pending exception pointer during JIT transfer: {error}");
0
} else {
pending
}
})
})
}
extern "C" fn jit_exception_matches(context: *const u8, cp_class_index: i32) -> i32 {
guarded!("jit_exception_matches", {
let ctx = ctx_from_ptr(context);
let cp_class_index = match cp_index_from_i32(cp_class_index, "jit_exception_matches") {
Ok(value) => value,
Err(error) => {
store_pending_error(ctx, error);
return Default::default();
}
};
let pending = ctx.pending_exception();
if pending == 0 {
return 0;
}
match jit_exception_matches_impl(ctx, pending, cp_class_index) {
Ok(true) => 1,
Ok(false) => 0,
Err(error) => {
store_pending_error(ctx, error);
0
}
}
})
}
fn jit_exception_matches_impl(
ctx: &RuntimeContext,
exception_ptr: i64,
cp_class_index: u16,
) -> Result<bool> {
let target_class = resolve_class_ref(ctx, cp_class_index)?;
let gc_ref: Gc<RwLock<Reference>> = Gc::from_raw_i64(exception_ptr)?;
let thread = thread_from_ctx(ctx);
is_instance_of(thread, &gc_ref, &target_class)
}
#[cfg(all(test, not(target_family = "wasm")))]
mod tests {
use super::*;
use crate::test;
#[test]
fn test_cp_index_from_i32_in_range() {
assert_eq!(cp_index_from_i32(0, "test").unwrap(), 0u16);
assert_eq!(cp_index_from_i32(1, "test").unwrap(), 1u16);
assert_eq!(
cp_index_from_i32(i32::from(u16::MAX), "test").unwrap(),
u16::MAX
);
}
#[test]
fn test_cp_index_from_i32_negative_rejected() {
let error = cp_index_from_i32(-1, "jit_test").unwrap_err();
match error {
InternalError(message) => {
assert!(message.contains("jit_test"), "message: {message}");
assert!(message.contains("-1"), "message: {message}");
}
other => panic!("expected InternalError, got {other:?}"),
}
}
#[test]
fn test_cp_index_from_i32_overflow_rejected() {
let value = i32::from(u16::MAX) + 1;
let error = cp_index_from_i32(value, "jit_test").unwrap_err();
match error {
InternalError(message) => {
assert!(message.contains("out of range"), "message: {message}");
}
other => panic!("expected InternalError, got {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_pending_exception_roundtrip_and_drop_unroots() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let collector = vm.garbage_collector();
let throwable_class = thread.class("java/lang/Throwable").await?;
let throwable =
Value::new_object(collector, Reference::from(Object::new(throwable_class)?));
let throwable_ptr = match &throwable {
Value::Object(Some(gc)) => gc.as_ptr_i64(),
_ => panic!("expected Object value"),
};
let ctx = RuntimeContext::new(collector, &vm, &thread, &class)?;
assert_eq!(ctx.pending_exception(), 0);
assert_eq!(
ctx.pending_exception_root.load(Ordering::Acquire),
0,
"no root should be registered initially"
);
ctx.set_pending_exception(throwable_ptr);
assert_eq!(ctx.pending_exception(), throwable_ptr);
let root_after_set = ctx.pending_exception_root.load(Ordering::Acquire);
assert_ne!(
root_after_set, 0,
"set_pending_exception must register a GC root"
);
let taken = ctx.take_pending_exception();
assert_eq!(taken, throwable_ptr);
assert_eq!(ctx.pending_exception(), 0);
assert_eq!(
ctx.pending_exception_root.load(Ordering::Acquire),
0,
"take_pending_exception must clear the GC root id"
);
ctx.set_pending_exception(throwable_ptr);
assert_ne!(ctx.pending_exception_root.load(Ordering::Acquire), 0);
drop(ctx);
drop(throwable);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_pending_exception_replace_unroots_prior() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let collector = vm.garbage_collector();
let throwable_class = thread.class("java/lang/Throwable").await?;
let throwable_a = Value::new_object(
collector,
Reference::from(Object::new(throwable_class.clone())?),
);
let throwable_b =
Value::new_object(collector, Reference::from(Object::new(throwable_class)?));
let ptr_a = match &throwable_a {
Value::Object(Some(gc)) => gc.as_ptr_i64(),
_ => panic!("expected Object value"),
};
let ptr_b = match &throwable_b {
Value::Object(Some(gc)) => gc.as_ptr_i64(),
_ => panic!("expected Object value"),
};
let ctx = RuntimeContext::new(collector, &vm, &thread, &class)?;
ctx.set_pending_exception(ptr_a);
let root_a = ctx.pending_exception_root.load(Ordering::Acquire);
assert_ne!(root_a, 0);
ctx.set_pending_exception(ptr_b);
let root_b = ctx.pending_exception_root.load(Ordering::Acquire);
assert_ne!(root_b, 0);
assert_ne!(
root_a, root_b,
"replacing the pending exception must allocate a fresh root id"
);
assert_eq!(ctx.pending_exception(), ptr_b);
ctx.set_pending_exception(0);
assert_eq!(ctx.pending_exception(), 0);
assert_eq!(
ctx.pending_exception_root.load(Ordering::Acquire),
0,
"clearing pending exception must clear the GC root id"
);
drop(throwable_a);
drop(throwable_b);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_multianewarray_boolean_leaf_is_boolean_array() -> Result<()> {
let (_vm, thread) = test::thread().await?;
let array_class = thread.class("[[Z").await?;
let value = build_multianewarray(&thread, array_class, &[2, 3], 0).await?;
let Value::Object(Some(outer_gc)) = value else {
panic!("expected outer object array")
};
let outer = outer_gc.read();
let outer_array = match &*outer {
Reference::Array(a) => a,
other => panic!("expected outer Array, got {other:?}"),
};
assert_eq!(outer_array.elements.len(), 2);
for element in &outer_array.elements {
let Value::Object(Some(leaf_gc)) = element else {
panic!("expected non-null leaf")
};
let leaf = leaf_gc.read();
match &*leaf {
Reference::BooleanArray(a) => assert_eq!(a.len(), 3),
other => panic!("expected BooleanArray leaf, got {other:?}"),
}
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_multianewarray_byte_leaf_is_byte_array() -> Result<()> {
let (_vm, thread) = test::thread().await?;
let array_class = thread.class("[[B").await?;
let value = build_multianewarray(&thread, array_class, &[1, 4], 0).await?;
let Value::Object(Some(outer_gc)) = value else {
panic!("expected outer object array")
};
let outer = outer_gc.read();
let outer_array = match &*outer {
Reference::Array(a) => a,
other => panic!("expected outer Array, got {other:?}"),
};
let Some(Value::Object(Some(leaf_gc))) = outer_array.elements.first() else {
panic!("expected non-null leaf")
};
let leaf = leaf_gc.read();
match &*leaf {
Reference::ByteArray(a) => assert_eq!(a.len(), 4),
other => panic!("expected ByteArray leaf, got {other:?}"),
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_athrow_rejects_non_throwable() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let collector = vm.garbage_collector();
let object_class = thread.class("java/lang/Object").await?;
let plain = Value::new_object(collector, Reference::from(Object::new(object_class)?));
let plain_ptr = match &plain {
Value::Object(Some(gc)) => gc.as_ptr_i64(),
_ => panic!("expected Object value"),
};
let ctx = RuntimeContext::new(collector, &vm, &thread, &class)?;
assert!(!is_athrow_operand_throwable(&ctx, plain_ptr)?);
let throwable_class = thread.class("java/lang/Throwable").await?;
let thr = Value::new_object(collector, Reference::from(Object::new(throwable_class)?));
let thr_ptr = match &thr {
Value::Object(Some(gc)) => gc.as_ptr_i64(),
_ => panic!("expected Object value"),
};
assert!(is_athrow_operand_throwable(&ctx, thr_ptr)?);
drop(plain);
drop(thr);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_c1_multianewarray_partial_dims_preserves_runtime_type() -> Result<()> {
let (_vm, thread) = test::thread().await?;
let array_class = thread.class("[[[Ljava/lang/String;").await?;
let value = build_multianewarray(&thread, array_class.clone(), &[2, 3], 0).await?;
let Value::Object(Some(outer_gc)) = value else {
panic!("expected outer object array")
};
let outer = outer_gc.read();
let outer_array = match &*outer {
Reference::Array(a) => a,
other => panic!("expected outer Array, got {other:?}"),
};
assert_eq!(outer_array.class.name(), "[[[Ljava/lang/String;");
assert_eq!(outer_array.elements.len(), 2);
for element in &outer_array.elements {
let Value::Object(Some(middle_gc)) = element else {
panic!("expected non-null middle layer")
};
let middle = middle_gc.read();
let middle_array = match &*middle {
Reference::Array(a) => a,
other => panic!("expected middle Array, got {other:?}"),
};
assert_eq!(middle_array.class.name(), "[[Ljava/lang/String;");
assert_eq!(middle_array.elements.len(), 3);
for inner in &middle_array.elements {
assert!(
matches!(inner, Value::Object(None)),
"expected null inner element, got {inner:?}"
);
}
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_m5_class_array_dimensions_metadata() -> Result<()> {
let (_vm, thread) = test::thread().await?;
let one_d = thread.class("[Ljava/lang/String;").await?;
let three_d = thread.class("[[[Ljava/lang/String;").await?;
assert_eq!(one_d.array_dimensions(), 1);
assert_eq!(three_d.array_dimensions(), 3);
assert!(one_d.array_dimensions() < 3);
assert!(three_d.array_dimensions() >= 3);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_h4_throwable_class_is_cached() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let ctx = RuntimeContext::new(vm.garbage_collector(), &vm, &thread, &class)?;
let direct = thread.class("java/lang/Throwable").await?;
assert!(Arc::ptr_eq(&ctx.throwable_class, &direct));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_m4_sentinel_is_preallocated() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let ctx = RuntimeContext::new(vm.garbage_collector(), &vm, &thread, &class)?;
assert_ne!(
ctx.sentinel_throwable, 0,
"RuntimeContext must pre-allocate a sentinel throwable"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_m2_runtime_context_owns_arcs() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let ctx = RuntimeContext::new(vm.garbage_collector(), &vm, &thread, &class)?;
let class_ptr = Arc::as_ptr(&ctx.class);
let thread_ptr = Arc::as_ptr(&ctx.thread);
drop(class);
assert!(!class_ptr.is_null());
assert!(!thread_ptr.is_null());
assert_eq!(Arc::as_ptr(&ctx.class), class_ptr);
Ok(())
}
#[test]
fn regression_l2_int_to_field_value_narrow_types() {
assert_eq!(int_to_field_value("Z", 1), Value::Int(1));
assert_eq!(int_to_field_value("Z", 0), Value::Int(0));
assert_eq!(int_to_field_value("Z", 42), Value::Int(0));
assert_eq!(int_to_field_value("Z", 43), Value::Int(1));
assert_eq!(int_to_field_value("B", 0xFF), Value::Int(-1));
assert_eq!(int_to_field_value("B", 0x7F), Value::Int(127));
assert_eq!(int_to_field_value("C", 65), Value::Int(65));
assert_eq!(int_to_field_value("C", 0xD800), Value::Int(0xD800));
assert_eq!(int_to_field_value("C", -1), Value::Int(0xFFFF));
assert_eq!(int_to_field_value("S", -7), Value::Int(-7));
assert_eq!(int_to_field_value("S", 0xFFFF), Value::Int(-1));
assert_eq!(int_to_field_value("I", 12345), Value::Int(12345));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_h1_current_bci_is_recorded_by_throwing_helpers() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let ctx = RuntimeContext::new(vm.garbage_collector(), &vm, &thread, &class)?;
assert_eq!(ctx.current_bci.load(Ordering::Relaxed), -1);
ctx.current_bci.store(42, Ordering::Relaxed);
assert_eq!(ctx.current_bci.load(Ordering::Relaxed), 42);
ctx.current_bci.store(123, Ordering::Relaxed);
assert_eq!(ctx.current_bci.load(Ordering::Relaxed), 123);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_h3_resolution_caches_present_and_initially_empty() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let ctx = RuntimeContext::new(vm.garbage_collector(), &vm, &thread, &class)?;
assert_eq!(ctx.field_cache.read().len(), 0);
assert_eq!(ctx.class_cache.read().len(), 0);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn coverage_multianewarray_with_zero_dimensions_returns_error() -> Result<()> {
let (_vm, thread) = test::thread().await?;
let array_class = thread.class("[Ljava/lang/String;").await?;
assert_eq!(array_class.array_dimensions(), 1);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_h3_aastore_primitive_array_into_object_slot() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let collector = vm.garbage_collector();
let ctx = RuntimeContext::new(collector, &vm, &thread, &class)?;
let object_array_class = thread.class("[Ljava/lang/Object;").await?;
let object_array = Reference::try_from((object_array_class, vec![Value::Object(None)]))?;
let array_ptr = alloc_reference(&ctx, object_array);
let int_array = Reference::IntArray(vec![1, 2, 3].into_boxed_slice());
let value_ptr = alloc_reference(&ctx, int_array);
jit_aastore_inner(ctx.as_ptr(), 0, array_ptr, 0, value_ptr);
assert_eq!(
ctx.pending_exception(),
0,
"storing int[] into Object[] must not raise ArrayStoreException"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_h3_aastore_string_array_into_object_array_array() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let collector = vm.garbage_collector();
let ctx = RuntimeContext::new(collector, &vm, &thread, &class)?;
let outer_class = thread.class("[[Ljava/lang/Object;").await?;
let outer = Reference::try_from((outer_class, vec![Value::Object(None)]))?;
let array_ptr = alloc_reference(&ctx, outer);
let string_array_class = thread.class("[Ljava/lang/String;").await?;
let string_array = Reference::try_from((
string_array_class,
vec![Value::Object(None), Value::Object(None)],
))?;
let value_ptr = alloc_reference(&ctx, string_array);
jit_aastore_inner(ctx.as_ptr(), 0, array_ptr, 0, value_ptr);
assert_eq!(
ctx.pending_exception(),
0,
"storing String[] into Object[][] must not raise ArrayStoreException"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn regression_h3_aastore_object_into_string_array() -> Result<()> {
let (vm, thread, class) = test::class().await?;
let collector = vm.garbage_collector();
let ctx = RuntimeContext::new(collector, &vm, &thread, &class)?;
let string_array_class = thread.class("[Ljava/lang/String;").await?;
let string_array = Reference::try_from((string_array_class, vec![Value::Object(None)]))?;
let array_ptr = alloc_reference(&ctx, string_array);
let object_class = thread.class("java/lang/Object").await?;
let object_value = alloc_reference(&ctx, Reference::from(Object::new(object_class)?));
assert_eq!(ctx.pending_exception(), 0, "precondition: no pending");
jit_aastore_inner(ctx.as_ptr(), 0, array_ptr, 0, object_value);
assert_ne!(
ctx.pending_exception(),
0,
"storing Object into String[] must raise ArrayStoreException"
);
Ok(())
}
}