From 1ba19759c863fac9cb50fbb2625a4588ce6bee7f Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sat, 20 Jun 2026 15:46:11 -0400 Subject: [PATCH] Chez parity: missing native core fns + ns runtime fns (jolt-cf1q.7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Native Chez shims for clojure.core fns that live in the Janet seed but had none on the zero-Janet spine (they resolved to nil -> "not a fn"): - host/chez/natives-parity.ss: hash / hash-combine / hash-ordered-coll / hash-unordered-coll (24-bit masked like core_extra), transient?, rseq (vectors + sorted colls), cat (transducer). - jolt-invoke now dispatches a TRANSIENT vec/map/set as a fn (callable on the JVM): ((transient [10 20 30]) 1) -> 20. - ns.ss: ns-resolve, ns-imports, remove-ns, intern, alias, ns-unalias, refer, ns-refers, refer-clojure, alter-meta!, reset-meta!, and a real ns-aliases (was a stub returning {}). - runtime (require ...)/(use ...) now register :as/:refer into the Chez ns tables (was a no-op). The Chez analyzer already pre-registers at analyze time, but when the JANET analyzer compiled the form (prelude path) the Chez tables stayed empty, so ns-aliases/ns-resolve over an alias diverged — this fixes both paths. Seed unchanged (overlay doesn't reference these at mint time). zero-Janet corpus 2567->2600, prelude 2557->2590, 0 new divergences on either; Janet gate + JVM cert green. Filed jolt-vgrp for the pre-existing var-get-of-scalar-native-op quirk. jolt-cf1q.7 --- host/chez/natives-parity.ss | 46 ++++++++++++ host/chez/natives-str.ss | 16 +++-- host/chez/ns.ss | 100 +++++++++++++++++++++++++- host/chez/rt.ss | 5 ++ host/chez/seq.ss | 1 + test/chez/run-corpus-prelude.janet | 4 +- test/chez/run-corpus-zero-janet.janet | 5 +- 7 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 host/chez/natives-parity.ss diff --git a/host/chez/natives-parity.ss b/host/chez/natives-parity.ss new file mode 100644 index 0000000..5c8f986 --- /dev/null +++ b/host/chez/natives-parity.ss @@ -0,0 +1,46 @@ +;; natives-parity.ss (jolt-cf1q.7) — native Chez shims for clojure.core fns that +;; live in the Janet seed (src/jolt/core*.janet) but had no Chez shim, so on the +;; zero-Janet spine they resolved to nil ("not a fn"). Pure-Chez, JVM-matching. +;; +;; Loaded after host-table.ss (htable-sorted?), transients.ss (jolt-transient?), +;; values.ss (jolt-hash), seq.ss (jolt-seq/seq->list/list->cseq/jolt-invoke). + +;; --- hash family (mirrors core_extra.janet: 24-bit masked so int? holds) ------ +(define (np-h24 x) (bitwise-and (jolt-hash x) #xffffff)) +(define (np-hash x) (np-h24 x)) +(define (np-hash-combine a b) + (bitwise-and (bitwise-xor (np-h24 a) (+ (np-h24 b) #x9e3779)) #xffffff)) +(define (np-hash-ordered-coll coll) + (let loop ((xs (seq->list (jolt-seq coll))) (h 1)) + (if (null? xs) h (loop (cdr xs) (bitwise-and (+ (* 31 h) (np-h24 (car xs))) #xffffff))))) +(define (np-hash-unordered-coll coll) + (let loop ((xs (seq->list (jolt-seq coll))) (h 0)) + (if (null? xs) h (loop (cdr xs) (bitwise-and (+ h (np-h24 (car xs))) #xffffff))))) + +;; --- transient? --------------------------------------------------------------- +(define (np-transient? x) (jolt-transient? x)) + +;; --- rseq: vectors + sorted colls only (Clojure), reverse of the ascending seq. +(define (np-rseq coll) + (if (or (pvec? coll) (htable-sorted? coll)) + (list->cseq (reverse (seq->list (jolt-seq coll)))) + (jolt-throw (jolt-ex-info "rseq requires a vector or sorted collection" (jolt-hash-map))))) + +;; --- cat transducer (mirrors core_refs.janet core-cat): each item of the input +;; is itself a collection, concatenated into the downstream reducing fn. +(define (np-cat rf) + (lambda a + (cond + ((null? a) (jolt-invoke rf)) + ((null? (cdr a)) (jolt-invoke rf (car a))) + (else + (let loop ((xs (seq->list (jolt-seq (cadr a)))) (acc (car a))) + (if (null? xs) acc (loop (cdr xs) (jolt-invoke rf acc (car xs))))))))) + +(def-var! "clojure.core" "hash" np-hash) +(def-var! "clojure.core" "hash-combine" np-hash-combine) +(def-var! "clojure.core" "hash-ordered-coll" np-hash-ordered-coll) +(def-var! "clojure.core" "hash-unordered-coll" np-hash-unordered-coll) +(def-var! "clojure.core" "transient?" np-transient?) +(def-var! "clojure.core" "rseq" np-rseq) +(def-var! "clojure.core" "cat" np-cat) diff --git a/host/chez/natives-str.ss b/host/chez/natives-str.ss index 1e8bd12..f31dbd7 100644 --- a/host/chez/natives-str.ss +++ b/host/chez/natives-str.ss @@ -277,8 +277,14 @@ (def-var! "clojure.core" "str-replace" str-replace) (def-var! "clojure.core" "str-replace-all" str-replace-all) -;; (require ...) / (use ...) at runtime: the Chez AOT driver pre-evals these -;; against the analyzer ctx to register aliases + load aliased nss (driver.janet), -;; so the emitted call only needs to not crash. A no-op returning nil. -(def-var! "clojure.core" "require" (lambda args jolt-nil)) -(def-var! "clojure.core" "use" (lambda args jolt-nil)) +;; (require ...) / (use ...) at runtime: register each spec's :as alias + :refer +;; names into the runtime ns tables (chez-register-spec!, ns.ss), keyed by the +;; current ns. The zero-Janet spine also pre-registers these at analyze time +;; (idempotent); but when the JANET analyzer compiled the form (the prelude path) +;; the Chez tables were never populated, so ns-aliases/ns-resolve over an :as alias +;; need this runtime registration (jolt-cf1q.7). Specs arrive evaluated (quoted). +(define (chez-runtime-require . specs) + (for-each (lambda (s) (chez-register-spec! (chez-current-ns) s)) specs) + jolt-nil) +(def-var! "clojure.core" "require" chez-runtime-require) +(def-var! "clojure.core" "use" chez-runtime-require) diff --git a/host/chez/ns.ss b/host/chez/ns.ss index 470e461..f0751b0 100644 --- a/host/chez/ns.ss +++ b/host/chez/ns.ss @@ -121,7 +121,34 @@ (hashtable-values var-table)) m)) (define (jolt-ns-publics desig) (ns-vars-pmap (ns-desig->name desig))) -(define (jolt-ns-aliases desig) (jolt-hash-map)) + +;; ns-aliases (jolt-cf1q.7): the {alias-sym -> ns-value} registered under `desig` +;; (default the current ns) via require :as / alias. Reads ns-alias-table. +(define (jolt-ns-aliases . desig) + (let ((cns (if (pair? desig) (ns-desig->name (car desig)) (chez-current-ns))) + (m (jolt-hash-map))) + (vector-for-each + (lambda (k) + (when (string=? (car k) cns) + (set! m (jolt-assoc m (jolt-symbol #f (cdr k)) + (intern-ns! (hashtable-ref ns-alias-table k #f)))))) + (hashtable-keys ns-alias-table)) + m)) + +;; ns-refers (jolt-cf1q.7): the {sym -> var} referred into `desig` via refer/use. +(define (jolt-ns-refers desig) + (let ((cns (ns-desig->name desig)) (m (jolt-hash-map))) + (vector-for-each + (lambda (k) + (when (string=? (car k) cns) + (let* ((target (hashtable-ref ns-refer-table k #f)) + (c (and target (var-cell-lookup target (cdr k))))) + (when c (set! m (jolt-assoc m (jolt-symbol #f (cdr k)) c)))))) + (hashtable-keys ns-refer-table)) + m)) + +;; ns-imports: Chez has no Java class imports -> always the empty map. +(define (jolt-ns-imports . _) (jolt-hash-map)) ;; resolve: an unqualified symbol resolves in the current ns then clojure.core; a ;; qualified one in its own ns. Returns the var iff genuinely defined, else nil — @@ -147,6 +174,66 @@ (when c (var-cell-defined?-set! c #f) (var-cell-root-set! c jolt-unbound))) jolt-nil) +;; --- ns runtime fns (jolt-cf1q.7) ------------------------------------------- +;; ns-resolve: resolve `sym` as if reading it in namespace `ns-desig`. Qualified +;; syms consult that ns's :as aliases; unqualified resolve in the ns, its :refers, +;; then clojure.core. Returns the var or nil (never interns). +(define (jolt-ns-resolve ns-desig sym) + (let* ((cns (ns-desig->name ns-desig)) + (sns (symbol-t-ns sym)) (nm (symbol-t-name sym)) + (c (if (string? sns) + (var-cell-lookup (or (chez-resolve-alias cns sns) sns) nm) + (or (var-cell-lookup cns nm) + (let ((ref (chez-resolve-refer cns nm))) (and ref (var-cell-lookup ref nm))) + (var-cell-lookup "clojure.core" nm))))) + (if (and c (var-cell-defined? c)) c jolt-nil))) + +;; remove-ns: drop the namespace from the registry AND its vars, so find-ns +;; (which also derives existence from the var-table) returns nil afterward. +(define (jolt-remove-ns desig) + (let ((nm (ns-desig->name desig))) + (hashtable-delete! ns-registry nm) + (vector-for-each + (lambda (k) (let ((c (hashtable-ref var-table k #f))) + (when (and c (string=? (var-cell-ns c) nm)) (hashtable-delete! var-table k)))) + (hashtable-keys var-table)) + jolt-nil)) + +;; intern: create/set a var ns/sym to val (or an unbound cell). Returns the var. +(define (jolt-intern ns-desig sym . vopt) + (let ((nm (ns-desig->name ns-desig)) (s (symbol-t-name sym))) + (if (pair? vopt) (def-var! nm s (car vopt)) (declare-var! nm s)))) + +;; alias / ns-unalias: register/drop an :as alias under the current (or given) ns. +;; A runtime alias is registered into the SAME table the analyzer consults, so a +;; later form in the program resolves alias/foo (the spine analyzes form by form). +(define (jolt-alias alias-sym ns-sym) + (chez-register-alias! (chez-current-ns) (symbol-t-name alias-sym) (ns-desig->name ns-sym)) + jolt-nil) +(define (jolt-ns-unalias ns-desig alias-sym) + (hashtable-delete! ns-alias-table (cons (ns-desig->name ns-desig) (symbol-t-name alias-sym))) + jolt-nil) + +;; refer: bring every public var of `ns-sym` into the current ns as an unqualified +;; name (filters accepted/ignored — the corpus uses the bare form). refer-clojure +;; is a no-op (clojure.core always resolves on Chez). +(define (jolt-refer ns-sym . _filters) + (let ((target (ns-desig->name ns-sym)) (cns (chez-current-ns))) + (vector-for-each + (lambda (c) (when (and (string=? (var-cell-ns c) target) (var-cell-defined? c)) + (chez-register-refer! cns (var-cell-name c) target))) + (hashtable-values var-table)) + jolt-nil)) +(define (jolt-refer-clojure . _) jolt-nil) + +;; alter-meta! / reset-meta!: update a var's metadata (var-meta-table, rt.ss). +(define (jolt-alter-meta! ref f . args) + (let* ((cur (or (hashtable-ref var-meta-table ref #f) (jolt-hash-map))) + (new (apply jolt-invoke f cur args))) + (hashtable-set! var-meta-table ref new) + new)) +(define (jolt-reset-meta! ref m) (hashtable-set! var-meta-table ref m) m) + ;; --- RESOLVE FRICTION: native-op cells ------------------------------------- ;; Native-op primitives (+ map reduce …) are INLINED at emit, so they have no ;; var-cell and (resolve '+) would be nil — diverging from Clojure where it is a @@ -187,9 +274,20 @@ (def-var! "clojure.core" "ns-map" jolt-ns-publics) (def-var! "clojure.core" "ns-interns" jolt-ns-publics) (def-var! "clojure.core" "ns-aliases" jolt-ns-aliases) +(def-var! "clojure.core" "ns-refers" jolt-ns-refers) +(def-var! "clojure.core" "ns-imports" jolt-ns-imports) (def-var! "clojure.core" "resolve" jolt-resolve) +(def-var! "clojure.core" "ns-resolve" jolt-ns-resolve) (def-var! "clojure.core" "find-var" jolt-find-var) (def-var! "clojure.core" "ns-unmap" jolt-ns-unmap) +(def-var! "clojure.core" "remove-ns" jolt-remove-ns) +(def-var! "clojure.core" "intern" jolt-intern) +(def-var! "clojure.core" "alias" jolt-alias) +(def-var! "clojure.core" "ns-unalias" jolt-ns-unalias) +(def-var! "clojure.core" "refer" jolt-refer) +(def-var! "clojure.core" "refer-clojure" jolt-refer-clojure) +(def-var! "clojure.core" "alter-meta!" jolt-alter-meta!) +(def-var! "clojure.core" "reset-meta!" jolt-reset-meta!) ;; *ns* starts at the user namespace (the current ns for -e user code). in-ns ;; re-binds it. (ns-name is overridden natively in post-prelude.ss.) (def-var! "clojure.core" "*ns*" (intern-ns! "user")) diff --git a/host/chez/rt.ss b/host/chez/rt.ss index 7e179eb..7d07a2a 100644 --- a/host/chez/rt.ss +++ b/host/chez/rt.ss @@ -320,6 +320,11 @@ ;; clojure.math ns. Self-contained (only def-var! + Chez math), order-independent. (load "host/chez/math.ss") +;; parity shims (jolt-cf1q.7): native clojure.core fns missing on the zero-Janet +;; spine (hash family / rseq / cat / transient?). After host-table.ss (sorted), +;; transients.ss, values.ss (jolt-hash), seq.ss. +(load "host/chez/natives-parity.ss") + ;; syntax-quote form builders (jolt-r9lm, inc6b): __sqcat/__sqvec/__sqmap/__sqset/ ;; __sq1, def-var!'d into clojure.core. A cross-compiled macro expander (analyzer ;; on Chez, inc6b) calls these to build its expansion as reader forms. Needs the diff --git a/host/chez/seq.ss b/host/chez/seq.ss index a5aa17d..bf109a4 100644 --- a/host/chez/seq.ss +++ b/host/chez/seq.ss @@ -132,6 +132,7 @@ ((procedure? f) (apply f args)) ((keyword? f) (apply jolt-get (car args) f (cdr args))) ; (:k m [d]) -> (get m :k [d]) ((jolt-coll? f) (apply jolt-get f args)) ; (coll k [d]) -> (get coll k [d]) + ((jolt-transient? f) (apply jolt-get f args)) ; a transient vec/map/set is callable on the JVM (else (error 'invoke "not a fn" f)))) ;; ============================================================================ diff --git a/test/chez/run-corpus-prelude.janet b/test/chez/run-corpus-prelude.janet index 62d88e2..6c43f59 100644 --- a/test/chez/run-corpus-prelude.janet +++ b/test/chez/run-corpus-prelude.janet @@ -312,8 +312,10 @@ # future/deref/pmap cases run instead of crashing. 2534->2559, 0 new divergences. # Then -2 when agents went async (the two "send/send-off applies" sync-shim cases # match the JVM's async raciness and are allowlisted) -> 2557. +# jolt-cf1q.7 parity batches (hash/rseq/cat/transient-as-fn + ns runtime fns + +# runtime-require alias registration) -> 2590. # Strided runs scale down. -(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_PRELUDE_FLOOR") "2557"))) +(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_PRELUDE_FLOOR") "2590"))) (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))) diff --git a/test/chez/run-corpus-zero-janet.janet b/test/chez/run-corpus-zero-janet.janet index 446017b..f297661 100644 --- a/test/chez/run-corpus-zero-janet.janet +++ b/test/chez/run-corpus-zero-janet.janet @@ -191,8 +191,9 @@ # on any NEW divergence or if pass drops below the floor. Strided runs scale to 0. # 2569 after futures/pmap (jolt-byjr pt.1); -2 when agents went async (the two # "send/send-off applies" sync-shim cases now match the JVM's async raciness and -# are allowlisted) -> 2567. -(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_ZJ_FLOOR") "2567"))) +# are allowlisted) -> 2567. jolt-cf1q.7 parity batches (hash/rseq/cat/transient-as-fn +# + ns runtime fns) -> 2600. +(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_ZJ_FLOOR") "2600"))) (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)))