Skip to main content

cljrs_runtime/env/
dynamics.rs

1//! Thread-local dynamic variable binding stack.
2//!
3//! `binding` forms push a frame onto `BINDING_STACK` for the duration of their
4//! body; the RAII `BindingGuard` pops it on drop (handles both normal return
5//! and panics).
6
7use std::cell::RefCell;
8use std::collections::HashMap;
9
10use cljrs_gc::GcPtr;
11use cljrs_gc::Trace as _;
12use cljrs_value::{Value, Var};
13
14/// Opaque key for a Var in the binding stack (pointer identity).
15/// Stable because the GC is non-moving.
16pub type VarKey = usize;
17
18pub fn var_key_of(var: &GcPtr<Var>) -> VarKey {
19    var.get() as *const Var as usize
20}
21
22thread_local! {
23    static BINDING_STACK: RefCell<Vec<HashMap<VarKey, Value>>> =
24        const { RefCell::new(Vec::new()) };
25}
26cljrs_gc::mutator_slot!("cljrs-runtime/BINDING_STACK", refcell BINDING_STACK: Vec<HashMap<VarKey, Value>>);
27
28// ── RAII guard ────────────────────────────────────────────────────────────────
29
30/// Pops the innermost binding frame when dropped.
31pub struct BindingGuard;
32
33impl Drop for BindingGuard {
34    fn drop(&mut self) {
35        pop_frame();
36    }
37}
38
39// ── Stack manipulation ────────────────────────────────────────────────────────
40
41/// Push a new dynamic binding frame; return a guard that pops it on drop.
42pub fn push_frame(bindings: HashMap<VarKey, Value>) -> BindingGuard {
43    BINDING_STACK.with(|s| s.borrow_mut().push(bindings));
44    BindingGuard
45}
46
47/// Pop the frame `guard` owns and hand it back, so a caller that yields between
48/// uses (the async evaluator) can keep a `binding` frame off the thread-local
49/// stack while other tasks on the same thread run, and push it again later.
50/// Must be called with the innermost frame's guard.
51pub fn take_frame(guard: BindingGuard) -> HashMap<VarKey, Value> {
52    std::mem::forget(guard);
53    BINDING_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default())
54}
55
56fn pop_frame() {
57    BINDING_STACK.with(|s| {
58        s.borrow_mut().pop();
59    });
60}
61
62// ── Lookup ────────────────────────────────────────────────────────────────────
63
64/// Check the thread-local stack first (innermost frame wins); fall back to the
65/// root binding stored in the `Var` itself.
66pub fn deref_var(var: &GcPtr<Var>) -> Option<Value> {
67    let key = var_key_of(var);
68    let tl = BINDING_STACK.with(|s| {
69        s.borrow()
70            .iter()
71            .rev()
72            .find_map(|frame| frame.get(&key).cloned())
73    });
74    tl.or_else(|| var.get().deref())
75}
76
77/// True if `var` has any thread-local binding on this thread.
78pub fn is_thread_bound(var: &GcPtr<Var>) -> bool {
79    let key = var_key_of(var);
80    BINDING_STACK.with(|s| s.borrow().iter().any(|frame| frame.contains_key(&key)))
81}
82
83/// Set the innermost thread-local binding for `var`.
84/// Returns `false` if no thread-local binding exists (caller should fall back
85/// to setting the root).
86pub fn set_thread_local(var: &GcPtr<Var>, val: Value) -> bool {
87    let key = var_key_of(var);
88    BINDING_STACK.with(|s| {
89        for frame in s.borrow_mut().iter_mut().rev() {
90            if let std::collections::hash_map::Entry::Occupied(mut e) = frame.entry(key) {
91                e.insert(val);
92                return true;
93            }
94        }
95        false
96    })
97}
98
99// ── Binding conveyance ────────────────────────────────────────────────────────
100
101/// Snapshot the current thread's entire binding stack (for conveyance into a
102/// child thread, e.g. `future`).
103pub fn capture_current() -> Vec<HashMap<VarKey, Value>> {
104    BINDING_STACK.with(|s| s.borrow().clone())
105}
106
107/// Install a previously captured snapshot on the current (new) thread.
108pub fn install_frames(frames: Vec<HashMap<VarKey, Value>>) {
109    BINDING_STACK.with(|s| *s.borrow_mut() = frames);
110}
111
112// ── GC root tracing ───────────────────────────────────────────────────────────
113
114/// Trace all values in the current thread's binding stack as GC roots.
115/// Call this during the GC root enumeration phase.
116pub fn trace_current(visitor: &mut cljrs_gc::MarkVisitor) {
117    BINDING_STACK.with(|s| {
118        for frame in s.borrow().iter() {
119            for val in frame.values() {
120                val.trace(visitor);
121            }
122        }
123    });
124}