Chez Phase 1 (increment 3q): multimethod dispatch + late-bind

host/chez/multimethods.ss implements the multimethod runtime: defmulti/defmethod
expand to defmulti-setup/defmethod-setup calls (+ get-method/methods/
remove-method/prefer-method/prefers). A jolt-multifn record carries its dispatch
fn and a jolt=-keyed method table; jolt-invoke dispatches it (exact match, then
isa?/hierarchy with prefer-method, then :default), reusing the overlay's
isa?/derive/make-hierarchy. The multifn's ns comes from a runtime chez-current-ns
(default user; the prelude load sets clojure.core for print-method/print-dup).

Two emit-side changes were needed:
- late-bind (:late-bind-unresolved? ctx flag, default OFF): defmulti expands to a
  bare-symbol setup call, so the analyzer doesn't intern the name and a forward
  reference '(area ...)' after '(defmulti area ...)' in one form was 'Unable to
  resolve symbol'. The strict compiler punts these to the interpreter; the Chez
  back end has none, so the flag lowers an unresolved symbol to a var-ref against
  the compile ns (open-world -e semantics). Set only by the Chez make-ctx /
  jolt-chez; the main compiler keeps strict resolution (host_iface late-bind?
  defaults nil).
- a :var call head now routes through jolt-invoke, since a late-bound var can hold
  a multifn (or keyword/coll IFn), not just a procedure. Transparent for
  procedures; the hot self-recursive call is a :local known-proc, stays direct.

Class-based dispatch ((class x)/String) deferred (needs deftype/class subsystem).

Parity 1506 -> 1530/2497, 0 new divergences. emit-test 302/302. Full janet gate
green (the analyzer flag is off there; suite flakiness under parallel load only).
This commit is contained in:
Yogthos 2026-06-18 01:24:01 -04:00
parent e51cc2e47e
commit 02139af0a1
10 changed files with 279 additions and 8 deletions

View file

