Chez Phase 2 (inc M): dynamic var binding (jolt-2o7x)

Add the dynamic-binding cluster on the Chez RT: a per-thread binding stack
(host/chez/dyn-binding.ss) backing binding / with-bindings* / var-set /
thread-bound? / with-local-vars / with-redefs / bound-fn* /
get-thread-bindings / alter-var-root + the __local-var seam.

Frames are stored innermost-first as identity-keyed alists of mutable
(cell . value) pairs, so var-set updates the current binding in place. The
two var-read paths — var-deref (compiled code) and jolt-var-get (var-get /
deref on a cell) — are chained onto the stack so a `binding` frame is seen
by every read, with a fast path when the stack is empty. var-cells now hash
by ns/name so a var works as a map key (with-redefs builds (hash-map (var f)
v); get-thread-bindings returns a var-keyed map).

Fix a latent bug exposed once with-in-str could bind *in*: seq? didn't
recognize a lazy-seq. predicates.ss's jolt-seq? predates the lazyseq record,
and unlike the native-op dispatchers it's reached via var-deref, so the
patch must re-def-var! the var, not just set! the top-level binding.

Note: with-bindings* over a hash-map literal now returns the correct Clojure
value where the seed returns a stale one — the seed's PHM can't find a var
key (which is why its `binding` uses array-map); on Chez frames look up by
cell identity.

Prelude corpus parity 1972 -> 2000, floor raised. Gate: _dynbind 24/24,
prelude corpus 0 divergences, full jpm test, conformance 355x3.
This commit is contained in:
Yogthos 2026-06-18 18:29:55 -04:00
parent 737288bff5
commit fc6551f877
5 changed files with 238 additions and 1 deletions

View file

@ -74,5 +74,14 @@
(define %ls-str-render-one jolt-str-render-one)
(set! jolt-str-render-one (lambda (x) (if (jolt-lazyseq? x) (%ls-str-render-one (jolt-seq x)) (%ls-str-render-one x))))
;; seq? — a lazy seq IS a seq (predicates.ss's jolt-seq? predates the lazyseq
;; record). Unlike the native-op dispatchers above (called via a direct top-level
;; reference, so the set! is enough), seq? is reached through var-deref, which
;; reads the var-cell root — so the patched closure must be re-def-var!'d, not just
;; set!. (Exposed once dynamic binding let with-in-str/line-seq reach seq?.)
(define %ls-seq? jolt-seq?)
(set! jolt-seq? (lambda (x) (or (jolt-lazyseq? x) (%ls-seq? x))))
(def-var! "clojure.core" "seq?" jolt-seq?)
(def-var! "clojure.core" "make-lazy-seq" jolt-make-lazy-seq)
(def-var! "clojure.core" "coll->cells" jolt-coll->cells)