From a2805c4f5103bd61fa93709ae059052b7c5d44e8 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sat, 6 Jun 2026 16:47:44 -0400 Subject: [PATCH] vars: add per-var generation counter, bumped on root change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/jolt/types.janet | 18 ++++++++++++++---- test/unit/types-test.janet | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/jolt/types.janet b/src/jolt/types.janet index 2bca5c9..9580267 100644 --- a/src/jolt/types.janet +++ b/src/jolt/types.janet @@ -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 diff --git a/test/unit/types-test.janet b/test/unit/types-test.janet index aa35266..4acd864 100644 --- a/test/unit/types-test.janet +++ b/test/unit/types-test.janet @@ -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})]