@ -163,6 +163,28 @@ class names, eval-order, with-open — all deferred Phase-2 / dynamic-var gaps).
The Chez host throws (Clojure-correct), so the 4 `odd arg …` corpus rows — which
encode the seed's lenient behavior — sit in the crash bucket until the seed is
fixed and those spec rows are updated to `:throws`.
- inc 3q multimethod dispatch + late-bind (jolt-9ls5, Phase 2): `host/chez/
multimethods.ss` implements the multimethod runtime — `defmulti`/`defmethod`
expand to `defmulti-setup`/`defmethod-setup` calls (+ `get-method`/`methods`/
`remove-method`/`prefer-method`/`prefers`). A `jolt-multifn` record carries its
dispatch fn and a `jolt=`-keyed method table; `jolt-invoke` dispatches it (exact
match, then isa?/hierarchy with `prefer-method`, then `:default`), reusing the
overlay's `isa?`/`derive`/`make-hierarchy`. The multifn's ns is set via a runtime
`chez-current-ns` (default "user"; the prelude load sets "clojure.core").
Two emit-side changes made this work:
- **late-bind** (`:late-bind-unresolved?` ctx flag, default OFF): `defmulti`
expands to a bare-symbol setup *call*, so the analyzer doesn't intern the name
and a forward reference (`(area …)` after `(defmulti area …)` in one form) was
"Unable to resolve symbol". The strict compiler punts these to the interpreter;
the Chez back end has none, so the flag makes an unresolved symbol lower to a
`var-ref` against the compile ns — the open-world semantics of `-e`. Set only by
the Chez `make-ctx`/`jolt-chez`; the main compiler keeps its strict behavior.
- a `:var` call head now routes through `jolt-invoke` (not a direct application),
since a late-bound var can hold a multifn (or a keyword/coll IFn), not just a
procedure. Transparent for procedures; the hot self-recursive call is a `:local`
known-proc, so it stays direct. (Class-based dispatch — `(class x)`/`String` — is
deferred; it needs the deftype/class subsystem.)
The remaining buckets are the punch-list the next increments chase: ~361 emit-fail
(genuine host interop — qualified Java/Janet refs, runtime `defmacro`/`eval`, out of

View file

@ -583,6 +583,32 @@
(ok (string "y1zq -e: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err)))))
# 3v) multimethod dispatch (jolt-9ls5): defmulti/defmethod expand to
# defmulti-setup/defmethod-setup (+ get-method/methods/remove-method/
# prefer-method/prefers); host/chez/multimethods.ss provides the runtime. A
# jolt-multifn record carries its dispatch fn + method table; jolt-invoke
# dispatches it (direct match, then isa?/hierarchy + prefers, then :default).
# Dispatch uses the overlay isa?/derive/make-hierarchy, so these need the full
# prelude -> the -e binary. (Class-based dispatch — (class x)/String — is
# deferred; it needs the deftype/class subsystem.)
(when (os/stat "bin/jolt-chez")
(each src ["(= \"two\" (do (defmulti f identity) (defmethod f 1 [_] \"one\") (defmethod f 2 [_] \"two\") (f 2)))"
"(= \"circle\" (do (defmulti area :shape) (defmethod area :circle [_] \"circle\") (area {:shape :circle})))"
"(= \"other\" (do (defmulti f identity) (defmethod f 1 [_] \"one\") (defmethod f :default [_] \"other\") (f 99)))"
"(= 5 (do (defmulti g (fn [a b] a)) (defmethod g :add [_ b] b) (g :add 5)))"
"(= :is-shape (do (derive :hsq :hshape) (defmulti hmm identity) (defmethod hmm :hshape [_] :is-shape) (hmm :hsq)))"
"(= :parent (do (def hh (atom (derive (make-hierarchy) :c :p))) (defmulti cmm identity :hierarchy hh) (defmethod cmm :p [_] :parent) (cmm :c)))"
"(= :exact (do (derive :de1 :de2) (defmulti emm identity) (defmethod emm :de2 [_] :parent) (defmethod emm :de1 [_] :exact) (emm :de1)))"
"(= \"one\" (do (defmulti f identity) (defmethod f 1 [_] \"one\") ((get-method f 1) 1)))"
"(= \"one\" (do (defmulti f identity) (defmethod f 1 [_] \"one\") ((get (methods f) 1) 1)))"
"(= 2 (do (defmulti f identity) (defmethod f 1 [_] \"one\") (defmethod f 2 [_] \"two\") (count (methods f))))"]
(let [[code out err] (run-jolt-chez src) want (cli-oracle src)]
(ok (string "multimethod: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# no-match throws (exits non-zero), like the corpus :throws row.
(let [[code out err] (run-jolt-chez "(do (defmulti f identity) (defmethod f 1 [_] \"one\") (f 99))")]
(ok "multimethod: no match throws" (not= code 0) (string "code=" code))))
# 4) perf signal: emitted fib(30) in-Scheme timing (excludes Chez startup), to
# track against the spike ceiling (hand-Scheme fib ~5ms). Informational — the
# jolt-truthy? wrapper (~3x) and flonum modeling are known Phase-4 levers.

View file

@ -136,9 +136,9 @@
# on any NEW (un-allowlisted) divergence — a real Chez correctness regression.
# Full-corpus baseline: inc 3j 1220/2497; 3k (converters) 1326; 3l (transients)
# 1382; 3m (numeric-edge emit + variadic assoc!) 1407; 3n (seq-native shims +
# reduced) 1467; 3o (transducer arities) 1493; 3p (misc seq/regex gaps) 1506.
# Strided runs scale down.
(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_PRELUDE_FLOOR") "1506")))
# reduced) 1467; 3o (transducer arities) 1493; 3p (misc seq/regex gaps) 1506;
# 3q (multimethod dispatch + late-bind) 1530. Strided runs scale down.
(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_PRELUDE_FLOOR") "1530")))
(def floor (if (os/getenv "JOLT_CORPUS_LIMIT") 0 base-floor))
(when (or (> (length diverged) 0) (< pass floor))
(printf "REGRESSION: pass %d < floor %d or %d new divergence(s)" pass floor (length diverged)))