perf: var-deref fast path; drop the memoized-getter indirection
var-get short-circuits to the root when no dynamic bindings are active (the common case — the binding stack is only non-empty inside a `binding` block), skipping the stack walk on every global deref. The compiled indirect deref now emits (var-get (quote cell)) instead of routing through a per-var memoized getter closure — one fewer call and no per-var closure alloc. The cell is quoted so it embeds by reference: a bare table in argument position is re-evaluated as a constructor, which deep-copies the cell (and any atom in its root) on every call — that silently broke swap!/atoms/self-ref lazy-seqs until the quote. Conformance 218/218 all three modes; battery 3916.
This commit is contained in:
parent
a3cc035782
commit
db08cecde8
2 changed files with 25 additions and 19 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue