From 8f26433469c6df2deee55965712efe76ddc4391d Mon Sep 17 00:00:00 2001 From: Yogthos Date: Wed, 17 Jun 2026 16:29:29 -0400 Subject: [PATCH] Chez Phase 1 (increment 3d): clojure.core prelude emit mode + gap catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a prelude emit mode to host/chez/emit.janet: when emitting clojure.core itself (not user -e), a non-native clojure.core ref lowers to a runtime var-deref instead of being rejected as out-of-subset, so core fns chain through each other. Default (subset) mode is unchanged — the corpus probe still rejects unimplemented core refs for a clean signal. core-prelude-probe.janet walks the tiers through the live analyzer->emit pipeline and catalogs reach + gaps (macros skipped; analyze-time only). Baseline: 303/355 non-macro core forms emit. Remaining gaps are a tight punch-list for the next increments: :throw (29), :quote (8), :try (2), Java host interop (6), letfn (4), declare (2). Probe has a regression floor. emit-test 83/83 (added prelude-mode lowering assertions); subset probe 619/619 unchanged; full gate green. --- host/chez/emit.janet | 14 +++- test/chez/README.md | 21 ++++++ test/chez/core-prelude-probe.janet | 104 +++++++++++++++++++++++++++++ test/chez/emit-test.janet | 17 +++++ 4 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 test/chez/core-prelude-probe.janet diff --git a/host/chez/emit.janet b/host/chez/emit.janet index 77cf2c4..26ec649 100644 --- a/host/chez/emit.janet +++ b/host/chez/emit.janet @@ -85,6 +85,16 @@ (and arity-ok (not (arity-ok nargs))) nil op)) +# PRELUDE MODE (inc 3d). The default (subset) mode rejects any clojure.core ref +# that isn't a native-op — a clean "out of subset" signal for user-facing `-e`. +# When emitting clojure.core ITSELF as a Scheme prelude, core fns reference each +# other constantly; those refs must lower to `var-deref` (resolved at runtime +# from the prelude's own def-var! forms) instead of being rejected. Host interop +# (:host) and unhandled IR ops still error in both modes — those are the real +# gaps that need a hand-written RT shim. +(var- prelude-mode? false) +(defn set-prelude-mode! [on] (set prelude-mode? on)) + (var- recur-target nil) # Munged local names known to hold a procedure (a named fn's self-recursion name). # Calls to these stay DIRECT; any other :local callee routes through jolt-invoke @@ -252,7 +262,7 @@ (= kind :keyword) (string "(jolt-get " (first args) " " (emit fnode) default ")") # (coll k [default]) -> (jolt-get coll k [default]) (= kind :coll) (string "(jolt-get " (emit fnode) " " (first args) default ")") - (stdlib-var? fnode) + (and (stdlib-var? fnode) (not prelude-mode?)) (errorf "emit: unsupported stdlib fn `%s/%s` (no core on Chez yet)" (get fnode :ns) (get fnode :name)) (= :host (get fnode :op)) (errorf "emit: unsupported host call `%s` (no host interop on Chez yet)" (get fnode :name)) @@ -275,7 +285,7 @@ :var (let [core-proc (and (= "clojure.core" (get node :ns)) (get core-value-procs (get node :name)))] (cond core-proc core-proc - (stdlib-var? node) + (and (stdlib-var? node) (not prelude-mode?)) (errorf "emit: unsupported stdlib ref `%s/%s` (no core on Chez yet)" (get node :ns) (get node :name)) (string "(var-deref " (string/format "%j" (get node :ns)) " " (string/format "%j" (get node :name)) ")"))) diff --git a/test/chez/README.md b/test/chez/README.md index cfde20e..24c4774 100644 --- a/test/chez/README.md +++ b/test/chez/README.md @@ -54,6 +54,27 @@ cases pass**, 0 divergences; 2036/2655 out of subset (await clojure.core on Chez a rest-arg lambda (the Scheme rest list is coerced to a jolt seq, nil when empty), which is the gating lift for emitting the clojure.core tiers as a prelude. +## Phase 1 — clojure.core prelude emission (inc 3d, jolt-ocvi) +The `-e`-capable jolt-chez path: emit the clojure.core tiers +(`jolt-core/clojure/core/NN-*.clj`) through the same analyzer → emit pipeline as a +Scheme PRELUDE of `def-var!` forms, so user code's `(var-deref "clojure.core" …)` +resolves the fn at runtime. `emit/set-prelude-mode!` flips a switch: in the default +(subset) mode a non-native `clojure.core` ref is rejected ("out of subset"); in +prelude mode it lowers to a runtime `var-deref` so core fns chain through each +other. Host interop (`:host`) and unhandled IR ops still error in both modes — +those are the real gaps that need a hand-written RT shim or new emit support. + +`core-prelude-probe.janet` (gated behind `JOLT_CHEZ_PRELUDE=1`) measures reach and +catalogs the gaps; macros are skipped (analyze-time only, not a runtime value): + + JOLT_CHEZ_PRELUDE=1 janet test/chez/core-prelude-probe.janet + +Baseline: **303/355 non-macro core forms emit** to Scheme. Remaining 52 gaps: +`:throw` (29, the big one — needs an exception model), `:quote` (8, needs RT +symbols/lists), `:try` (2), Java host interop `.write`/`.isDirectory` (6, io tier), +`letfn` (4), `declare`/def-no-init (2), one edge (`parse-uuid`). Each is a clean +next-increment target. The probe has a regression floor (raise it as gaps close). + Prior, inc 3b (seq tier + dynamic IFn, jolt-5pso): 595/595 compiled, 0 divergences, 2060/2655 out of subset. The seq tier brought up a list/lazy-seq type with first/rest/next/seq/cons/list, map/filter/reduce/into/remove, diff --git a/test/chez/core-prelude-probe.janet b/test/chez/core-prelude-probe.janet new file mode 100644 index 0000000..f330416 --- /dev/null +++ b/test/chez/core-prelude-probe.janet @@ -0,0 +1,104 @@ +# Phase 1 (jolt-cf1q.2, inc 3d) — clojure.core prelude emission probe. +# +# The path to an `-e`-capable jolt-chez: emit the clojure.core tiers +# (jolt-core/clojure/core/NN-*.clj) through the SAME live Janet analyzer -> +# host/chez/emit pipeline, as a Scheme PRELUDE of `def-var!` forms. User code's +# `(var-deref "clojure.core" "")` then resolves the fn at runtime. +# +# Most core fns are NOT native-ops, so they must be emitted; the ones that +# reference host interop / native Janet ops / unimplemented primitives can't be +# emitted yet (each a clean "out of subset" emit error). This probe reports how +# far the emit gets per tier and aggregates the gap list — the punch-list the +# next increments chase down. Measurement tool, gated out of the default suite. +# JOLT_CHEZ_PRELUDE=1 janet test/chez/core-prelude-probe.janet +(import ../../src/jolt/api :as api) +(import ../../src/jolt/backend :as backend) +(import ../../src/jolt/reader :as r) +(import ../../src/jolt/types_ctx :as tctx) +(import ../../host/chez/emit :as emit) + +(unless (os/getenv "JOLT_CHEZ_PRELUDE") + (print "skip: set JOLT_CHEZ_PRELUDE=1 to run the core-prelude emission probe") + (os/exit 0)) + +# load order — same as api/core-tiers (the kernel tier is bootstrap-compiled in +# the live system; here we just measure emit reach, so treat it like the rest). +(def tier-files + ["00-syntax" "00-kernel" "10-seq" "20-coll" "25-sorted" "30-macros" "40-lazy" "50-io"]) + +(defn- parse-all [src] + (def out @[]) + (var s src) + (while (> (length (string/trim s)) 0) + (def parsed (r/parse-next s)) + (set s (in parsed 1)) + (def f (in parsed 0)) + (unless (nil? f) (array/push out f))) + out) + +# jolt reader forms are arrays of jolt VALUES; a symbol is a struct +# {:jolt/type :symbol :name "..."} (jolt symbols aren't Janet symbols). +(defn- sym-name [x] + (when (and (struct? x) (= :symbol (get x :jolt/type))) (get x :name))) + +# A short label for a top-level form: the defn/def name, or the form head. +(defn- form-label [f] + (if (and (indexed? f) (> (length f) 1)) + (let [head (or (sym-name (in f 0)) "?") nm (sym-name (in f 1))] + (if nm (string head " " nm) head)) + (string/slice (string/format "%p" f) 0 40))) + +# Pull the unsupported fn/op name out of an emit error message for aggregation. +(defn- gap-key [msg] + (def m (string msg)) + (cond + (string/find "stdlib fn" m) (let [i (string/find "`" m)] (string "stdlib: " (string/slice m (inc i) (string/find "`" m (inc i))))) + (string/find "stdlib ref" m) (let [i (string/find "`" m)] (string "stdlib: " (string/slice m (inc i) (string/find "`" m (inc i))))) + (string/find "host call" m) "host-call" + (string/find "host ref" m) "host-ref" + (string/find "unhandled op" m) (string/slice m (max 0 (- (length m) 30))) + (string/find "unsupported literal" m) "unsupported-literal" + (string/slice m 0 (min 50 (length m))))) + +# Macros are analyze-time only (the Janet analyzer expands them away before emit), +# so they don't belong in a RUNTIME prelude — skip them, don't count as gaps. +(defn- macro-form? [f] + (and (indexed? f) (> (length f) 0) + (let [h (sym-name (in f 0))] (and h (or (= h "defmacro") (= h "definline")))))) + +(emit/set-prelude-mode! true) +(def ctx (api/init {:compile? true})) +(tctx/ctx-set-current-ns ctx "clojure.core") + +(var total 0) (var compiled 0) +(def gaps @{}) # gap-key -> count +(def gap-examples @{}) # gap-key -> first form label that hit it + +(each tf tier-files + (def src (slurp (string "jolt-core/clojure/core/" tf ".clj"))) + (def forms (parse-all src)) + (var t-total 0) (var t-ok 0) + (each f forms + (unless (macro-form? f) + (++ total) (++ t-total) + (def res (protect (emit/emit (backend/analyze-form ctx f)))) + (if (res 0) + (do (++ compiled) (++ t-ok)) + (let [k (gap-key (res 1))] + (put gaps k (+ 1 (or (get gaps k) 0))) + (unless (get gap-examples k) (put gap-examples k (form-label f))))))) + (printf " %-10s %3d/%-3d forms emit" tf t-ok t-total)) + +(printf "\nCore prelude emit reach: %d/%d top-level forms compile to Scheme" compiled total) +(printf "%d distinct gaps (fn/op the emit back end can't lower yet):" (length gaps)) +(def sorted-gaps (sort-by (fn [k] (- (get gaps k))) (keys gaps))) +(each k sorted-gaps + (printf " %4d x %-34s e.g. %s" (get gaps k) k (get gap-examples k))) +(flush) + +# Regression floor (raise it as new IR ops / RT shims land, like the suite +# baseline). Fails if prelude emit reach drops below the recorded baseline. +(def reach-floor 303) +(when (< compiled reach-floor) + (printf "REGRESSION: prelude emit reach %d < floor %d" compiled reach-floor) + (os/exit 1)) diff --git a/test/chez/emit-test.janet b/test/chez/emit-test.janet index 3a677b4..0bd302f 100644 --- a/test/chez/emit-test.janet +++ b/test/chez/emit-test.janet @@ -207,6 +207,23 @@ (ok (string "arity: " src) (and (= code 0) (= out want)) (string "chez=" out " janet=" want " | " err)))) +# 3h) prelude mode (inc 3d): emitting clojure.core ITSELF, a core->core ref must +# lower to a runtime var-deref instead of being rejected as "out of subset". +# `frequencies` is a core fn but not a native-op, so it exercises the switch. +(let [ir (backend/analyze-form ctx (in (r/parse-next "(fn [x] (frequencies x))") 0))] + # subset mode (the default): a non-native core ref is rejected at emit time. + (ok "prelude: subset mode rejects non-native core ref" + (let [r (protect (emit/emit ir))] (not (r 0)))) + # prelude mode: the same ref lowers to (var-deref "clojure.core" "frequencies"). + (emit/set-prelude-mode! true) + (def scm (protect (emit/emit ir))) + (emit/set-prelude-mode! false) + (ok "prelude: mode lowers non-native core ref to var-deref" + (and (scm 0) + (string/find "var-deref" (scm 1)) + (string/find "frequencies" (scm 1))) + (string/format "%p" scm))) + # 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.