self-host: gate the analyzer build on the kernel tier; move case/cond-> to the overlay
The real bug behind the case/cond-> fixpoint regression wasn't gensym — it was build ordering. A top-level defn in a pre-kernel tier (the fresh-sym helper these macros need) gets compiled in compile mode, and that lazily builds the self-hosted analyzer via ensure-analyzer. 00-syntax loads before 00-kernel, so the analyzer was built against a core missing mapv/second/peek/...: its references to them were interned as forward-ref nil cells in jolt.analyzer. Those nil cells then shadowed the real clojure.core defs when the analyzer rebuilt itself (stage2), so the analyzer's own mapv calls went to nil — 'Cannot call nil as a function'. Only variadic-heavy paths (juxt+mapv) surfaced it. build-compiler! already documented that the kernel must be loaded first; nothing enforced it. Gate ensure-analyzer on a :kernel-ready? flag set after the kernel tier loads. A pre-kernel compile now falls back to the interpreter (compile-and-eval already handles that) instead of building the analyzer too early. fresh-sym ends up interpreted during 00-syntax load, which is fine. With ordering fixed, case and cond-> move to 00-syntax (they need a real gensym, so syntax-quote auto-gensym alone won't do). conformance 228x3, fixpoint stage1==2==3, clojure-test-suite 3930, full suite green, bench flat.
This commit is contained in:
parent
d0d48f0ebd
commit
646744ca1d
4 changed files with 58 additions and 58 deletions
|
|
@ -59,3 +59,39 @@
|
|||
;; Forward declaration is a no-op on Jolt — the compiler resolves forward refs via
|
||||
;; pending cells (matching the prior Janet macro).
|
||||
(defmacro declare [& syms] `(do))
|
||||
|
||||
;; A fresh jolt symbol inside a macro body (a bare (gensym) returns a Janet symbol
|
||||
;; the destructurer rejects). This defn compiles fine: by the time a tier triggers
|
||||
;; the analyzer build the kernel is in place (the build is gated until then).
|
||||
(defn- fresh-sym [] (symbol (str (gensym))))
|
||||
|
||||
;; cond->: thread expr through each (test form) pair, only when the test is truthy.
|
||||
;; Linear nested let*, a distinct fresh symbol per step.
|
||||
(defmacro cond-> [expr & clauses]
|
||||
(let [step (fn step [prev cls]
|
||||
(if (empty? cls)
|
||||
prev
|
||||
(let [t (first cls)
|
||||
f (nth cls 1)
|
||||
gn (fresh-sym)
|
||||
call (if (seq? f) `(~(first f) ~prev ~@(rest f)) `(~f ~prev))]
|
||||
`(let* [~gn (if ~t ~call ~prev)] ~(step gn (drop 2 cls))))))
|
||||
g0 (fresh-sym)]
|
||||
`(let* [~g0 ~expr] ~(step g0 clauses))))
|
||||
|
||||
;; case: nested =/or tests (no jump table). Test constants are NOT evaluated —
|
||||
;; symbols and list constants are quoted; a list in test position is a set (or).
|
||||
(defmacro case [expr & clauses]
|
||||
(let [g (fresh-sym)
|
||||
mk-const (fn [c] (if (or (symbol? c) (seq? c)) `(quote ~c) c))
|
||||
mk-test (fn [c]
|
||||
(if (seq? c)
|
||||
`(or ~@(map (fn [v] `(= ~g ~(mk-const v))) c))
|
||||
`(= ~g ~(mk-const c))))
|
||||
build (fn build [cls]
|
||||
(if (empty? cls)
|
||||
nil
|
||||
(if (empty? (rest cls))
|
||||
(first cls)
|
||||
`(if ~(mk-test (first cls)) ~(nth cls 1) ~(build (drop 2 cls))))))]
|
||||
`(let* [~g ~expr] ~(build clauses))))
|
||||
|
|
|
|||
|
|
@ -72,12 +72,21 @@
|
|||
(def core-dl (get env :aot-core?))
|
||||
(def saved (ctx-current-ns ctx))
|
||||
(ctx-set-current-ns ctx "clojure.core")
|
||||
# Gate the analyzer build until the kernel tier loads (see ensure-analyzer):
|
||||
# present-and-false here means pre-kernel compiles fall back to the interpreter.
|
||||
(put env :kernel-ready? false)
|
||||
(each tier core-tiers
|
||||
(when-let [src (get stdlib-embed/sources (tier :ns))]
|
||||
(put env :direct-linking? core-dl)
|
||||
(if (and compile? (tier :kernel))
|
||||
(backend/bootstrap-load-source ctx "clojure.core" src)
|
||||
(eval-overlay-source ctx src))))
|
||||
(eval-overlay-source ctx src))
|
||||
# The self-hosted compiler depends on the kernel tier (second/peek/mapv/...).
|
||||
# Mark it ready once that tier is in place so the analyzer can be built; a
|
||||
# pre-kernel tier that triggers a compile (e.g. a defn in 00-syntax) instead
|
||||
# falls back to the interpreter rather than building the analyzer against a
|
||||
# half-loaded core (which would forward-ref the missing kernel fns to nil).
|
||||
(when (tier :kernel) (put env :kernel-ready? true))))
|
||||
(put env :direct-linking? user-dl)
|
||||
(ctx-set-current-ns ctx saved))
|
||||
|
||||
|
|
|
|||
|
|
@ -306,7 +306,17 @@
|
|||
(compile-load ctx "jolt.analyzer"))
|
||||
|
||||
(defn- ensure-analyzer [ctx]
|
||||
(when (= 0 (length ((ctx-find-ns ctx "jolt.analyzer") :mappings)))
|
||||
# Don't build until the kernel tier is loaded (see api/load-core-overlay! and
|
||||
# build-compiler!). Before then a compile request — e.g. a defn in a pre-kernel
|
||||
# tier — must fall back to the interpreter, not build the analyzer against a
|
||||
# core missing the fns it references (which would intern them as nil cells that
|
||||
# then shadow the real definitions on the self-rebuild). The flag is absent in
|
||||
# bare/test contexts that never load core; treat that as ready so those keep
|
||||
# building the analyzer lazily as before.
|
||||
(def env (ctx :env))
|
||||
(def gated (and (has-key? env :kernel-ready?) (not (get env :kernel-ready?))))
|
||||
(when (and (not gated)
|
||||
(= 0 (length ((ctx-find-ns ctx "jolt.analyzer") :mappings))))
|
||||
(build-compiler! ctx)))
|
||||
|
||||
(defn rebuild-compiler!
|
||||
|
|
|
|||
|
|
@ -2062,38 +2062,6 @@
|
|||
(put gensym_counter :val (+ n 1))
|
||||
{:jolt/type :symbol :ns nil :name (string prefix-string n)})
|
||||
|
||||
(defn core-case
|
||||
"Macro: (case expr val1 result1 ... default)
|
||||
Supports single values, lists of values (one-of-many), and symbols."
|
||||
[expr & clauses]
|
||||
(def g (gensym))
|
||||
(defn make-const [c]
|
||||
# case constants are literals, never evaluated: quote symbols and list
|
||||
# literals (read as arrays) so e.g. `sym` and a wrapped list `(a b c)` match
|
||||
# by value rather than resolving/calling.
|
||||
(if (or (and (struct? c) (= :symbol (c :jolt/type))) (array? c))
|
||||
@[{:jolt/type :symbol :ns nil :name "quote"} c]
|
||||
c))
|
||||
(defn make-test [c]
|
||||
(if (array? c)
|
||||
(let [or-args @[{:jolt/type :symbol :ns nil :name "or"}]]
|
||||
(each v c
|
||||
(array/push or-args @[{:jolt/type :symbol :ns nil :name "="} g (make-const v)]))
|
||||
or-args)
|
||||
@[{:jolt/type :symbol :ns nil :name "="} g (make-const c)]))
|
||||
(defn build [cls]
|
||||
(if (= 0 (length cls))
|
||||
nil
|
||||
(if (= 1 (length cls))
|
||||
(first cls)
|
||||
(let [c (first cls)
|
||||
r (first (tuple/slice cls 1))]
|
||||
@[{:jolt/type :symbol :ns nil :name "if"}
|
||||
(make-test c)
|
||||
r
|
||||
(build (tuple/slice cls 2))]))))
|
||||
@[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses)])
|
||||
|
||||
|
||||
# if-let / when-let / if-some / when-some bind the name ONLY in the then/body
|
||||
# branch — the else branch must see the surrounding scope, not the binding (so
|
||||
|
|
@ -2170,27 +2138,6 @@
|
|||
(build 0 (parse-groups bindings))
|
||||
body))
|
||||
|
||||
(defn core-cond->
|
||||
"Macro: (cond-> expr test form ...) — thread first only when test is true"
|
||||
[expr & clauses]
|
||||
(def g (gensym))
|
||||
(defn build [cls result-form]
|
||||
(if (= 0 (length cls))
|
||||
result-form
|
||||
(let [t (first cls)
|
||||
f (in cls 1)
|
||||
f-call (if (array? f)
|
||||
(let [arr (array/slice f)]
|
||||
(array/insert arr 1 result-form)
|
||||
arr)
|
||||
@[f result-form])]
|
||||
(build (tuple/slice cls 2)
|
||||
@[{:jolt/type :symbol :ns nil :name "if"}
|
||||
t
|
||||
f-call
|
||||
result-form]))))
|
||||
@[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses g)])
|
||||
|
||||
(defn core-push-thread-bindings [b] (push-thread-bindings b))
|
||||
(defn core-pop-thread-bindings [] (pop-thread-bindings))
|
||||
|
||||
|
|
@ -3534,10 +3481,8 @@
|
|||
"add-watch" core-add-watch
|
||||
"remove-watch" core-remove-watch
|
||||
"not" core-not
|
||||
"case" core-case
|
||||
"for" core-for
|
||||
"when-let" core-when-let
|
||||
"cond->" core-cond->
|
||||
"defn" core-defn
|
||||
"defn-" core-defn-
|
||||
"derive" core-derive
|
||||
|
|
@ -3617,7 +3562,7 @@
|
|||
(defn core-macro-names
|
||||
"Set of core binding names that are macros."
|
||||
[]
|
||||
@{"case" true "for" true "when-let" true "defn" true "defn-" true "fn" true "let" true "loop" true "defrecord" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "lazy-seq" true "lazy-cat" true "cond->" true "doseq" true})
|
||||
@{"for" true "when-let" true "defn" true "defn-" true "fn" true "let" true "loop" true "defrecord" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "lazy-seq" true "lazy-cat" true "doseq" true})
|
||||
|
||||
(def init-core!
|
||||
(fn [& args]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue