use crate::Error::InternalError;
use crate::jit_runtime_helpers::{self as runtime_helpers, RuntimeContext};
use crate::{Result, Thread, VM};
use dashmap::DashMap;
#[cfg(not(target_family = "wasm"))]
use rayon::ThreadPoolBuilder;
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use ristretto_classloader::{Class, Method, Value};
use ristretto_gc::{GarbageCollector, Gc};
use ristretto_jit::Function;
use std::sync::Arc;
#[cfg(not(target_family = "wasm"))]
use std::time::Duration;
#[cfg(not(target_family = "wasm"))]
use sysinfo::System;
use tokio::sync::{Mutex, mpsc};
#[cfg(not(target_family = "wasm"))]
use tokio::time::sleep;
#[cfg(not(target_family = "wasm"))]
use tracing::{debug, error, info};
#[cfg(target_family = "wasm")]
use tracing::{debug, info};
const BATCH_SIZE: usize = 5;
const BATCH_TIMEOUT_MS: u64 = 10;
type CacheKey = usize;
fn cache_key(method: &Arc<Method>) -> CacheKey {
Arc::as_ptr(method) as CacheKey
}
struct CompilationRequest {
class: Arc<Class>,
method: Arc<Method>,
}
pub struct Compiler {
#[expect(clippy::struct_field_names)]
jit_compiler: ristretto_jit::Compiler,
function_cache: Arc<DashMap<CacheKey, Option<Arc<Function>>>>,
compilation_queue: Mutex<Option<mpsc::UnboundedSender<CompilationRequest>>>,
batch_compilation_enabled: bool,
interpreted: bool,
}
impl std::fmt::Debug for Compiler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Compiler")
.field("function_cache_size", &self.function_cache.len())
.field("batch_compilation_enabled", &self.batch_compilation_enabled)
.field("interpreted", &self.interpreted)
.finish_non_exhaustive()
}
}
impl Compiler {
#[must_use]
pub fn new(batch_compilation: bool, interpreted: bool) -> Option<Self> {
let jit_compiler = match ristretto_jit::Compiler::new() {
Ok(compiler) => compiler,
Err(error) => {
debug!("JIT compiler not available: {error:?}");
return None;
}
};
Some(Self {
jit_compiler,
function_cache: Arc::new(DashMap::new()),
compilation_queue: Mutex::new(None),
batch_compilation_enabled: batch_compilation,
interpreted,
})
}
#[expect(clippy::option_option)]
pub fn get_cached(&self, key: CacheKey) -> Option<Option<Arc<Function>>> {
self.function_cache.get(&key).map(|entry| entry.clone())
}
pub fn insert_cached(&self, key: CacheKey, function: Option<Arc<Function>>) {
self.function_cache.insert(key, function);
}
pub fn contains_cached(&self, key: CacheKey) -> bool {
self.function_cache.contains_key(&key)
}
#[must_use]
pub fn is_interpreted(&self) -> bool {
self.interpreted
}
#[must_use]
pub fn is_batch_compilation_enabled(&self) -> bool {
self.batch_compilation_enabled
}
#[cfg_attr(target_family = "wasm", expect(clippy::unused_async))]
pub async fn compile(
&self,
class: &Arc<Class>,
method: &Arc<Method>,
) -> Result<Option<Arc<Function>>> {
if self.interpreted {
return Ok(None);
}
if method.is_native() || method.is_abstract() {
return Ok(None);
}
let key = cache_key(method);
if let Some(function) = self.get_cached(key) {
return Ok(function);
}
if !self.batch_compilation_enabled {
return Ok(self.compile_method_sync(class, method, key));
}
#[cfg(target_family = "wasm")]
{
Ok(self.compile_method_sync(class, method, key))
}
#[cfg(not(target_family = "wasm"))]
{
self.insert_cached(key, None);
let request = CompilationRequest {
class: class.clone(),
method: method.clone(),
};
self.send_compilation_request(request).await;
Ok(None)
}
}
fn compile_method_sync(
&self,
class: &Arc<Class>,
method: &Arc<Method>,
key: CacheKey,
) -> Option<Arc<Function>> {
let definition = method.definition();
let function = if ristretto_jit::Compiler::can_compile(definition) {
let class_file = class.class_file();
let symbols = runtime_helpers::symbols();
match self.jit_compiler.compile(class_file, definition, &symbols) {
Ok(function) => {
let function = Arc::new(function);
info!(
"compiled method {}.{}{}",
class.name(),
method.name(),
method.descriptor()
);
Some(function)
}
Err(error) => {
debug!(
"JIT compilation failed for {}.{}{}: {error}",
class.name(),
method.name(),
method.descriptor()
);
None
}
}
} else {
None
};
self.insert_cached(key, function.clone());
function
}
#[cfg(not(target_family = "wasm"))]
async fn send_compilation_request(&self, request: CompilationRequest) {
let mut queue_guard = self.compilation_queue.lock().await;
if queue_guard.is_none() {
let (sender, mut receiver) = mpsc::unbounded_channel::<CompilationRequest>();
*queue_guard = Some(sender);
let cpus = System::physical_core_count().unwrap_or(1);
let compiler_threads = ((cpus + 5) / 10).max(1);
info!("JIT parallel compiler configured with {compiler_threads} threads");
let jit_compiler = self.jit_compiler.clone();
let function_cache = self.function_cache.clone();
tokio::spawn(async move {
let mut batch = Vec::new();
loop {
let timeout = sleep(Duration::from_millis(BATCH_TIMEOUT_MS));
tokio::pin!(timeout);
let should_process;
tokio::select! {
request = receiver.recv() => {
match request {
Some(request) => {
batch.push(request);
should_process = batch.len() >= BATCH_SIZE;
}
None => break,
}
}
() = &mut timeout => {
should_process = !batch.is_empty();
}
}
if should_process {
process_compilation_batch(
compiler_threads,
&mut batch,
&jit_compiler,
&function_cache,
);
}
}
});
}
if let Some(sender) = queue_guard.as_ref() {
let _ = sender.send(request);
}
}
}
#[cfg(not(target_family = "wasm"))]
fn process_compilation_batch(
compiler_threads: usize,
batch: &mut Vec<CompilationRequest>,
jit_compiler: &ristretto_jit::Compiler,
function_cache: &Arc<DashMap<CacheKey, Option<Arc<Function>>>>,
) {
debug!("Processing compilation batch of {} methods", batch.len());
let thread_pool = match ThreadPoolBuilder::new()
.num_threads(compiler_threads)
.thread_name(|thread_index| format!("jit-{thread_index}"))
.build()
{
Ok(thread_pool) => thread_pool,
Err(error) => {
error!("Failed to create thread pool for JIT compilation: {error}");
return;
}
};
let batch_requests = std::mem::take(batch);
thread_pool.install(|| {
batch_requests.into_par_iter().for_each(|request| {
let key = cache_key(&request.method);
let definition = request.method.definition();
let function = if ristretto_jit::Compiler::can_compile(definition) {
let class_file = request.class.class_file();
let symbols = runtime_helpers::symbols();
match jit_compiler.compile(class_file, definition, &symbols) {
Ok(function) => {
let function = Arc::new(function);
info!(
"compiled method {}.{}{}",
request.class.name(),
request.method.name(),
request.method.descriptor()
);
Some(function)
}
Err(error) => {
debug!(
"JIT compilation failed for {}.{}{}: {error}",
request.class.name(),
request.method.name(),
request.method.descriptor()
);
None
}
}
} else {
None
};
function_cache.insert(key, function);
});
});
}
pub(crate) fn execute(
function: &Arc<Function>,
parameters: &[Value],
gc: &Arc<GarbageCollector>,
vm: &Arc<VM>,
thread: &Arc<Thread>,
class: &Arc<Class>,
) -> Result<Option<Value>> {
let mut stack_buf: [ristretto_jit::Value; 8] = [const { ristretto_jit::Value::I32(0) }; 8];
let mut count = 0;
let arguments = if parameters.len() <= stack_buf.len() {
for value in parameters {
if matches!(value, Value::Unused) {
continue;
}
let slot = stack_buf
.get_mut(count)
.ok_or_else(|| InternalError(format!("Invalid JIT argument index {count}")))?;
*slot = convert_to_jit(value)?;
count += 1;
}
stack_buf
.get(..count)
.ok_or_else(|| InternalError(format!("Invalid JIT argument count {count}")))?
} else {
let vec = convert_parameters(parameters)?;
let runtime_context = RuntimeContext::new(gc, vm, thread, class)?;
let context = runtime_context.as_ptr();
let result = function.execute(&vec, context)?;
return finish_execute(&runtime_context, result);
};
let runtime_context = RuntimeContext::new(gc, vm, thread, class)?;
let context = runtime_context.as_ptr();
let result = function.execute(arguments, context)?;
finish_execute(&runtime_context, result)
}
fn finish_execute(
runtime_context: &RuntimeContext,
result: Option<ristretto_jit::Value>,
) -> Result<Option<Value>> {
runtime_context.take_pending_exception_into(|pending| {
if pending != 0 {
let gc_ref: Gc<ristretto_gc::sync::RwLock<ristretto_classloader::Reference>> =
Gc::from_raw_i64(pending)?;
return Err(crate::Error::Throwable(Value::Object(Some(gc_ref))));
}
result.map(|v| convert_to_vm(&v)).transpose()
})
}
fn convert_parameters(parameters: &[Value]) -> Result<Vec<ristretto_jit::Value>> {
let mut values = Vec::with_capacity(parameters.len());
for value in parameters {
if matches!(value, Value::Unused) {
continue;
}
let value = convert_to_jit(value)?;
values.push(value);
}
Ok(values)
}
fn convert_to_jit(value: &Value) -> Result<ristretto_jit::Value> {
let jit_value = match value {
Value::Int(value) => ristretto_jit::Value::I32(*value),
Value::Long(value) => ristretto_jit::Value::I64(*value),
Value::Float(value) => ristretto_jit::Value::F32(*value),
Value::Double(value) => ristretto_jit::Value::F64(*value),
Value::Object(None) => ristretto_jit::Value::Ptr(0),
Value::Object(Some(gc_ref)) => ristretto_jit::Value::Ptr(gc_ref.as_ptr_i64()),
Value::Unused => {
return Err(InternalError(
"Cannot convert Unused value to JIT".to_string(),
));
}
};
Ok(jit_value)
}
fn convert_to_vm(jit_value: &ristretto_jit::Value) -> Result<Value> {
let value = match jit_value {
ristretto_jit::Value::I32(value) => Value::from(*value),
ristretto_jit::Value::I64(value) => Value::from(*value),
ristretto_jit::Value::F32(value) => Value::from(*value),
ristretto_jit::Value::F64(value) => Value::from(*value),
ristretto_jit::Value::Ptr(0) => Value::Object(None),
ristretto_jit::Value::Ptr(ptr) => {
let gc_ref = Gc::from_raw_i64(*ptr)?;
Value::Object(Some(gc_ref))
}
};
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convert_parameters() -> Result<()> {
let parameters = vec![
Value::Int(1),
Value::Long(2),
Value::Float(3.1),
Value::Double(4.2),
];
let values = convert_parameters(¶meters)?;
assert_eq!(
values.as_slice(),
&[
ristretto_jit::Value::I32(1),
ristretto_jit::Value::I64(2),
ristretto_jit::Value::F32(3.1),
ristretto_jit::Value::F64(4.2),
]
);
Ok(())
}
#[test]
fn test_convert_to_jit_i32() -> Result<()> {
let value = Value::Int(42);
let result = convert_to_jit(&value)?;
assert_eq!(result, ristretto_jit::Value::I32(42));
Ok(())
}
#[test]
fn test_convert_to_jit_i64() -> Result<()> {
let value = Value::Long(42);
let result = convert_to_jit(&value)?;
assert_eq!(result, ristretto_jit::Value::I64(42));
Ok(())
}
#[test]
fn test_convert_to_jit_f32() -> Result<()> {
let value = Value::Float(42.1);
let result = convert_to_jit(&value)?;
assert_eq!(result, ristretto_jit::Value::F32(42.1));
Ok(())
}
#[test]
fn test_convert_to_jit_f64() -> Result<()> {
let value = Value::Double(42.1);
let result = convert_to_jit(&value)?;
assert_eq!(result, ristretto_jit::Value::F64(42.1));
Ok(())
}
#[test]
fn test_convert_to_jit_null_object() {
let value = Value::Object(None);
let result = convert_to_jit(&value);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ristretto_jit::Value::Ptr(0));
}
#[test]
fn test_convert_to_jit_unused() {
let value = Value::Unused;
let result = convert_to_jit(&value);
assert!(result.is_err());
}
#[test]
fn test_convert_to_vm_i32() {
let value = ristretto_jit::Value::I32(42);
let result = convert_to_vm(&value).unwrap();
assert_eq!(result, Value::Int(42));
}
#[test]
fn test_convert_to_vm_i64() {
let value = ristretto_jit::Value::I64(42);
let result = convert_to_vm(&value).unwrap();
assert_eq!(result, Value::Long(42));
}
#[test]
fn test_convert_to_vm_f32() {
let value = ristretto_jit::Value::F32(42.1);
let result = convert_to_vm(&value).unwrap();
assert_eq!(result, Value::Float(42.1));
}
#[test]
fn test_convert_to_vm_f64() {
let value = ristretto_jit::Value::F64(42.1);
let result = convert_to_vm(&value).unwrap();
assert_eq!(result, Value::Double(42.1));
}
#[test]
fn test_convert_to_vm_null_ptr() {
let value = ristretto_jit::Value::Ptr(0);
let result = convert_to_vm(&value).unwrap();
assert_eq!(result, Value::Object(None));
}
#[test]
fn test_compiler_new() {
let compiler = Compiler::new(false, false);
assert!(compiler.is_some());
let compiler = compiler.unwrap();
assert!(!compiler.is_interpreted());
assert!(!compiler.is_batch_compilation_enabled());
}
#[test]
fn test_compiler_interpreted_mode() {
let compiler = Compiler::new(false, true).unwrap();
assert!(compiler.is_interpreted());
assert!(!compiler.is_batch_compilation_enabled());
}
#[test]
fn test_compiler_batch_compilation_mode() {
let compiler = Compiler::new(true, false).unwrap();
assert!(!compiler.is_interpreted());
assert!(compiler.is_batch_compilation_enabled());
}
#[test]
fn test_compiler_cache_operations() {
let compiler = Compiler::new(false, false).unwrap();
let test_key: CacheKey = 42;
assert!(!compiler.contains_cached(test_key));
assert!(compiler.get_cached(test_key).is_none());
compiler.insert_cached(test_key, None);
assert!(compiler.contains_cached(test_key));
let cached = compiler.get_cached(test_key);
assert!(cached.is_some());
assert!(cached.unwrap().is_none());
}
#[test]
fn test_compiler_debug() {
let compiler = Compiler::new(true, false).unwrap();
let debug_str = format!("{compiler:?}");
assert!(debug_str.contains("Compiler"));
assert!(debug_str.contains("batch_compilation_enabled"));
}
}