diff --git a/src/jolt/backend.janet b/src/jolt/backend.janet index a43c1b5..6625856 100644 --- a/src/jolt/backend.janet +++ b/src/jolt/backend.janet @@ -14,12 +14,9 @@ (use ./evaluator) (import ./reader :as r) -# Var late-binding: deref/set through the cell via a memoized closure so compiled -# code sees redefinition (Janet early-binds plain symbols). Same scheme as the -# bootstrap compiler. -(defn- var-getter [cell] - (or (get cell :jolt/getter) - (let [g (fn [] (var-get cell))] (put cell :jolt/getter g) g))) +# Var late-binding: reads go through `(var-get cell)` with the cell embedded as a +# constant, so compiled code sees redefinition (Janet early-binds plain symbols) +# — var-get reads the cell's root live. Writes go through a memoized setter. (defn- var-setter [cell] (or (get cell :jolt/setter) (let [s (fn [v] (bind-root cell v) cell)] (put cell :jolt/setter s) s))) @@ -215,7 +212,10 @@ :var (let [cell (cell-for ctx (node :ns) (node :name))] (if (direct-var? ctx cell) (cell :root) # direct link: embed the fn value - (tuple (var-getter cell)))) # indirect: live, redefinable + # Indirect: live deref. Quote the cell so it's embedded by + # reference (a bare table in arg position would be re-evaluated as + # a constructor — deep-copying it, and any atom in :root, each call). + (tuple var-get (tuple 'quote cell)))) :if ['if (emit ctx (node :test)) (emit ctx (node :then)) (emit ctx (node :else))] :do (emit-seq ctx node) :loop (emit-loop ctx node) diff --git a/src/jolt/types.janet b/src/jolt/types.janet index b6d9823..ae56e4e 100644 --- a/src/jolt/types.janet +++ b/src/jolt/types.janet @@ -129,19 +129,25 @@ "Deref the var. If the var is dynamic and has a thread-local binding, return that. Otherwise return the root binding." [v] - # walk binding stack top-down for this var + # Fast path: no dynamic bindings are active (the common case — the stack is + # only non-empty inside a `binding` block), so the value is just the root. This + # is the hot path for every global deref; skip building the walk otherwise. (def bs (cur-binding-stack)) - (var result nil) - (var i (dec (length bs))) - (while (>= i 0) - (let [frame (in bs i) - val (get frame v)] - (if (not (nil? val)) - (do - (set result (if (var? val) (var-get val) val)) - (set i -1)) - (-- i)))) - (if (not (nil? result)) result (v :root))) + (if (= 0 (length bs)) + (v :root) + # walk binding stack top-down for this var + (do + (var result nil) + (var i (dec (length bs))) + (while (>= i 0) + (let [frame (in bs i) + val (get frame v)] + (if (not (nil? val)) + (do + (set result (if (var? val) (var-get val) val)) + (set i -1)) + (-- i)))) + (if (not (nil? result)) result (v :root))))) (defn var-set "Set a var's value. If the var has a thread-local binding on the stack, update