Skip to main content

cljrs_value/
types.rs

1//! Stub types for Phase 4/7 that are referenced by the Value enum.
2
3#![allow(unused)]
4
5use crate::parking::Event;
6use std::collections::HashMap;
7use std::mem;
8use std::rc::Rc;
9use std::sync::{Arc, Condvar, Mutex};
10use std::time::{Duration, Instant};
11
12use cljrs_gc::GcPtr;
13use cljrs_gc::mutator::TrackedMutex;
14use cljrs_reader::Form;
15
16use crate::TypeHint;
17use crate::Value;
18
19// ── No-GC debug provenance helper ────────────────────────────────────────────
20
21/// In `no-gc` debug builds: return `true` if the top-level `GcPtr` inside
22/// `value` (if any) was allocated by the global `StaticArena`.
23///
24/// Primitives (`Nil`, `Bool`, `Long`, `Double`, `Char`) contain no `GcPtr`
25/// and always return `true`.  `Resource` is Arc-managed and also returns
26/// `true`.  All other variants have a `GcPtr` that is checked against the
27/// static arena's chunk range.
28///
29/// This check is intentionally **shallow** (top-level pointer only).  If the
30/// value was produced inside a `StaticCtxGuard`, ALL allocations during its
31/// evaluation go to the static arena — so a static top-level pointer implies
32/// static contents.
33#[cfg(all(feature = "no-gc", debug_assertions))]
34pub(crate) fn value_gcptr_is_static(value: &Value) -> bool {
35    use crate::value::MapValue;
36    use crate::value::SetValue;
37    match value {
38        // Inline scalars — no GcPtr.
39        Value::Nil
40        | Value::Bool(_)
41        | Value::Long(_)
42        | Value::Double(_)
43        | Value::Char(_)
44        | Value::Uuid(_) => true,
45        // Arc-managed — not GcPtr; always considered static.
46        Value::Resource(_) | Value::SharedAtom(_) | Value::ByteBlob(_) => true,
47        // GcPtr variants.
48        Value::BigInt(p) => p.is_static_alloc(),
49        Value::BigDecimal(p) => p.is_static_alloc(),
50        Value::Ratio(p) => p.is_static_alloc(),
51        Value::Str(p) => p.is_static_alloc(),
52        Value::Pattern(p) => p.is_static_alloc(),
53        Value::Matcher(p) => p.is_static_alloc(),
54        Value::Symbol(p) => p.is_static_alloc(),
55        Value::Keyword(p) => p.is_static_alloc(),
56        Value::List(p) => p.is_static_alloc(),
57        Value::Vector(p) => p.is_static_alloc(),
58        Value::Queue(p) => p.is_static_alloc(),
59        Value::Map(m) => match m {
60            MapValue::Array(p) => p.is_static_alloc(),
61            MapValue::Hash(p) => p.is_static_alloc(),
62            MapValue::Sorted(p) => p.is_static_alloc(),
63        },
64        Value::Set(s) => match s {
65            SetValue::Hash(p) => p.is_static_alloc(),
66            SetValue::Sorted(p) => p.is_static_alloc(),
67        },
68        Value::NativeFunction(p) => p.is_static_alloc(),
69        Value::Fn(p) | Value::Macro(p) => p.is_static_alloc(),
70        Value::BoundFn(p) => p.is_static_alloc(),
71        Value::Var(p) => p.is_static_alloc(),
72        Value::Atom(p) => p.is_static_alloc(),
73        Value::Namespace(p) => p.is_static_alloc(),
74        Value::LazySeq(p) => p.is_static_alloc(),
75        Value::Cons(p) => p.is_static_alloc(),
76        Value::Protocol(p) => p.is_static_alloc(),
77        Value::ProtocolFn(p) => p.is_static_alloc(),
78        Value::MultiFn(p) => p.is_static_alloc(),
79        Value::Volatile(p) => p.is_static_alloc(),
80        Value::Delay(p) => p.is_static_alloc(),
81        Value::Promise(p) => p.is_static_alloc(),
82        Value::Future(p) => p.is_static_alloc(),
83        Value::Agent(p) => p.is_static_alloc(),
84        Value::TypeInstance(p) => p.is_static_alloc(),
85        Value::ObjectArray(p) => p.is_static_alloc(),
86        Value::NativeObject(p) => p.is_static_alloc(),
87        Value::Error(p) => p.is_static_alloc(),
88        Value::TransientMap(p) => p.is_static_alloc(),
89        Value::TransientVector(p) => p.is_static_alloc(),
90        Value::TransientSet(p) => p.is_static_alloc(),
91        // Primitive arrays — no meaningful pointer check needed.
92        Value::BooleanArray(_)
93        | Value::ByteArray(_)
94        | Value::ShortArray(_)
95        | Value::IntArray(_)
96        | Value::LongArray(_)
97        | Value::FloatArray(_)
98        | Value::DoubleArray(_)
99        | Value::CharArray(_) => true,
100        // Wrapper variants.
101        Value::Reduced(inner) | Value::WithMeta(inner, _) => value_gcptr_is_static(inner),
102    }
103}
104
105// ── Protocol ──────────────────────────────────────────────────────────────────
106
107/// Inner map type for protocol implementations: method_name → impl fn.
108pub type MethodMap = HashMap<Arc<str>, Value>;
109
110/// A Clojure protocol — an interface-like construct with named methods.
111#[derive(Debug)]
112pub struct Protocol {
113    pub name: Arc<str>,
114    pub ns: Arc<str>,
115    pub methods: Vec<ProtocolMethod>,
116    /// type_tag → { method_name → impl fn }
117    pub impls: TrackedMutex<HashMap<Arc<str>, MethodMap>>,
118    /// `(defprotocol Name :extend-via-metadata true ...)` — when set, protocol
119    /// dispatch consults the dispatch value's metadata (keyed by this
120    /// protocol's `ProtocolFn`s) before falling back to type-tag impls.
121    pub extend_via_metadata: bool,
122}
123
124impl Protocol {
125    pub fn new(
126        name: Arc<str>,
127        ns: Arc<str>,
128        methods: Vec<ProtocolMethod>,
129        extend_via_metadata: bool,
130    ) -> Self {
131        Self {
132            name,
133            ns,
134            methods,
135            impls: TrackedMutex::new(HashMap::new()),
136            extend_via_metadata,
137        }
138    }
139}
140
141/// Global protocol-extension generation, bumped on every `impls` mutation
142/// (`extend-type`, `extend-protocol`, `defrecord`/`reify` inline impls).
143///
144/// Inline caches for protocol dispatch (Phase 10.6, `rt_call_ic` in
145/// `cljrs-compiler`'s rt_abi) tag each cached `(dispatch type → impl fn)`
146/// entry with the generation observed at fill time; a later bump invalidates
147/// every cache entry at once, so re-extending a protocol mid-session is
148/// picked up on the next dispatch through any call site.
149static PROTOCOL_GENERATION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
150
151/// Current protocol-extension generation (see [`bump_protocol_generation`]).
152pub fn protocol_generation() -> u64 {
153    PROTOCOL_GENERATION.load(std::sync::atomic::Ordering::Acquire)
154}
155
156/// Invalidate all protocol-dispatch inline caches.  Must be called after
157/// every mutation of any [`Protocol::impls`] map.
158pub fn bump_protocol_generation() {
159    PROTOCOL_GENERATION.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
160}
161
162impl cljrs_gc::Trace for Protocol {
163    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
164        {
165            let impls = self.impls.lock().unwrap();
166            for method_map in impls.values() {
167                for v in method_map.values() {
168                    v.trace(visitor);
169                }
170            }
171        }
172    }
173}
174
175/// One method signature declared in a `defprotocol`.
176#[derive(Debug, Clone)]
177pub struct ProtocolMethod {
178    pub name: Arc<str>,
179    pub min_arity: usize,
180    pub variadic: bool,
181}
182
183impl cljrs_gc::Trace for ProtocolMethod {
184    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
185}
186
187// ── ProtocolFn ────────────────────────────────────────────────────────────────
188
189/// Callable that dispatches a single protocol method on the type of `args[0]`.
190#[derive(Debug)]
191pub struct ProtocolFn {
192    pub protocol: GcPtr<Protocol>,
193    pub method_name: Arc<str>,
194    pub min_arity: usize,
195    pub variadic: bool,
196}
197
198impl cljrs_gc::Trace for ProtocolFn {
199    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
200        use cljrs_gc::GcVisitor as _;
201        visitor.visit(&self.protocol);
202    }
203}
204
205// ── MultiFn ───────────────────────────────────────────────────────────────────
206
207/// A Clojure multimethod — arbitrary dispatch via a user-supplied function.
208#[derive(Debug)]
209pub struct MultiFn {
210    pub name: Arc<str>,
211    pub dispatch_fn: Value,
212    /// pr_str(dispatch-val) → implementation fn
213    pub methods: TrackedMutex<HashMap<String, Value>>,
214    /// pr_str(dispatch-val) → the dispatch value itself, for `isa?` lookups
215    pub dispatch_vals: TrackedMutex<HashMap<String, Value>>,
216    /// recorded preferences: pr_str(preferred) → pr_str(over)
217    pub prefers: TrackedMutex<HashMap<String, Vec<String>>>,
218    /// pr_str(preference dispatch value) → original value, including values
219    /// which do not currently have a method.
220    pub preference_vals: TrackedMutex<HashMap<String, Value>>,
221    /// normally ":default"
222    pub default_dispatch: String,
223    method_generation: std::sync::atomic::AtomicU64,
224    method_cache: TrackedMutex<MultiFnMethodCache>,
225}
226
227#[derive(Debug, Default)]
228struct MultiFnMethodCache {
229    hierarchy_generation: u64,
230    method_generation: u64,
231    methods: HashMap<String, String>,
232}
233
234impl MultiFn {
235    pub fn new(name: Arc<str>, dispatch_fn: Value, default_dispatch: String) -> Self {
236        Self {
237            name,
238            dispatch_fn,
239            methods: TrackedMutex::new(HashMap::new()),
240            dispatch_vals: TrackedMutex::new(HashMap::new()),
241            prefers: TrackedMutex::new(HashMap::new()),
242            preference_vals: TrackedMutex::new(HashMap::new()),
243            default_dispatch,
244            method_generation: std::sync::atomic::AtomicU64::new(0),
245            method_cache: TrackedMutex::new(MultiFnMethodCache::default()),
246        }
247    }
248
249    /// Current generation of this multimethod's method and preference tables.
250    pub fn method_generation(&self) -> u64 {
251        self.method_generation
252            .load(std::sync::atomic::Ordering::Acquire)
253    }
254
255    /// Invalidate this multimethod's cached inherited/default resolutions.
256    pub fn bump_method_generation(&self) {
257        self.method_generation
258            .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
259    }
260
261    /// Return a cached resolved method key when both source generations match.
262    /// Observing either new generation lazily discards stale entries at once.
263    pub fn cached_method(
264        &self,
265        dispatch_key: &str,
266        hierarchy_generation: u64,
267        method_generation: u64,
268    ) -> Option<String> {
269        let mut cache = self.method_cache.lock().unwrap();
270        if cache.hierarchy_generation != hierarchy_generation
271            || cache.method_generation != method_generation
272        {
273            cache.hierarchy_generation = hierarchy_generation;
274            cache.method_generation = method_generation;
275            cache.methods.clear();
276        }
277        cache.methods.get(dispatch_key).cloned()
278    }
279
280    /// Cache `dispatch_key -> method_key` under the source generations used to
281    /// resolve it. A concurrent mutation makes the entry stale, so the next
282    /// lookup discards it; the write itself never needs to be suppressed.
283    pub fn cache_method(
284        &self,
285        dispatch_key: String,
286        method_key: String,
287        hierarchy_generation: u64,
288        method_generation: u64,
289    ) {
290        let mut cache = self.method_cache.lock().unwrap();
291        if cache.hierarchy_generation != hierarchy_generation
292            || cache.method_generation != method_generation
293        {
294            cache.hierarchy_generation = hierarchy_generation;
295            cache.method_generation = method_generation;
296            cache.methods.clear();
297        }
298        cache.methods.insert(dispatch_key, method_key);
299    }
300
301    /// Number of entries in the current cache, exposed for regression tests.
302    pub fn cached_method_count(&self) -> usize {
303        self.method_cache.lock().unwrap().methods.len()
304    }
305}
306
307impl cljrs_gc::Trace for MultiFn {
308    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
309        self.dispatch_fn.trace(visitor);
310        {
311            let methods = self.methods.lock().unwrap();
312            for v in methods.values() {
313                v.trace(visitor);
314            }
315        }
316        {
317            let dispatch_vals = self.dispatch_vals.lock().unwrap();
318            for v in dispatch_vals.values() {
319                v.trace(visitor);
320            }
321        }
322        {
323            let preference_vals = self.preference_vals.lock().unwrap();
324            for v in preference_vals.values() {
325                v.trace(visitor);
326            }
327        }
328    }
329}
330
331// ── Var ───────────────────────────────────────────────────────────────────────
332
333/// A Clojure var — a namespace-interned mutable root binding.
334///
335/// ## Two-tier root (Phase B3, issue #171)
336///
337/// A var's *root* binding uses the same two-tier mechanism as `shared-atom`:
338///
339/// - **`value`** — the isolate-local, GC-backed fast path.  Every var deref,
340///   the IR tier, and the JIT/AOT `rt_*` ABI read this slot; promotion never
341///   touches it, so compiled inline caches and pointer-identity assumptions
342///   baked into native code stay valid.
343/// - **`shared_root`** — a `Send + Sync` cross-isolate mirror,
344///   `Arc<ArcSwap<Option<SharedValue>>>`, reusing
345///   [`crate::shared::SharedValue`].  `bind` (i.e. `def` / `alter-var-root` /
346///   `set!`) *promotes-on-write*: if the new root value is promotable the cell
347///   holds `Some(SharedValue)`, otherwise it is cleared to `None` (option (b)
348///   of the ADR — non-promotable roots, e.g. closures, stay isolate-local).
349///
350/// The shared cell is what crosses the structured-clone boundary: a var
351/// `def`'d in one isolate is observable *by value* from another, with keyword
352/// /symbol identity preserved through the intern table.  See
353/// `crate::clone` for the serialize/deserialize seam.
354///
355/// Dynamic `binding` is unchanged — it is already thread-local / per-isolate
356/// and lives on the binding stack, not in the var root.
357#[derive(Debug)]
358pub struct Var {
359    pub namespace: Arc<str>,
360    pub name: Arc<str>,
361    pub value: TrackedMutex<Option<Value>>,
362    /// Cross-isolate mirror of the root binding (Phase B3).  `None` when the
363    /// var is unbound or its current root is not promotable.
364    pub shared_root: Arc<arc_swap::ArcSwap<Option<crate::shared::SharedValue>>>,
365    pub is_macro: bool,
366    /// Metadata map (e.g. `{:dynamic true}`).
367    pub meta: TrackedMutex<Option<Value>>,
368    pub watches: TrackedMutex<Vec<(Value, Value)>>,
369    binding_generation: std::sync::atomic::AtomicU64,
370}
371
372impl Var {
373    pub fn new(namespace: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
374        Self {
375            namespace: namespace.into(),
376            name: name.into(),
377            value: TrackedMutex::new(None),
378            shared_root: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
379            is_macro: false,
380            meta: TrackedMutex::new(None),
381            watches: TrackedMutex::new(Vec::new()),
382            binding_generation: std::sync::atomic::AtomicU64::new(0),
383        }
384    }
385
386    /// Reconstruct a var on the receiving side of an isolate boundary.
387    ///
388    /// The `shared_root` cell is the *same* `Arc` as the sending isolate's, so
389    /// both isolates share the cross-isolate root cell.  The local `value`
390    /// fast-path slot is seeded by demoting the current shared snapshot, so an
391    /// immediate `deref` observes the value the var carried at crossing time.
392    pub fn from_shared_root(
393        namespace: impl Into<Arc<str>>,
394        name: impl Into<Arc<str>>,
395        is_macro: bool,
396        shared_root: Arc<arc_swap::ArcSwap<Option<crate::shared::SharedValue>>>,
397    ) -> Self {
398        let local = shared_root
399            .load()
400            .as_ref()
401            .as_ref()
402            .map(crate::shared::demote);
403        Self {
404            namespace: namespace.into(),
405            name: name.into(),
406            value: TrackedMutex::new(local),
407            shared_root,
408            is_macro,
409            meta: TrackedMutex::new(None),
410            watches: TrackedMutex::new(Vec::new()),
411            binding_generation: std::sync::atomic::AtomicU64::new(0),
412        }
413    }
414
415    pub fn is_bound(&self) -> bool {
416        self.value.lock().unwrap().is_some()
417    }
418
419    pub fn deref(&self) -> Option<Value> {
420        self.value.lock().unwrap().clone()
421    }
422
423    /// Generation of the isolate-local root binding, incremented after each
424    /// completed [`Var::bind`].
425    pub fn binding_generation(&self) -> u64 {
426        self.binding_generation
427            .load(std::sync::atomic::Ordering::Acquire)
428    }
429
430    /// Read the cross-isolate root by demoting the shared cell, ignoring the
431    /// isolate-local fast path.  Returns `None` when the shared root is empty
432    /// (unbound or non-promotable).  Used to observe writes another isolate
433    /// made through the shared cell.
434    pub fn deref_shared(&self) -> Option<Value> {
435        self.shared_root
436            .load()
437            .as_ref()
438            .as_ref()
439            .map(crate::shared::demote)
440    }
441
442    pub fn bind(&self, v: Value) {
443        // In no-gc debug builds: assert the value being stored in this
444        // program-lifetime Var came from the StaticArena, not a scratch region.
445        // A region-local pointer would dangle after the function returns.
446        #[cfg(all(feature = "no-gc", debug_assertions))]
447        debug_assert!(
448            value_gcptr_is_static(&v),
449            "no-gc: Var::bind({}/{}) received a region-local value — store violations \
450             indicate a missing StaticCtxGuard around the value expression",
451            self.namespace,
452            self.name
453        );
454        // GC builds: heap-promotion fallback — a region-allocated value bound
455        // to a program-lifetime var is deep-copied to the heap (or the active
456        // regions are retired when it cannot be).  One depth check when no
457        // region is open.
458        let v = crate::publish::publish_value(v);
459        // Replace the binding, holding the lock only across the swap.  The
460        // previous value (if any) is handed to the JIT rebind hook so it can
461        // reclaim native code compiled for a now-superseded definition
462        // (Phase 10.2 — code unloading).  `v.clone()` is O(1) for the only
463        // values that carry compiled code (`Value::Fn`, a `GcPtr` clone).
464        let prev = {
465            let mut slot = self.value.lock().unwrap();
466            slot.replace(v.clone())
467        };
468        // Promote-on-`def` (Phase B3): mirror the new root into the
469        // cross-isolate cell when it is promotable, else clear the cell so it
470        // never advertises a stale or non-shareable root.  `def` is rare and
471        // global by nature, so this write-path cost is acceptable; the read
472        // path (and the JIT) never touch the shared cell.
473        let shared = crate::shared::promote(&v).ok();
474        self.shared_root.store(Arc::new(shared));
475        self.binding_generation
476            .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
477        if let Some(prev) = prev {
478            crate::jit_hooks::notify_var_rebind(&prev, &v);
479        }
480    }
481
482    pub fn get_meta(&self) -> Option<Value> {
483        self.meta.lock().unwrap().clone()
484    }
485
486    pub fn set_meta(&self, m: Value) {
487        *self.meta.lock().unwrap() = Some(m);
488    }
489
490    pub fn full_name(&self) -> String {
491        format!("{}/{}", self.namespace, self.name)
492    }
493}
494
495impl cljrs_gc::Trace for Var {
496    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
497        {
498            let value = self.value.lock().unwrap();
499            if let Some(v) = value.as_ref() {
500                v.trace(visitor);
501            }
502        }
503        {
504            let meta = self.meta.lock().unwrap();
505            if let Some(m) = meta.as_ref() {
506                m.trace(visitor);
507            }
508        }
509        {
510            let watches = self.watches.lock().unwrap();
511            for (key, f) in watches.iter() {
512                key.trace(visitor);
513                f.trace(visitor);
514            }
515        }
516    }
517}
518
519// ── Atom ──────────────────────────────────────────────────────────────────────
520
521/// A Clojure atom — a thread-safe mutable reference.
522#[derive(Debug)]
523pub struct Atom {
524    pub value: TrackedMutex<Value>,
525    pub meta: TrackedMutex<Option<Value>>,
526    pub validator: TrackedMutex<Option<Value>>,
527    pub watches: TrackedMutex<Vec<(Value, Value)>>,
528}
529
530impl Atom {
531    pub fn new(v: Value) -> Self {
532        // Heap-promotion fallback (GC builds): an atom is program-lifetime
533        // shared state, so its initial value must not be region-allocated.
534        let v = crate::publish::publish_value(v);
535        Self {
536            value: TrackedMutex::new(v),
537            meta: TrackedMutex::new(None),
538            validator: TrackedMutex::new(None),
539            watches: TrackedMutex::new(Vec::new()),
540        }
541    }
542
543    pub fn deref(&self) -> Value {
544        self.value.lock().unwrap().clone()
545    }
546
547    pub fn reset(&self, v: Value) -> Value {
548        // In no-gc debug builds: assert the new value came from the StaticArena.
549        #[cfg(all(feature = "no-gc", debug_assertions))]
550        debug_assert!(
551            value_gcptr_is_static(&v),
552            "no-gc: Atom::reset() received a region-local value — the new-value \
553             expression must be computed inside a StaticCtxGuard (i.e. inside \
554             the swap! / reset! call) so it is allocated in the static arena"
555        );
556        // GC builds: heap-promotion fallback (see `Var::bind`).
557        let v = crate::publish::publish_value(v);
558        let mut guard = self.value.lock().unwrap();
559        *guard = v.clone();
560        v
561    }
562
563    pub fn get_meta(&self) -> Option<Value> {
564        self.meta.lock().unwrap().clone()
565    }
566
567    pub fn set_meta(&self, m: Option<Value>) {
568        *self.meta.lock().unwrap() = m;
569    }
570
571    pub fn get_validator(&self) -> Option<Value> {
572        self.validator.lock().unwrap().clone()
573    }
574
575    pub fn set_validator(&self, vf: Option<Value>) {
576        *self.validator.lock().unwrap() = vf;
577    }
578}
579
580impl cljrs_gc::Trace for Atom {
581    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
582        {
583            let value = self.value.lock().unwrap();
584            value.trace(visitor);
585        }
586        {
587            let meta = self.meta.lock().unwrap();
588            if let Some(m) = meta.as_ref() {
589                m.trace(visitor);
590            }
591        }
592        {
593            let validator = self.validator.lock().unwrap();
594            if let Some(vf) = validator.as_ref() {
595                vf.trace(visitor);
596            }
597        }
598        {
599            let watches = self.watches.lock().unwrap();
600            for (key, f) in watches.iter() {
601                key.trace(visitor);
602                f.trace(visitor);
603            }
604        }
605    }
606}
607
608// ── Namespace ─────────────────────────────────────────────────────────────────
609
610/// Which `clojure.core` names a namespace auto-refers, as narrowed by an
611/// `(:refer-clojure ...)` clause in `ns`.  An absent filter (the default)
612/// refers every public core name.
613#[derive(Debug, Clone, Default)]
614pub struct ReferClojureFilter {
615    /// `:only` — when set, no core name outside this set is referred.
616    pub only: Option<std::collections::HashSet<Arc<str>>>,
617    /// `:exclude` — core names that are never referred.
618    pub exclude: std::collections::HashSet<Arc<str>>,
619    /// `:rename` — core name → the local name it is referred under.  A renamed
620    /// name is *not* also referred under its original name (matching
621    /// `clojure.core/refer`).
622    pub rename: HashMap<Arc<str>, Arc<str>>,
623}
624
625impl ReferClojureFilter {
626    /// The local name `name` is referred under, or `None` when this filter
627    /// drops it.
628    pub fn local_name(&self, name: &Arc<str>) -> Option<Arc<str>> {
629        if self.exclude.contains(name) {
630            return None;
631        }
632        if let Some(only) = &self.only
633            && !only.contains(name)
634        {
635            return None;
636        }
637        Some(
638            self.rename
639                .get(name)
640                .cloned()
641                .unwrap_or_else(|| name.clone()),
642        )
643    }
644}
645
646/// A Clojure namespace with intern table, refers, and aliases.
647#[derive(Debug)]
648pub struct Namespace {
649    pub name: Arc<str>,
650    /// Vars interned directly in this namespace.
651    pub interns: TrackedMutex<HashMap<Arc<str>, GcPtr<Var>>>,
652    /// Vars referred from other namespaces (e.g. clojure.core).
653    pub refers: TrackedMutex<HashMap<Arc<str>, GcPtr<Var>>>,
654    /// Namespace aliases: short-name → full namespace name.
655    pub aliases: TrackedMutex<HashMap<Arc<str>, Arc<str>>>,
656    /// Absolute path of the source file this namespace was loaded from,
657    /// populated by the loader.  Used by the versioned resolver to locate the
658    /// file for `git show`.
659    pub source_file: TrackedMutex<Option<Arc<str>>>,
660    /// Absolute path of the git repository root that contains `source_file`.
661    pub git_repo_root: TrackedMutex<Option<Arc<str>>>,
662    /// `true` for namespaces loaded from a specific commit (`name@hash`).
663    /// Versioned namespaces are immutable: `intern()` will refuse new bindings.
664    pub is_versioned: bool,
665    /// Metadata attached via `(ns ^{...} name ...)` or an `ns` attr-map.
666    pub meta: TrackedMutex<Option<Value>>,
667    /// Narrowing applied to the automatic `clojure.core` refer, set by an
668    /// `(:refer-clojure ...)` clause.  `None` refers all of core.
669    pub refer_clojure_filter: TrackedMutex<Option<ReferClojureFilter>>,
670}
671
672impl Namespace {
673    pub fn new(name: impl Into<Arc<str>>) -> Self {
674        Self {
675            name: name.into(),
676            interns: TrackedMutex::new(HashMap::new()),
677            refers: TrackedMutex::new(HashMap::new()),
678            aliases: TrackedMutex::new(HashMap::new()),
679            source_file: TrackedMutex::new(None),
680            git_repo_root: TrackedMutex::new(None),
681            is_versioned: false,
682            meta: TrackedMutex::new(None),
683            refer_clojure_filter: TrackedMutex::new(None),
684        }
685    }
686
687    /// Create a versioned (immutable) namespace for `name@commit`.
688    pub fn new_versioned(name: impl Into<Arc<str>>) -> Self {
689        Self {
690            is_versioned: true,
691            ..Self::new(name)
692        }
693    }
694
695    /// Record the source file path and its git repo root (if in a repo).
696    pub fn set_source_location(&self, file: &str, repo_root: Option<&str>) {
697        *self.source_file.lock().unwrap() = Some(Arc::from(file));
698        *self.git_repo_root.lock().unwrap() = repo_root.map(Arc::from);
699    }
700
701    pub fn get_meta(&self) -> Option<Value> {
702        self.meta.lock().unwrap().clone()
703    }
704
705    pub fn set_meta(&self, m: Value) {
706        *self.meta.lock().unwrap() = Some(m);
707    }
708}
709
710impl cljrs_gc::Trace for Namespace {
711    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
712        use cljrs_gc::GcVisitor as _;
713        {
714            let interns = self.interns.lock().unwrap();
715            for var in interns.values() {
716                visitor.visit(var);
717            }
718        }
719        {
720            let refers = self.refers.lock().unwrap();
721            for var in refers.values() {
722                visitor.visit(var);
723            }
724        }
725        {
726            let meta = self.meta.lock().unwrap();
727            if let Some(m) = meta.as_ref() {
728                m.trace(visitor);
729            }
730        }
731    }
732}
733
734// ── NativeFn ──────────────────────────────────────────────────────────────────
735
736/// A Rust function callable from Clojure.
737/// Legacy type alias kept for source compatibility. Bare `fn` pointers
738/// implement `Fn` and can be passed anywhere a `NativeFnFunc` is expected.
739pub type NativeFnPtr = fn(&[Value]) -> crate::error::ValueResult<Value>;
740
741/// The callable stored inside a `NativeFn`. Supports both bare function
742/// pointers and closures that capture state.
743pub type NativeFnFunc = Arc<dyn Fn(&[Value]) -> crate::error::ValueResult<Value>>;
744
745#[derive(Clone, Debug)]
746pub enum Arity {
747    Fixed(usize),
748    Variadic { min: usize },
749}
750
751pub struct NativeFn {
752    pub name: Arc<str>,
753    pub arity: Arity,
754    pub func: NativeFnFunc,
755    /// Optional adapter that marshals owned inputs and offloads native work.
756    pub blocking_func: Option<NativeFnFunc>,
757}
758
759impl NativeFn {
760    /// Create from a bare function pointer (backwards-compatible).
761    pub fn new(name: impl Into<Arc<str>>, arity: Arity, func: NativeFnPtr) -> Self {
762        Self {
763            name: name.into(),
764            arity,
765            blocking_func: None,
766            func: Arc::new(move |args| {
767                crate::realization::catch(|| func(args))
768                    .unwrap_or_else(|message| Err(crate::ValueError::Other(message)))
769            }),
770        }
771    }
772
773    /// Create from a closure or any `Fn(&[Value]) -> ValueResult<Value>`.
774    pub fn with_closure(
775        name: impl Into<Arc<str>>,
776        arity: Arity,
777        func: impl Fn(&[Value]) -> crate::error::ValueResult<Value> + 'static,
778    ) -> Self {
779        Self {
780            name: name.into(),
781            arity,
782            blocking_func: None,
783            func: Arc::new(move |args| {
784                crate::realization::catch(|| func(args))
785                    .unwrap_or_else(|message| Err(crate::ValueError::Other(message)))
786            }),
787        }
788    }
789
790    /// Create a native function with a separate owned-work blocking adapter.
791    /// The adapter must keep managed conversions on the caller's isolate.
792    pub fn with_blocking(
793        name: impl Into<Arc<str>>,
794        arity: Arity,
795        func: impl Fn(&[Value]) -> crate::ValueResult<Value> + 'static,
796        blocking_func: impl Fn(&[Value]) -> crate::ValueResult<Value> + 'static,
797    ) -> Self {
798        let mut native = Self::with_closure(name, arity, func);
799        native.blocking_func = Some(Arc::new(move |args| {
800            crate::realization::catch(|| blocking_func(args))
801                .unwrap_or_else(|message| Err(crate::ValueError::Other(message)))
802        }));
803        native
804    }
805
806    /// Call the blocking adapter. Reject unsupported functions before execution.
807    pub fn call_blocking(&self, args: &[Value]) -> crate::ValueResult<Value> {
808        let expected = match self.arity {
809            Arity::Fixed(n) if args.len() != n => Some(n.to_string()),
810            Arity::Variadic { min } if args.len() < min => Some(format!("{min}+")),
811            _ => None,
812        };
813        if let Some(expected) = expected {
814            return Err(crate::ValueError::ArityError {
815                name: self.name.to_string(),
816                expected,
817                got: args.len(),
818            });
819        }
820        let adapter = self.blocking_func.as_ref().ok_or_else(|| {
821            crate::ValueError::Other(format!(
822                "blocking cannot offload {}: this function has no owned-work adapter",
823                self.name
824            ))
825        })?;
826        adapter(args)
827    }
828}
829
830impl std::fmt::Debug for NativeFn {
831    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
832        f.debug_struct("NativeFn")
833            .field("name", &self.name)
834            .field("arity", &self.arity)
835            .field("func", &"<fn>")
836            .finish()
837    }
838}
839
840impl cljrs_gc::Trace for NativeFn {
841    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
842}
843
844// ── CljxFnArity ───────────────────────────────────────────────────────────────
845
846/// One arity branch of a Clojure function.
847#[derive(Debug, Clone)]
848pub struct CljxFnArity {
849    /// Simple parameter names (no `&`).
850    /// For destructured params, these are gensym'd names.
851    pub params: Vec<Arc<str>>,
852    /// The name after `&`, if any.
853    pub rest_param: Option<Arc<str>>,
854    /// The body forms for this arity.
855    pub body: Vec<Form>,
856    /// Destructuring patterns: (param_index, original_form).
857    /// After binding the gensym'd param, these patterns are applied
858    /// via `bind_pattern` to destructure the value.
859    pub destructure_params: Vec<(usize, Form)>,
860    /// If the rest param is destructured, the original form.
861    pub destructure_rest: Option<Form>,
862    /// Unique ID for IR cache lookup (assigned by the evaluator).
863    pub ir_arity_id: u64,
864    /// Optional primitive type hint per positional parameter (parallel to
865    /// `params`).  `^long x` → `Some(TypeHint::Long)`; an un-hinted or
866    /// non-primitive-tagged param → `None`.  Drives unboxed codegen.
867    pub param_hints: Vec<Option<TypeHint>>,
868    /// Primitive type hint on the rest param, if any (rarely useful, but parsed
869    /// for symmetry).
870    pub rest_hint: Option<TypeHint>,
871}
872
873impl CljxFnArity {
874    /// Heap bytes owned by this arity, not counting the `CljxFnArity` struct itself.
875    pub fn heap_size(&self) -> usize {
876        // params Vec buffer (Arc<str> pointers; the str data is shared, skip it)
877        self.params.capacity() * mem::size_of::<Arc<str>>()
878        // body: the dominant consumer — Form AST trees stored inline
879        + self.body.capacity() * mem::size_of::<Form>()
880        + self.body.iter().map(|f| f.heap_size()).sum::<usize>()
881        // destructure_params
882        + self.destructure_params.capacity() * mem::size_of::<(usize, Form)>()
883        + self.destructure_params.iter().map(|(_, f)| f.heap_size()).sum::<usize>()
884        // destructure_rest
885        + self.destructure_rest.as_ref()
886            .map_or(0, |f| mem::size_of::<Form>() + f.heap_size())
887        // param_hints (Copy elements, no nested heap)
888        + self.param_hints.capacity() * mem::size_of::<Option<TypeHint>>()
889    }
890}
891
892// ── CljxFn ────────────────────────────────────────────────────────────────────
893
894/// An interpreted Clojure closure with captured environment.
895#[derive(Debug, Clone)]
896pub struct CljxFn {
897    pub name: Option<Arc<str>>,
898    pub arities: Vec<CljxFnArity>,
899    /// Names of closed-over bindings (parallel to `closed_over_vals`).
900    pub closed_over_names: Vec<Arc<str>>,
901    /// Values of closed-over bindings (parallel to `closed_over_names`).
902    pub closed_over_vals: Vec<Value>,
903    /// True if this function was defined with `defmacro`.
904    pub is_macro: bool,
905    /// True if this function carries `^:async` metadata. When an async runtime
906    /// (`cljrs-async`) is registered, calling such a function spawns its body as
907    /// a task and returns a `Value::Future` immediately instead of running it
908    /// synchronously. Without a runtime it runs synchronously like any other fn.
909    pub is_async: bool,
910    /// Namespace in which this function was defined (for macro hygiene).
911    pub defining_ns: Arc<str>,
912    /// Back-pointer to the `GcPtr` that owns this `CljxFn`, set immediately
913    /// after allocation so that a named anonymous function's self-reference
914    /// (e.g. `(fn g [] g)`) returns the *identical* pointer to the caller,
915    /// preserving pointer-equality semantics (`(= f (f))` → `true`).
916    pub self_ptr: Option<GcPtr<CljxFn>>,
917}
918
919impl CljxFn {
920    pub fn new(
921        name: Option<Arc<str>>,
922        arities: Vec<CljxFnArity>,
923        closed_over_names: Vec<Arc<str>>,
924        closed_over_vals: Vec<Value>,
925        is_macro: bool,
926        defining_ns: Arc<str>,
927    ) -> Self {
928        Self {
929            name,
930            arities,
931            closed_over_names,
932            closed_over_vals,
933            is_macro,
934            is_async: false,
935            defining_ns,
936            self_ptr: None,
937        }
938    }
939}
940
941impl cljrs_gc::Trace for CljxFn {
942    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
943        use cljrs_gc::GcVisitor as _;
944        for v in &self.closed_over_vals {
945            v.trace(visitor);
946        }
947        if let Some(ref p) = self.self_ptr {
948            visitor.visit(p);
949        }
950    }
951
952    fn gc_size_extra(&self) -> usize {
953        // Vec<CljxFnArity> buffer + each arity's inline-owned heap
954        self.arities.capacity() * mem::size_of::<CljxFnArity>()
955            + self
956                .arities
957                .iter()
958                .map(CljxFnArity::heap_size)
959                .sum::<usize>()
960    }
961}
962
963// ── BoundFn ──────────────────────────────────────────────────────────────────
964
965/// A function wrapped with captured dynamic bindings.
966/// When called, the captured bindings are pushed as a frame before delegating
967/// to the wrapped function. This means captured bindings override the caller's
968/// for the same var, but vars not in the capture fall through normally.
969#[derive(Debug)]
970pub struct BoundFn {
971    /// The wrapped callable.
972    pub wrapped: Value,
973    /// Captured dynamic bindings (merged flat frame; opaque to cljrs-value).
974    pub captured_bindings: HashMap<usize, Value>,
975}
976
977impl cljrs_gc::Trace for BoundFn {
978    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
979        self.wrapped.trace(visitor);
980        for val in self.captured_bindings.values() {
981            val.trace(visitor);
982        }
983    }
984
985    fn gc_size_extra(&self) -> usize {
986        // HashMap<usize, Value>: hashbrown open-addressing, ~1 control byte + entry per slot.
987        self.captured_bindings.capacity() * (1 + mem::size_of::<usize>() + mem::size_of::<Value>())
988    }
989}
990
991// ── Thunk / LazySeq ───────────────────────────────────────────────────────────
992
993/// A deferred computation that produces a `Value` when forced.
994pub trait Thunk: std::fmt::Debug + cljrs_gc::Trace {
995    fn force(&self) -> Result<Value, String>;
996}
997
998/// Identifies a computation by its isolate thread and optional fiber.
999#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1000pub struct Realizer {
1001    thread: std::thread::ThreadId,
1002    fiber: Option<u64>,
1003}
1004impl Realizer {
1005    pub fn current() -> Self {
1006        Self {
1007            thread: std::thread::current().id(),
1008            fiber: crate::parking::current(),
1009        }
1010    }
1011
1012    /// Reject a carrier wait that would prevent this owner's fiber resuming.
1013    /// A different fiber can park; a different thread can block normally.
1014    pub fn ensure_can_wait(self) -> Result<(), String> {
1015        if self.thread == std::thread::current().id()
1016            && self.fiber.is_some()
1017            && crate::parking::current().is_none()
1018        {
1019            Err("cannot block the isolate thread waiting for a parked fiber. Run this code inside a fiber.".into())
1020        } else {
1021            Ok(())
1022        }
1023    }
1024}
1025
1026// Cancellation/panic returns the original thunk to Pending for another reader.
1027struct RealizationGuard<'a> {
1028    lazy: Option<&'a LazySeq>,
1029    delay: Option<&'a Delay>,
1030}
1031impl Drop for RealizationGuard<'_> {
1032    fn drop(&mut self) {
1033        if let Some(cell) = self.lazy {
1034            let mut state = cell.state.lock().unwrap();
1035            if let LazySeqState::Realizing { thunk, .. } = &*state {
1036                *state = LazySeqState::Pending(thunk.clone());
1037            }
1038            drop(state);
1039            cell.ready.notify_all();
1040        }
1041        if let Some(cell) = self.delay {
1042            let mut state = cell.state.lock().unwrap();
1043            if let DelayState::Realizing { thunk, .. } = &*state {
1044                *state = DelayState::Pending(thunk.clone());
1045            }
1046            drop(state);
1047            cell.ready.notify_all();
1048        }
1049    }
1050}
1051
1052/// Internal state of a lazy sequence cell.
1053pub enum LazySeqState {
1054    /// Thunk not yet evaluated.
1055    Pending(Rc<dyn Thunk>),
1056    Realizing {
1057        owner: Realizer,
1058        thunk: Rc<dyn Thunk>,
1059    },
1060    /// Result cached after first force.
1061    Forced(Value),
1062    /// Thunk evaluation failed; error message is cached.
1063    Error(String),
1064}
1065
1066/// A lazy sequence that caches its result and retries interrupted realizations.
1067pub struct LazySeq {
1068    ready: Event,
1069    pub state: TrackedMutex<LazySeqState>,
1070}
1071
1072impl LazySeq {
1073    pub fn new(thunk: Box<dyn Thunk>) -> Self {
1074        Self {
1075            state: TrackedMutex::new(LazySeqState::Pending(Rc::from(thunk))),
1076            ready: Event::default(),
1077        }
1078    }
1079
1080    /// Realize the sequence: force the thunk on first call, return cached value on subsequent calls.
1081    /// Cached thunk errors return nil and remain available through `error()`.
1082    /// Panics if a carrier wait would prevent the owning fiber resuming.
1083    /// Use `try_realize` when the caller can return an error.
1084    pub fn realize(&self) -> Value {
1085        match self.try_realize() {
1086            Ok(value) => value,
1087            Err(error) => {
1088                // Infallible trait callers cannot return a transient wait
1089                // error. Fail explicitly instead of silently losing data.
1090                assert!(self.error().is_some(), "{error}");
1091                Value::Nil
1092            }
1093        }
1094    }
1095
1096    /// Realize a sequence with explicit errors, including unsafe carrier waits.
1097    /// A rejected wait does not change the thunk or its realization state.
1098    pub fn try_realize(&self) -> Result<Value, String> {
1099        let owner = Realizer::current();
1100        let thunk = loop {
1101            let listener = self.ready.listen();
1102            let mut state = self.state.lock().unwrap();
1103            match &*state {
1104                LazySeqState::Forced(v) => return Ok(v.clone()),
1105                LazySeqState::Error(error) => return Err(error.clone()),
1106                LazySeqState::Realizing { owner: active, .. } => {
1107                    if *active == owner {
1108                        return Ok(Value::Nil);
1109                    }
1110                    active.ensure_can_wait()?;
1111                    drop(state);
1112                    listener.wait(None);
1113                    continue;
1114                }
1115                LazySeqState::Pending(_) => {}
1116            }
1117            let LazySeqState::Pending(thunk) =
1118                mem::replace(&mut *state, LazySeqState::Forced(Value::Nil))
1119            else {
1120                unreachable!()
1121            };
1122            *state = LazySeqState::Realizing {
1123                owner,
1124                thunk: thunk.clone(),
1125            };
1126            break thunk;
1127        };
1128        let mut guard = RealizationGuard {
1129            lazy: Some(self),
1130            delay: None,
1131        };
1132        let result = match thunk.force() {
1133            Ok(result) => {
1134                let result = crate::publish::publish_value(result);
1135                *self.state.lock().unwrap() = LazySeqState::Forced(result.clone());
1136                Ok(result)
1137            }
1138            Err(msg) => {
1139                *self.state.lock().unwrap() = LazySeqState::Error(msg.clone());
1140                Err(msg)
1141            }
1142        };
1143        guard.lazy = None;
1144        self.ready.notify_all();
1145        result
1146    }
1147
1148    /// Return the cached error message, if the thunk failed.
1149    pub fn error(&self) -> Option<String> {
1150        let guard = self.state.lock().unwrap();
1151        if let LazySeqState::Error(e) = &*guard {
1152            Some(e.clone())
1153        } else {
1154            None
1155        }
1156    }
1157}
1158
1159impl std::fmt::Debug for LazySeq {
1160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1161        write!(f, "LazySeq(...)")
1162    }
1163}
1164
1165impl cljrs_gc::Trace for LazySeq {
1166    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1167        // Safe to lock unconditionally: realize() drops the lock before entering
1168        // eval (thunk.force()), so the lock is never held across a GC safepoint.
1169        {
1170            let state = self.state.lock().unwrap();
1171            match &*state {
1172                LazySeqState::Pending(thunk) => thunk.trace(visitor),
1173                LazySeqState::Realizing { thunk, .. } => thunk.trace(visitor),
1174                LazySeqState::Forced(v) => v.trace(visitor),
1175                LazySeqState::Error(_) => {}
1176            }
1177        }
1178    }
1179}
1180
1181// ── CljxCons ──────────────────────────────────────────────────────────────────
1182
1183/// A lazy cons cell: head element + tail (may be a `LazySeq`, `List`, or `Nil`).
1184///
1185/// Used when `cons` is called with a `LazySeq` or `Cons` tail, enabling lazy
1186/// sequences without eagerly realizing them.
1187#[derive(Debug, Clone)]
1188pub struct CljxCons {
1189    pub head: Value,
1190    pub tail: Value,
1191}
1192
1193impl cljrs_gc::Trace for CljxCons {
1194    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1195        self.head.trace(visitor);
1196        self.tail.trace(visitor);
1197    }
1198}
1199
1200// ── Volatile ──────────────────────────────────────────────────────────────────
1201
1202/// Non-atomic mutable cell (single-thread performance, no CAS).
1203pub struct Volatile {
1204    pub value: TrackedMutex<Value>,
1205}
1206
1207impl Volatile {
1208    pub fn new(v: Value) -> Self {
1209        // GC builds: heap-promotion fallback (see `Var::bind`).
1210        let v = crate::publish::publish_value(v);
1211        Self {
1212            value: TrackedMutex::new(v),
1213        }
1214    }
1215
1216    pub fn deref(&self) -> Value {
1217        self.value.lock().unwrap().clone()
1218    }
1219
1220    pub fn reset(&self, v: Value) -> Value {
1221        // In no-gc debug builds: assert the new value came from the StaticArena.
1222        #[cfg(all(feature = "no-gc", debug_assertions))]
1223        debug_assert!(
1224            value_gcptr_is_static(&v),
1225            "no-gc: Volatile::reset() received a region-local value — ensure the \
1226             new-value expression is inside a StaticCtxGuard (vreset! handles this)"
1227        );
1228        // GC builds: heap-promotion fallback (see `Var::bind`).
1229        let v = crate::publish::publish_value(v);
1230        *self.value.lock().unwrap() = v.clone();
1231        v
1232    }
1233}
1234
1235impl std::fmt::Debug for Volatile {
1236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1237        write!(f, "Volatile")
1238    }
1239}
1240
1241impl cljrs_gc::Trace for Volatile {
1242    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1243        {
1244            let value = self.value.lock().unwrap();
1245            value.trace(visitor);
1246        }
1247    }
1248}
1249
1250// ── Delay ─────────────────────────────────────────────────────────────────────
1251
1252/// Internal state of a delay cell.
1253pub enum DelayState {
1254    Error(String),
1255    Pending(Rc<dyn Thunk>),
1256    Realizing {
1257        owner: Realizer,
1258        thunk: Rc<dyn Thunk>,
1259    },
1260    Forced(Value),
1261}
1262
1263/// A lazy computation with a cached result and retry after interruption.
1264pub struct Delay {
1265    ready: Event,
1266    pub state: TrackedMutex<DelayState>,
1267}
1268
1269impl Delay {
1270    pub fn new(thunk: Box<dyn Thunk>) -> Self {
1271        Self {
1272            state: TrackedMutex::new(DelayState::Pending(Rc::from(thunk))),
1273            ready: Event::default(),
1274        }
1275    }
1276
1277    /// Force the delay and cache the result.
1278    /// Returns the value on success, or an error message on failure.
1279    pub fn force(&self) -> Result<Value, String> {
1280        let owner = Realizer::current();
1281        let thunk = loop {
1282            let listener = self.ready.listen();
1283            let mut state = self.state.lock().unwrap();
1284            match &*state {
1285                DelayState::Forced(v) => return Ok(v.clone()),
1286                DelayState::Error(e) => return Err(e.clone()),
1287                DelayState::Realizing { owner: active, .. } => {
1288                    if *active == owner {
1289                        return Err("recursive delay realization".into());
1290                    }
1291                    active.ensure_can_wait()?;
1292                    drop(state);
1293                    listener.wait(None);
1294                    continue;
1295                }
1296                DelayState::Pending(_) => {}
1297            }
1298            let DelayState::Pending(thunk) =
1299                mem::replace(&mut *state, DelayState::Forced(Value::Nil))
1300            else {
1301                unreachable!()
1302            };
1303            *state = DelayState::Realizing {
1304                owner,
1305                thunk: thunk.clone(),
1306            };
1307            break thunk;
1308        };
1309        let mut guard = RealizationGuard {
1310            lazy: None,
1311            delay: Some(self),
1312        };
1313        let result = thunk.force().map(crate::publish::publish_value);
1314        *self.state.lock().unwrap() = match &result {
1315            Ok(value) => DelayState::Forced(value.clone()),
1316            Err(error) => DelayState::Error(error.clone()),
1317        };
1318        guard.delay = None;
1319        self.ready.notify_all();
1320        result
1321    }
1322
1323    /// True if the delay has already been forced.
1324    pub fn is_realized(&self) -> bool {
1325        matches!(
1326            &*self.state.lock().unwrap(),
1327            DelayState::Forced(_) | DelayState::Error(_)
1328        )
1329    }
1330}
1331
1332impl std::fmt::Debug for Delay {
1333    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1334        write!(f, "Delay")
1335    }
1336}
1337
1338impl cljrs_gc::Trace for Delay {
1339    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1340        // Safe to lock unconditionally: force() drops the lock before entering
1341        // eval (thunk.force()), so the lock is never held across a GC safepoint.
1342        {
1343            let state = self.state.lock().unwrap();
1344            match &*state {
1345                DelayState::Pending(thunk) => thunk.trace(visitor),
1346                DelayState::Realizing { thunk, .. } => thunk.trace(visitor),
1347                DelayState::Error(_) => {}
1348                DelayState::Forced(v) => v.trace(visitor),
1349            }
1350        }
1351    }
1352}
1353
1354// ── CljxPromise ───────────────────────────────────────────────────────────────
1355
1356/// A one-shot rendezvous (promise).
1357pub struct CljxPromise {
1358    pub ready: Event,
1359    pub value: Mutex<Option<Value>>,
1360    pub cond: Condvar,
1361}
1362
1363impl CljxPromise {
1364    pub fn new() -> Self {
1365        Self {
1366            value: Mutex::new(None),
1367            cond: Condvar::new(),
1368            ready: Event::default(),
1369        }
1370    }
1371
1372    /// Deliver a value (no-op if already delivered).
1373    pub fn deliver(&self, v: Value) {
1374        // GC builds: heap-promotion fallback — the promise may outlive (and be
1375        // read from outside) any region scope active at delivery time.
1376        let v = crate::publish::publish_value(v);
1377        let mut guard = self.value.lock().unwrap();
1378        if guard.is_none() {
1379            *guard = Some(v);
1380            self.cond.notify_all();
1381            drop(guard);
1382            self.ready.notify_all();
1383        }
1384    }
1385
1386    /// Block until a value is available, then return it.
1387    pub fn deref_blocking(&self) -> Value {
1388        self.wait_value(None).unwrap()
1389    }
1390
1391    pub fn wait_value(&self, timeout: Option<Duration>) -> Option<Value> {
1392        let deadline = timeout.map(|d| Instant::now() + d);
1393        loop {
1394            let listener = self.ready.listen();
1395            if let Some(value) = self.value.lock().unwrap().clone() {
1396                return Some(value);
1397            }
1398            if !listener.wait(deadline.map(|d| d.saturating_duration_since(Instant::now()))) {
1399                return self.value.lock().unwrap().clone();
1400            }
1401        }
1402    }
1403
1404    /// True if already delivered.
1405    pub fn is_realized(&self) -> bool {
1406        self.value.lock().unwrap().is_some()
1407    }
1408}
1409
1410impl Default for CljxPromise {
1411    fn default() -> Self {
1412        Self::new()
1413    }
1414}
1415
1416impl std::fmt::Debug for CljxPromise {
1417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1418        write!(f, "Promise")
1419    }
1420}
1421
1422impl cljrs_gc::Trace for CljxPromise {
1423    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1424        {
1425            let value = self.value.lock().unwrap();
1426            if let Some(v) = value.as_ref() {
1427                v.trace(visitor);
1428            }
1429        }
1430    }
1431}
1432
1433// ── CljxFuture ────────────────────────────────────────────────────────────────
1434
1435/// Thread-pool future state.
1436pub enum FutureState {
1437    Running,
1438    Done(Value),
1439    /// The future's body threw. Holds the thrown Clojure value (a
1440    /// `Value::Error`) so `await`/`deref` can re-throw it with its
1441    /// `ex-data`/`ex-cause` intact, rather than a stringified message.
1442    Failed(Value),
1443    /// The future exhausted the evaluation budget. Kept distinct from a
1444    /// thrown value so user-level catch clauses cannot intercept it.
1445    GasExhausted,
1446    /// The future's body failed with a managed-memory limit fault (strict
1447    /// governor mode). Kept distinct for the same reason as `GasExhausted`.
1448    MemoryLimitExceeded(cljrs_gc::MemoryLimitExceeded),
1449    Cancelled,
1450}
1451
1452/// The message carried by the error raised when reading a cancelled future.
1453pub const FUTURE_CANCELLED_MSG: &str = "future was cancelled";
1454
1455/// A future value computed asynchronously on another thread.
1456pub struct CljxFuture {
1457    pub ready: Event,
1458    pub state: Mutex<FutureState>,
1459    pub cond: Condvar,
1460    /// Set once a consumer has read the settled result (via `await`/`deref`).
1461    /// Used to warn about a `Failed` future that is discarded without anyone
1462    /// ever observing its error (the fire-and-forget footgun).
1463    observed: std::sync::atomic::AtomicBool,
1464}
1465
1466impl CljxFuture {
1467    pub fn new() -> Self {
1468        Self {
1469            state: Mutex::new(FutureState::Running),
1470            cond: Condvar::new(),
1471            ready: Event::default(),
1472            observed: std::sync::atomic::AtomicBool::new(false),
1473        }
1474    }
1475
1476    /// Wait for settlement, parking only when running on a fiber.
1477    pub fn wait_value(&self, timeout: Option<Duration>) -> crate::ValueResult<Option<Value>> {
1478        let deadline = timeout.map(|d| Instant::now() + d);
1479        loop {
1480            let listener = self.ready.listen();
1481            if let Some(result) = self.settled_value() {
1482                return result.map(Some);
1483            }
1484            if !listener.wait(deadline.map(|d| d.saturating_duration_since(Instant::now()))) {
1485                return self.settled_value().transpose();
1486            }
1487        }
1488    }
1489
1490    pub fn settled_value(&self) -> Option<crate::ValueResult<Value>> {
1491        use crate::ValueError;
1492        let state = self.state.lock().unwrap();
1493        let value = match &*state {
1494            FutureState::Running => return None,
1495            FutureState::Done(v) => Ok(v.clone()),
1496            FutureState::Failed(v) => Err(ValueError::Thrown(v.clone())),
1497            FutureState::GasExhausted => Err(ValueError::GasExhausted),
1498            FutureState::MemoryLimitExceeded(f) => Err(ValueError::MemoryLimitExceeded(*f)),
1499            FutureState::Cancelled => {
1500                drop(state);
1501                return Some(Err(ValueError::Thrown(Self::cancelled_error())));
1502            }
1503        };
1504        self.mark_observed();
1505        Some(value)
1506    }
1507
1508    /// True if done, failed, or cancelled (not still running).
1509    pub fn is_done(&self) -> bool {
1510        !matches!(&*self.state.lock().unwrap(), FutureState::Running)
1511    }
1512
1513    /// True if explicitly cancelled.
1514    pub fn is_cancelled(&self) -> bool {
1515        matches!(&*self.state.lock().unwrap(), FutureState::Cancelled)
1516    }
1517
1518    /// Mark a still-running future cancelled, waking anyone waiting on it.
1519    ///
1520    /// Returns false when it had already settled. The task itself is not
1521    /// interrupted; what is cancelled is the result.
1522    pub fn cancel(&self) -> bool {
1523        let mut state = self.state.lock().unwrap();
1524        if matches!(&*state, FutureState::Running) {
1525            *state = FutureState::Cancelled;
1526            self.cond.notify_all();
1527            drop(state);
1528            self.ready.notify_all();
1529            true
1530        } else {
1531            false
1532        }
1533    }
1534
1535    /// The catchable value every reader of a cancelled future raises.
1536    ///
1537    /// One shape for every path — the sync tree-walker's `await`/`deref`, the
1538    /// async tree-walker, and the compiled state machine all hand user code
1539    /// the same `ex-info`-like error, so a `catch` behaves the same whether or
1540    /// not the enclosing fn happened to be async-JIT-compiled.
1541    pub fn cancelled_error() -> Value {
1542        Value::Error(GcPtr::new(crate::ExceptionInfo::new(
1543            crate::ValueError::Other(FUTURE_CANCELLED_MSG.to_string()),
1544            FUTURE_CANCELLED_MSG.to_string(),
1545            None,
1546            None,
1547        )))
1548    }
1549
1550    /// Mark this future's result as observed. Call when a consumer reads the
1551    /// settled value (`await`/`deref`), so a later drop doesn't warn about an
1552    /// unobserved error.
1553    pub fn mark_observed(&self) {
1554        self.observed
1555            .store(true, std::sync::atomic::Ordering::Relaxed);
1556    }
1557}
1558
1559impl Drop for CljxFuture {
1560    fn drop(&mut self) {
1561        // Warn if a future failed but nobody ever observed the error — the
1562        // fire-and-forget case where a thrown error would otherwise vanish.
1563        // Tied to GC sweep timing: only fires once the future is unreachable,
1564        // so a not-yet-awaited (still reachable) failed future won't warn.
1565        //
1566        // SAFETY: this Drop can run during GC sweep. The thrown value held in
1567        // `Failed(v)` is itself a GC value whose backing box may be freed in
1568        // the *same* sweep, so we must NOT dereference it here (no `{v}`). We
1569        // only inspect the state discriminant, which is inline in our own
1570        // (still-valid) allocation.
1571        if !self.observed.load(std::sync::atomic::Ordering::Relaxed)
1572            && let Ok(state) = self.state.lock()
1573            && matches!(&*state, FutureState::Failed(_))
1574        {
1575            eprintln!(
1576                "[clojurust warning] a failed future was discarded without its error \
1577                 being observed (no await/deref); the thrown exception was lost"
1578            );
1579        }
1580    }
1581}
1582
1583impl Default for CljxFuture {
1584    fn default() -> Self {
1585        Self::new()
1586    }
1587}
1588
1589impl std::fmt::Debug for CljxFuture {
1590    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1591        write!(f, "Future")
1592    }
1593}
1594
1595impl cljrs_gc::Trace for CljxFuture {
1596    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1597        {
1598            let state = self.state.lock().unwrap();
1599            // Both Done and Failed hold a Value (the result or the thrown
1600            // error); trace either so the GC keeps it alive until observed.
1601            if let FutureState::Done(v) | FutureState::Failed(v) = &*state {
1602                v.trace(visitor);
1603            }
1604        }
1605    }
1606}
1607
1608// ── Agent ─────────────────────────────────────────────────────────────────────
1609
1610/// A Clojure agent — asynchronous state update queue (stub: not yet implemented).
1611pub struct Agent {
1612    /// Current state.
1613    pub state: Arc<Mutex<Value>>,
1614    /// Last error.
1615    pub error: Arc<Mutex<Option<Value>>>,
1616    pub watches: TrackedMutex<Vec<(Value, Value)>>,
1617}
1618
1619impl Agent {
1620    pub fn get_state(&self) -> Value {
1621        self.state.lock().unwrap().clone()
1622    }
1623
1624    pub fn get_error(&self) -> Option<Value> {
1625        self.error.lock().unwrap().clone()
1626    }
1627
1628    pub fn clear_error(&self) {
1629        *self.error.lock().unwrap() = None;
1630    }
1631}
1632
1633impl std::fmt::Debug for Agent {
1634    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1635        write!(f, "Agent")
1636    }
1637}
1638
1639impl cljrs_gc::Trace for Agent {
1640    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1641        {
1642            let state = self.state.lock().unwrap();
1643            state.trace(visitor);
1644        }
1645        {
1646            let error = self.error.lock().unwrap();
1647            if let Some(e) = error.as_ref() {
1648                e.trace(visitor);
1649            }
1650        }
1651        {
1652            let watches = self.watches.lock().unwrap();
1653            for (key, f) in watches.iter() {
1654                key.trace(visitor);
1655                f.trace(visitor);
1656            }
1657        }
1658    }
1659}
1660
1661// ── Tests ─────────────────────────────────────────────────────────────────────
1662
1663#[cfg(test)]
1664mod var_tests {
1665    use super::*;
1666    use crate::shared::SharedValue;
1667
1668    #[test]
1669    fn bind_promotable_mirrors_shared_root() {
1670        let var = Var::new("user", "x");
1671        assert!(var.shared_root.load().is_none());
1672        var.bind(Value::Long(7));
1673        assert!(matches!(
1674            var.shared_root.load().as_ref().as_ref(),
1675            Some(SharedValue::Long(7))
1676        ));
1677        assert_eq!(var.deref(), Some(Value::Long(7)));
1678        assert_eq!(var.deref_shared(), Some(Value::Long(7)));
1679    }
1680
1681    #[test]
1682    fn bind_nonpromotable_clears_shared_root() {
1683        let var = Var::new("user", "f");
1684        var.bind(Value::Long(1));
1685        assert!(var.shared_root.load().is_some());
1686        // Rebinding to a non-promotable value clears the mirror, but the
1687        // isolate-local fast path still holds it.
1688        let f = Value::NativeFunction(GcPtr::new(NativeFn::new("f", Arity::Fixed(0), |_| {
1689            Ok(Value::Nil)
1690        })));
1691        var.bind(f);
1692        assert!(var.shared_root.load().is_none());
1693        assert!(var.is_bound());
1694        assert_eq!(var.deref_shared(), None);
1695    }
1696
1697    #[test]
1698    fn from_shared_root_seeds_local_slot() {
1699        let src = Var::new("user", "y");
1700        src.bind(Value::Long(99));
1701        let recv = Var::from_shared_root("user", "y", false, src.shared_root.clone());
1702        assert_eq!(recv.deref(), Some(Value::Long(99)));
1703        // Same underlying cell.
1704        src.bind(Value::Long(100));
1705        assert_eq!(recv.deref_shared(), Some(Value::Long(100)));
1706    }
1707}