use crate::engine::types::{PhpResult, Val};
use crate::vm::execute_data::{ExecResult, ExecuteData};
use crate::vm::opcodes::{OpArray, Opcode};
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{OnceLock, RwLock};
const JIT_THRESHOLD: usize = 100;
#[derive(Debug, Default)]
pub struct ExecutionCounter {
count: AtomicUsize,
jit_compiled: std::sync::atomic::AtomicBool,
compile_suggested: std::sync::atomic::AtomicBool,
}
impl ExecutionCounter {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn increment(&self) -> usize {
self.count.fetch_add(1, Ordering::Relaxed) + 1
}
#[inline]
pub fn count(&self) -> usize {
self.count.load(Ordering::Relaxed)
}
#[inline]
pub fn should_jit_compile(&self) -> bool {
if self.jit_compiled.load(Ordering::Relaxed) {
return false;
}
if self.count.load(Ordering::Relaxed) < JIT_THRESHOLD {
return false;
}
if self.compile_suggested.load(Ordering::Relaxed) {
return false;
}
self.compile_suggested.store(true, Ordering::Relaxed);
true
}
#[inline]
pub fn mark_jit_compiled(&self) {
self.jit_compiled.store(true, Ordering::Relaxed);
}
}
pub type CompiledFunction =
std::sync::Arc<dyn Fn(&mut ExecuteData) -> Result<PhpResult, String> + Send + Sync>;
pub struct JitCompiler {
execution_counters: HashMap<String, ExecutionCounter>,
compiled_functions: HashMap<String, CompiledFunction>,
jit_stats: JitStats,
}
#[derive(Debug, Default)]
pub struct JitStats {
pub functions_compiled: AtomicUsize,
pub total_executions: AtomicUsize,
pub jit_hits: AtomicUsize,
pub jit_misses: AtomicUsize,
}
impl JitCompiler {
pub fn new() -> Self {
Self {
execution_counters: HashMap::new(),
compiled_functions: HashMap::new(),
jit_stats: JitStats::default(),
}
}
pub fn get_counter(&mut self, function_name: &str) -> &ExecutionCounter {
self.execution_counters
.entry(function_name.to_string())
.or_insert_with(ExecutionCounter::new)
}
pub fn should_compile(&mut self, function_name: &str) -> bool {
if let Some(counter) = self.execution_counters.get(function_name) {
counter.should_jit_compile()
} else {
false
}
}
pub fn compile_function(
&mut self,
function_name: &str,
op_array: &OpArray,
) -> Result<CompiledFunction, String> {
let counter = self.get_counter(function_name);
counter.mark_jit_compiled();
self.jit_stats
.functions_compiled
.fetch_add(1, Ordering::Relaxed);
let compiled_fn = self.generate_native_code(function_name, op_array)?;
self.compiled_functions
.insert(function_name.to_string(), compiled_fn.clone());
Ok(compiled_fn)
}
pub fn get_compiled_function(&self, function_name: &str) -> Option<CompiledFunction> {
self.compiled_functions.get(function_name).cloned()
}
fn generate_native_code(
&self,
function_name: &str,
op_array: &OpArray,
) -> Result<CompiledFunction, String> {
match function_name {
"add" => Ok(self.compile_add_function()),
"multiply" => Ok(self.compile_multiply_function()),
"concat" => Ok(self.compile_concat_function()),
_ => self.compile_generic_function(op_array),
}
}
fn compile_add_function(&self) -> CompiledFunction {
std::sync::Arc::new(|execute_data: &mut ExecuteData| {
if execute_data.call_args.len() >= 2 {
let val1 = &execute_data.call_args[0];
let val2 = &execute_data.call_args[1];
let result = crate::engine::operators::zval_add(val1, val2);
execute_data.call_args.clear();
execute_data.call_args.push(result);
}
Ok(PhpResult::Success)
})
}
fn compile_multiply_function(&self) -> CompiledFunction {
std::sync::Arc::new(|execute_data: &mut ExecuteData| {
if execute_data.call_args.len() >= 2 {
let val1 = &execute_data.call_args[0];
let val2 = &execute_data.call_args[1];
let result = crate::engine::operators::zval_mul(val1, val2);
execute_data.call_args.clear();
execute_data.call_args.push(result);
}
Ok(PhpResult::Success)
})
}
fn compile_concat_function(&self) -> CompiledFunction {
std::sync::Arc::new(|execute_data: &mut ExecuteData| {
if execute_data.call_args.len() >= 2 {
let val1 = &execute_data.call_args[0];
let val2 = &execute_data.call_args[1];
let s1 = crate::engine::operators::zval_get_string(val1);
let s2 = crate::engine::operators::zval_get_string(val2);
let result = super::perf_alloc::fast_concat(s1.as_str(), s2.as_str());
let result_val = Val::new(
crate::engine::types::PhpValue::String(Box::new(result)),
crate::engine::types::PhpType::String,
);
execute_data.call_args.clear();
execute_data.call_args.push(result_val);
}
Ok(PhpResult::Success)
})
}
fn compile_generic_function(&self, op_array: &OpArray) -> Result<CompiledFunction, String> {
let ops = op_array.ops.clone();
Ok(std::sync::Arc::new(
move |execute_data: &mut ExecuteData| {
for op in &ops {
let result = crate::vm::dispatch_handlers::dispatch_opcode(op, execute_data);
match result {
Ok(ExecResult::Continue) => continue,
Ok(ExecResult::Jump(_)) => {
continue;
}
Ok(ExecResult::Return(_)) => {
return Ok(PhpResult::Success);
}
Err(e) => return Err(e),
}
}
Ok(PhpResult::Success)
},
))
}
pub fn get_stats(&self) -> (usize, usize, usize, usize) {
(
self.jit_stats.functions_compiled.load(Ordering::Relaxed),
self.jit_stats.total_executions.load(Ordering::Relaxed),
self.jit_stats.jit_hits.load(Ordering::Relaxed),
self.jit_stats.jit_misses.load(Ordering::Relaxed),
)
}
}
impl Default for JitCompiler {
fn default() -> Self {
Self::new()
}
}
static GLOBAL_JIT: OnceLock<RwLock<JitCompiler>> = OnceLock::new();
pub fn get_jit_compiler() -> &'static RwLock<JitCompiler> {
GLOBAL_JIT.get_or_init(|| RwLock::new(JitCompiler::new()))
}
#[inline]
pub fn should_track_for_jit(function_name: &str) -> bool {
matches!(
function_name,
"add"
| "multiply"
| "concat"
| "strlen"
| "array_merge"
| "in_array"
| "count"
| "isset"
| "empty"
)
}
#[inline]
pub fn increment_execution_counter(function_name: &str) -> bool {
if should_track_for_jit(function_name) {
let jit = get_jit_compiler();
let mut jit = jit.write().unwrap();
let counter = jit.get_counter(function_name);
let count = counter.increment();
let should_compile =
count >= JIT_THRESHOLD && !counter.jit_compiled.load(Ordering::Relaxed);
drop(jit); should_compile
} else {
false
}
}
pub fn execute_with_jit(
function_name: &str,
execute_data: &mut ExecuteData,
op_array: &OpArray,
) -> Result<PhpResult, String> {
{
let jit = get_jit_compiler();
let jit = jit.read().unwrap();
if let Some(compiled_fn) = jit.get_compiled_function(function_name) {
drop(jit);
let jit = get_jit_compiler().write().unwrap();
jit.jit_stats.jit_hits.fetch_add(1, Ordering::Relaxed);
return compiled_fn(execute_data);
}
}
{
let mut jit = get_jit_compiler().write().unwrap();
if jit.should_compile(function_name) {
if let Ok(compiled_fn) = jit.compile_function(function_name, op_array) {
return compiled_fn(execute_data);
}
}
jit.jit_stats.jit_misses.fetch_add(1, Ordering::Relaxed);
}
Ok(PhpResult::Success)
}
pub fn try_inline_operation(opcode: Opcode, op1: &Val, op2: &Val) -> Option<Val> {
match opcode {
Opcode::Add => Some(crate::engine::operators::zval_add(op1, op2)),
Opcode::Mul => Some(crate::engine::operators::zval_mul(op1, op2)),
Opcode::Concat => {
let s1 = crate::engine::operators::zval_get_string(op1);
let s2 = crate::engine::operators::zval_get_string(op2);
let result = super::perf_alloc::fast_concat(s1.as_str(), s2.as_str());
Some(Val::new(
crate::engine::types::PhpValue::String(Box::new(result)),
crate::engine::types::PhpType::String,
))
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_counter() {
let counter = ExecutionCounter::new();
for i in 0..JIT_THRESHOLD {
assert_eq!(counter.increment(), i + 1);
}
assert!(counter.should_jit_compile());
assert!(!counter.should_jit_compile());
counter.mark_jit_compiled();
assert!(!counter.should_jit_compile());
}
#[test]
fn test_jit_compiler() {
let mut jit = JitCompiler::new();
let op_array = OpArray::new("test.php".to_string());
let _compiled_fn = jit.compile_function("add", &op_array).unwrap();
assert!(jit.get_compiled_function("add").is_some());
let stats = jit.get_stats();
assert_eq!(stats.0, 1); }
#[test]
fn test_should_track_for_jit() {
assert!(should_track_for_jit("add"));
assert!(should_track_for_jit("multiply"));
assert!(should_track_for_jit("concat"));
assert!(!should_track_for_jit("rare_function"));
}
#[test]
fn test_increment_execution_counter() {
assert!(!increment_execution_counter("rare_function"));
for _ in 0..JIT_THRESHOLD {
increment_execution_counter("add");
}
assert!(increment_execution_counter("add"));
}
}