use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, LazyLock, RwLock};
use cljrs_ir::IrFunction;
use cljrs_ir::lower::ExternalDefn;
struct RegisteredArity {
fn_name: Arc<str>,
param_count: usize,
is_variadic: bool,
ir: Arc<IrFunction>,
}
struct RegisteredDefn {
arities: Vec<RegisteredArity>,
}
type DefnKey = (usize, Arc<str>, Arc<str>);
static REGISTRY: LazyLock<RwLock<HashMap<DefnKey, RegisteredDefn>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
static DEPENDENTS: LazyLock<RwLock<HashMap<(Arc<str>, Arc<str>), HashSet<u64>>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
static RELOWER: LazyLock<RwLock<HashSet<u64>>> = LazyLock::new(|| RwLock::new(HashSet::new()));
static RELOWER_PENDING: AtomicUsize = AtomicUsize::new(0);
fn arity_key(globals_id: usize, ns: &str, name: &str, param_count: usize) -> Arc<str> {
Arc::from(format!("{name}__{ns}__g{globals_id:x}__ext{param_count}").as_str())
}
pub fn register_defn(
globals_id: usize,
ns: &Arc<str>,
name: &Arc<str>,
arities: Vec<(usize, bool, Arc<IrFunction>)>,
) {
let entry = RegisteredDefn {
arities: arities
.into_iter()
.map(|(param_count, is_variadic, ir)| RegisteredArity {
fn_name: arity_key(globals_id, ns, name, param_count),
param_count,
is_variadic,
ir,
})
.collect(),
};
REGISTRY
.write()
.unwrap()
.insert((globals_id, ns.clone(), name.clone()), entry);
}
pub fn externals_for(
globals_id: usize,
referenced: &HashSet<(Arc<str>, Arc<str>)>,
) -> Vec<ExternalDefn> {
if referenced.is_empty() {
return Vec::new();
}
let registry = REGISTRY.read().unwrap();
let mut out = Vec::new();
for (ns, name) in referenced {
let Some(defn) = registry.get(&(globals_id, ns.clone(), name.clone())) else {
continue;
};
out.push(ExternalDefn {
ns: ns.clone(),
name: name.clone(),
arity_fn_names: defn.arities.iter().map(|a| a.fn_name.clone()).collect(),
param_counts: defn.arities.iter().map(|a| a.param_count).collect(),
is_variadic: defn.arities.iter().map(|a| a.is_variadic).collect(),
arity_irs: defn.arities.iter().map(|a| a.ir.clone()).collect(),
});
}
out
}
pub fn record_dependents(arity_id: u64, used: impl IntoIterator<Item = (Arc<str>, Arc<str>)>) {
let mut deps = DEPENDENTS.write().unwrap();
for key in used {
deps.entry(key).or_default().insert(arity_id);
}
}
pub fn snapshot_externals(
globals_id: usize,
arity_id: u64,
referenced: &HashSet<(Arc<str>, Arc<str>)>,
) -> Vec<ExternalDefn> {
if referenced.is_empty() {
return Vec::new();
}
let registry = REGISTRY.read().unwrap();
let mut out = Vec::new();
let mut deps = DEPENDENTS.write().unwrap();
for (ns, name) in referenced {
let Some(defn) = registry.get(&(globals_id, ns.clone(), name.clone())) else {
continue;
};
deps.entry((ns.clone(), name.clone()))
.or_default()
.insert(arity_id);
out.push(ExternalDefn {
ns: ns.clone(),
name: name.clone(),
arity_fn_names: defn.arities.iter().map(|a| a.fn_name.clone()).collect(),
param_counts: defn.arities.iter().map(|a| a.param_count).collect(),
is_variadic: defn.arities.iter().map(|a| a.is_variadic).collect(),
arity_irs: defn.arities.iter().map(|a| a.ir.clone()).collect(),
});
}
out
}
pub fn on_redefined(ns: &str, name: &str) -> Vec<u64> {
REGISTRY
.write()
.unwrap()
.retain(|(_, k_ns, k_name), _| !(k_ns.as_ref() == ns && k_name.as_ref() == name));
let drained = DEPENDENTS
.write()
.unwrap()
.remove(&(Arc::from(ns), Arc::from(name)));
let Some(deps) = drained else {
return Vec::new();
};
if !deps.is_empty() {
let mut relower = RELOWER.write().unwrap();
for &id in &deps {
if relower.insert(id) {
RELOWER_PENDING.fetch_add(1, Ordering::Release);
}
}
}
deps.into_iter().collect()
}
#[inline]
pub fn relower_pending() -> bool {
RELOWER_PENDING.load(Ordering::Acquire) != 0
}
pub fn relower_marked(arity_id: u64) -> bool {
RELOWER.read().unwrap().contains(&arity_id)
}
pub fn take_relower(arity_id: u64) -> bool {
let mut relower = RELOWER.write().unwrap();
if relower.remove(&arity_id) {
RELOWER_PENDING.fetch_sub(1, Ordering::Release);
true
} else {
false
}
}
pub fn install_invalidation_hook() {
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
cljrs_value::set_var_rebind_hook(|old, _new| {
let cljrs_value::Value::Fn(f) = old else {
return;
};
let f = f.get();
let Some(name) = f.name.as_deref() else {
return;
};
for dep in on_redefined(&f.defining_ns, name) {
crate::tiered::ir_cache::invalidate(dep);
crate::tiered::jit_state::stale_native_code(dep);
}
});
});
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_ir() -> Arc<IrFunction> {
Arc::new(IrFunction::new(None, None))
}
#[test]
fn snapshot_externals_records_edges_drained_by_rebind() {
let gid = 0xD500_0001usize;
let ns: Arc<str> = Arc::from("test.snapshot-ns");
let name: Arc<str> = Arc::from("callee-fn");
let dep_id = 0xD500_0002u64;
register_defn(gid, &ns, &name, vec![(1, false, dummy_ir())]);
let mut referenced = HashSet::new();
referenced.insert((ns.clone(), name.clone()));
referenced.insert((ns.clone(), Arc::from("never-registered")));
let externals = snapshot_externals(gid, dep_id, &referenced);
assert_eq!(externals.len(), 1);
assert_eq!(externals[0].name, name);
assert!(!relower_marked(dep_id));
let deps = on_redefined(&ns, &name);
assert!(deps.contains(&dep_id));
assert!(relower_marked(dep_id));
assert!(relower_marked(dep_id));
assert!(take_relower(dep_id));
assert!(!relower_marked(dep_id));
assert!(!take_relower(dep_id));
let externals = snapshot_externals(gid, dep_id, &referenced);
assert!(externals.is_empty());
}
}