vars: add per-var generation counter, bumped on root change

Foundation for the linking-mode work (jolt-5ed). Every var cell carries a :gen
that bumps on bind-root / var-set(root) / alter-var-root — i.e. on redefinition.
Behavior-neutral on its own; it's the substrate the upcoming work needs:
direct-linked (sealed) call sites can detect staleness, and redefinition-aware
dispatch caches can invalidate per-var (only a redefined var's callers miss,
not a global flush). Full suite green, conformance 218/218 in all three modes.
This commit is contained in:
Yogthos 2026-06-06 16:47:44 -04:00
parent 28ef15ea4b
commit a2805c4f51
2 changed files with 29 additions and 4 deletions

View file

@ -86,6 +86,10 @@
:name name
:root init-val
:meta m
# Generation: bumped on every root change (redefinition). Call
# sites / dispatch caches keyed on this can detect a redef and
# invalidate; direct-linked (sealed) sites can detect staleness.
:gen 0
:dynamic (if meta (get meta :dynamic) false)
:macro (if meta (get meta :macro) false)
:ns (if meta (get meta :ns) nil)}]
@ -152,14 +156,16 @@
(if (not (nil? (get frame v)))
(do (put bs i (merge frame {v val})) (set done true))
(-- i))))
(unless done (put v :root val))
(unless done (do (put v :root val) (put v :gen (+ 1 (or (v :gen) 0)))))
val)
(defn alter-var-root
"Atomically alter the root binding of v by applying f to current value plus args."
[v f & args]
(let [new-val (f (v :root) ;args)]
(put v :root new-val)))
(put v :root new-val)
(put v :gen (+ 1 (or (v :gen) 0)))
new-val))
(defn alter-meta!
"Atomically update a var's metadata via (apply f args)."
@ -244,14 +250,18 @@
:name (v :name)
:root (v :root)
:meta new-meta
:gen (or (v :gen) 0)
:dynamic (v :dynamic)
:macro (v :macro)
:ns (v :ns)}))
(defn bind-root
"Set the root binding (internal, same as var-set)."
"Set the root binding and bump the var's generation (the redefinition
chokepoint: def, ns-intern-with-val, and the root-set paths all route here)."
[v val]
(put v :root val))
(put v :root val)
(put v :gen (+ 1 (or (v :gen) 0)))
val)
# ============================================================
# Namespace

View file

@ -40,6 +40,21 @@
(assert (deep= {:name 'x :private true} (var-meta v2)) "with-meta merges meta")
(assert (= 42 (var-get v2)) "with-meta preserves root binding"))
# generation counter — bumps on every root change (substrate for direct-link
# staleness detection and redefinition-aware dispatch caches)
(let [v (make-var 'g 1)]
(assert (= 0 (v :gen)) "fresh var starts at generation 0")
(bind-root v 2)
(assert (= 1 (v :gen)) "bind-root bumps generation")
(var-set v 3)
(assert (= 2 (v :gen)) "var-set (root) bumps generation")
(alter-var-root v inc)
(assert (= 3 (v :gen)) "alter-var-root bumps generation")
(assert (= 4 (var-get v)) "value still tracks through gen bumps"))
(let [v (make-var 'g 1)
v2 (with-meta v {:doc "x"})]
(assert (= (v :gen) (v2 :gen)) "with-meta carries generation"))
# var with namespace
(let [ns (make-ns 'my.ns)
v (make-var 'my.ns/x 1 {:ns ns})]