use std::sync::Arc;
use crate::env::env::Env;
use crate::env::error::EvalResult;
use crate::interp::apply::select_arity;
use cljrs_gc::GcPtr;
use cljrs_value::{CljxFn, PersistentList, Value};
static EAGER_LOWER_FORCED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub fn force_eager_lowering() {
EAGER_LOWER_FORCED.store(true, std::sync::atomic::Ordering::Relaxed);
}
pub(crate) fn eager_lower_enabled() -> bool {
if std::env::var("CLJRS_NO_IR").is_ok() {
return false;
}
if EAGER_LOWER_FORCED.load(std::sync::atomic::Ordering::Relaxed) {
return true;
}
std::env::var("CLJRS_EAGER_LOWER").is_ok()
}
pub fn call_cljrs_fn(f: &CljxFn, args: &[Value], caller_env: &mut Env) -> EvalResult {
let arity = select_arity(f, args.len())?;
if !f.is_macro {
let arity_id = arity.ir_arity_id;
if caller_env.globals.tier_state().jit_enabled()
&& let Some((fn_ptr, epoch)) = caller_env.globals.jit().get_native_fn(arity_id)
{
return call_jit_native(f, fn_ptr, epoch, arity, args, caller_env);
}
if let Some(result) = try_ir_path(f, arity, args, caller_env) {
return result;
}
maybe_request_lowering(f, arity_id, caller_env);
}
crate::interp::apply::call_cljrs_fn(f, args, caller_env)
}
thread_local! {
pub static IR_LOWERING_ACTIVE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
fn try_ir_path(
f: &CljxFn,
arity: &cljrs_value::CljxFnArity,
args: &[Value],
caller_env: &mut Env,
) -> Option<EvalResult> {
let arity_id = arity.ir_arity_id;
if crate::tiered::defn_registry::relower_pending()
&& crate::tiered::defn_registry::relower_marked(arity_id)
&& !IR_LOWERING_ACTIVE.get()
&& !caller_env.globals.jit().lower_queued(arity_id)
{
request_background_lower(f, caller_env);
}
let ir_func = caller_env.globals.ir_cache().get(arity_id)?;
if ir_func.is_async {
return None;
}
let profile_args = if arity.rest_param.is_some() {
&args[..arity.params.len().min(args.len())]
} else {
args
};
caller_env
.globals
.jit()
.record_call(arity_id, Arc::clone(&ir_func), profile_args);
Some(execute_ir(f, arity, &ir_func, args, caller_env))
}
fn no_ir() -> bool {
static NO_IR: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*NO_IR.get_or_init(|| std::env::var("CLJRS_NO_IR").is_ok())
}
fn maybe_request_lowering(f: &CljxFn, arity_id: u64, caller_env: &mut Env) {
let named_without_global = f.name.as_deref().is_some_and(|n| {
caller_env
.globals
.lookup_var_in_ns(&f.defining_ns, n)
.is_none()
});
if IR_LOWERING_ACTIVE.get()
|| f.is_async
|| !f.closed_over_names.is_empty()
|| named_without_global
|| caller_env.globals.jit().is_bootstrap_arity(arity_id)
|| no_ir()
|| !caller_env.globals.ir_enabled()
{
return;
}
if !caller_env.globals.jit().record_interp_call(arity_id) {
return;
}
if caller_env.globals.builtin_source(&f.defining_ns).is_some() {
caller_env.globals.jit().mark_lower_queued(arity_id);
return;
}
if !caller_env.globals.ir_cache().should_attempt(arity_id) {
caller_env.globals.jit().mark_lower_queued(arity_id);
return;
}
request_background_lower(f, caller_env);
}
fn request_background_lower(f: &CljxFn, caller_env: &mut Env) {
let saved_ns = caller_env.current_ns.clone();
caller_env.current_ns = f.defining_ns.clone();
let arities: Vec<crate::tiered::lower_worker::LowerArityRequest> = f
.arities
.iter()
.map(|a| crate::tiered::lower_worker::LowerArityRequest {
arity_id: a.ir_arity_id,
params: a.params.clone(),
rest_param: a.rest_param.clone(),
destructure_params: a.destructure_params.clone(),
destructure_rest: a.destructure_rest.clone(),
expanded_body: crate::tiered::lower::macroexpand_body(&a.body, caller_env),
param_hints: a.param_hints.clone(),
})
.collect();
caller_env.current_ns = saved_ns;
let arity_ids: Vec<u64> = arities.iter().map(|a| a.arity_id).collect();
let accepted =
crate::tiered::lower_worker::enqueue(crate::tiered::lower_worker::LowerRequest {
tiers: caller_env.globals.tiers().handle(),
name: f.name.clone(),
ns: f.defining_ns.clone(),
is_async: f.is_async,
arities,
});
if accepted {
for id in arity_ids {
caller_env.globals.jit().mark_lower_queued(id);
}
}
}
fn execute_ir(
f: &CljxFn,
arity: &cljrs_value::CljxFnArity,
ir_func: &cljrs_ir::IrFunction,
args: &[Value],
caller_env: &mut Env,
) -> EvalResult {
let _caller_root = crate::env::gc_roots::push_env_root(caller_env);
let mut env = Env::with_closure(caller_env.globals.clone(), &f.defining_ns, f);
env.push_frame();
crate::interp::apply::bind_fn_params(arity, args, &mut env)?;
if let Some(ref name) = f.name {
let self_val = if let Some(ref p) = f.self_ptr {
Value::Fn(p.clone())
} else {
Value::Fn(GcPtr::new(f.clone()))
};
env.bind(name.clone(), self_val);
}
crate::env::callback::push_eval_context(&env);
let ir_args = if arity.rest_param.is_some() {
let n = arity.params.len();
let mut ir_args = args[..n.min(args.len())].to_vec();
let rest_items: Vec<Value> = args[n.min(args.len())..].to_vec();
let rest_val = if rest_items.is_empty() {
Value::Nil
} else {
Value::List(GcPtr::new(PersistentList::from_iter(rest_items)))
};
ir_args.push(rest_val);
ir_args
} else {
args.to_vec()
};
let result = crate::tiered::ir_interp::interpret_ir_with_osr(
ir_func,
ir_args,
&caller_env.globals,
&f.defining_ns,
&mut env,
Some(arity.ir_arity_id),
);
crate::env::callback::pop_eval_context();
env.pop_frame();
result
}
fn call_jit_native(
f: &CljxFn,
fn_ptr: *const (),
epoch: u64,
arity: &cljrs_value::CljxFnArity,
args: &[Value],
caller_env: &mut Env,
) -> EvalResult {
let _jit_frame = crate::tiered::jit_state::push_jit_frame(epoch);
let _caller_root = crate::env::gc_roots::push_env_root(caller_env);
let _eval_ctx = crate::env::callback::install_eval_context_guard(
caller_env.globals.clone(),
f.defining_ns.clone(),
);
let call_args: Vec<Value> = if arity.rest_param.is_some() {
let n = arity.params.len();
let split = n.min(args.len());
let mut v = args[..split].to_vec();
let rest_items = &args[split..];
let rest_val = if rest_items.is_empty() {
Value::Nil
} else {
Value::List(GcPtr::new(PersistentList::from_iter(rest_items.to_vec())))
};
v.push(rest_val);
v
} else {
args.to_vec()
};
let _arg_roots = crate::env::gc_roots::root_values(&call_args);
let _alloc_frame = cljrs_gc::push_alloc_frame();
let arg_ptrs: Vec<*const Value> = call_args.iter().map(|v| v as *const Value).collect();
let result_ptr = unsafe { crate::tiered::jit_state::dispatch_jit_call(fn_ptr, &arg_ptrs) };
if caller_env.globals.jit().is_deopt_result(result_ptr) {
caller_env.globals.jit().record_deopt(arity.ir_arity_id);
if let Some(ir_func) = caller_env.globals.ir_cache().get(arity.ir_arity_id) {
return execute_ir(f, arity, &ir_func, args, caller_env);
}
return crate::interp::apply::call_cljrs_fn(f, args, caller_env);
}
let result = unsafe { (*result_ptr).clone() };
let gas_exhausted = crate::env::gas::is_exhausted();
let pending_exception = caller_env.globals.jit().take_pending_exception();
if gas_exhausted {
return Err(crate::env::error::EvalError::GasExhausted);
}
if let Some(thrown) = pending_exception {
return Err(crate::env::error::EvalError::Thrown(thrown));
}
Ok(result)
}