core: wrap macro expanders in fn (not fn*) so destructured arglists compile
Follow-up to the staged macro-compile change. The macro-recompile pass wrapped each expander in the raw fn* primitive, which punts on a destructuring rest param — so if-not/if-let/if-some/assert (using the canonical '& [else]') couldn't compile and had been rewritten to plain rest params as a workaround. Root cause: fn* is the primitive; the fn MACRO is what desugars destructuring (rest, map, nested) into the body before lowering. Wrapping expanders in fn instead of fn* compiles any destructured macro arglist uniformly, so the workaround is unnecessary — reverted those 4 macros to the canonical '& [else]' forms. Net: rest-destructuring is fully compiled for all normal code (fn/defn/ let/macro params). Only the hand-written raw fn* primitive still punts (jolt-f79, downgraded to P4 — falls back to interpreter, still correct). 47/47 overlay macros compiled. Gate green: conformance 267x3, fallback-zero 31/5, bootstrap-fixpoint stage1==2==3, self-host, staged-bootstrap, lazy-infinite 44/44, clojure-test-suite >=4034/67, sci-bootstrap, features, all unit+spec.
This commit is contained in:
parent
ad5216a178
commit
20da34a432
3 changed files with 25 additions and 22 deletions
|
|
@ -12,27 +12,25 @@
|
||||||
|
|
||||||
(defmacro comment [& body] nil)
|
(defmacro comment [& body] nil)
|
||||||
|
|
||||||
;; Single arglist (Jolt defmacro is single-arity); the optional else is the head of
|
;; Single arglist (Jolt defmacro is single-arity); the optional else defaults nil
|
||||||
;; a plain rest param — (first else) is the else form or nil. (A `& [else]`-style
|
;; via rest-destructuring.
|
||||||
;; rest-destructure reads the same but the analyzer can't yet compile destructuring
|
(defmacro if-not [test then & [else]]
|
||||||
;; in a rest param, which would force this expander to stay interpreted — jolt-f79.)
|
`(if (not ~test) ~then ~else))
|
||||||
(defmacro if-not [test then & else]
|
|
||||||
`(if (not ~test) ~then ~(first else)))
|
|
||||||
|
|
||||||
;; Conditional binding macros: the name is bound ONLY in the taken branch (the
|
;; Conditional binding macros: the name is bound ONLY in the taken branch (the
|
||||||
;; auto-gensym temp# tests the value; the else/empty branch sees the surrounding
|
;; auto-gensym temp# tests the value; the else/empty branch sees the surrounding
|
||||||
;; scope). temp# is a single template-local gensym — referenced twice, same symbol.
|
;; scope). temp# is a single template-local gensym — referenced twice, same symbol.
|
||||||
(defmacro if-let [bindings then & else]
|
(defmacro if-let [bindings then & [else]]
|
||||||
(let [form (bindings 0) tst (bindings 1)]
|
(let [form (bindings 0) tst (bindings 1)]
|
||||||
`(let [temp# ~tst]
|
`(let [temp# ~tst]
|
||||||
(if temp# (let [~form temp#] ~then) ~(first else)))))
|
(if temp# (let [~form temp#] ~then) ~else))))
|
||||||
|
|
||||||
;; when-let lives in 00-syntax (not here): 20-coll uses it, which loads before this tier.
|
;; when-let lives in 00-syntax (not here): 20-coll uses it, which loads before this tier.
|
||||||
|
|
||||||
(defmacro if-some [bindings then & else]
|
(defmacro if-some [bindings then & [else]]
|
||||||
(let [form (bindings 0) tst (bindings 1)]
|
(let [form (bindings 0) tst (bindings 1)]
|
||||||
`(let [temp# ~tst]
|
`(let [temp# ~tst]
|
||||||
(if (some? temp#) (let [~form temp#] ~then) ~(first else)))))
|
(if (some? temp#) (let [~form temp#] ~then) ~else))))
|
||||||
|
|
||||||
(defmacro when-some [bindings & body]
|
(defmacro when-some [bindings & body]
|
||||||
(let [form (bindings 0) tst (bindings 1)]
|
(let [form (bindings 0) tst (bindings 1)]
|
||||||
|
|
@ -93,9 +91,8 @@
|
||||||
(partition 2 clauses))]
|
(partition 2 clauses))]
|
||||||
`(let [~g ~expr ~@(thread-binds g steps)] ~(if (empty? steps) g (last steps)))))
|
`(let [~g ~expr ~@(thread-binds g steps)] ~(if (empty? steps) g (last steps)))))
|
||||||
|
|
||||||
(defmacro assert [x & message]
|
(defmacro assert [x & [message]]
|
||||||
(let [m (first message)
|
(let [msg (if message message (str "Assert failed: " (pr-str x)))]
|
||||||
msg (if m m (str "Assert failed: " (pr-str x)))]
|
|
||||||
`(when-not ~x (throw (ex-info ~msg {})))))
|
`(when-not ~x (throw (ex-info ~msg {})))))
|
||||||
|
|
||||||
(defmacro delay [& body]
|
(defmacro delay [& body]
|
||||||
|
|
|
||||||
|
|
@ -14,18 +14,21 @@
|
||||||
(import ./stdlib_embed :as stdlib-embed)
|
(import ./stdlib_embed :as stdlib-embed)
|
||||||
(import ./host_iface :as host)
|
(import ./host_iface :as host)
|
||||||
|
|
||||||
# A defmacro expander compiles to a native fn (built as (fn* args body...) and run
|
# A defmacro expander compiles to a native fn (built as (fn args body...) and run
|
||||||
# through the self-hosted pipeline) so macro expansion is COMPILED code, zero runtime
|
# through the self-hosted pipeline) so macro expansion is COMPILED code, zero runtime
|
||||||
# cost — instead of an interpreted closure, mirroring Clojure (macros are ordinary
|
# cost — instead of an interpreted closure, mirroring Clojure (macros are ordinary
|
||||||
# compiled fns). Returns nil when the analyzer isn't built yet (the early macros,
|
# compiled fns). Wrapped in the `fn` MACRO (not the `fn*` primitive) so a destructured
|
||||||
# expanded WHILE the analyzer is being bootstrapped) or the body isn't compilable; in
|
# macro arglist — `[a & [b]]`, `[& {:keys [x]}]`, nested — desugars before lowering;
|
||||||
# that case defmacro keeps an interpreted closure, and backend/recompile-macros!
|
# raw fn* punts on a destructuring rest param. Returns nil when the analyzer isn't
|
||||||
# replaces it with a compiled expander once the analyzer comes alive (staged
|
# built yet (the early macros, expanded WHILE the analyzer is being bootstrapped) or
|
||||||
# bootstrap — the interpreter is a build-time crutch, gone by steady state).
|
# the body isn't compilable; in that case defmacro keeps an interpreted closure, and
|
||||||
|
# backend/recompile-macros! replaces it with a compiled expander once the analyzer
|
||||||
|
# comes alive (staged bootstrap — the interpreter is a build-time crutch, gone by
|
||||||
|
# steady state).
|
||||||
(set macro-compile-hook
|
(set macro-compile-hook
|
||||||
(fn [ctx args-form body]
|
(fn [ctx args-form body]
|
||||||
(backend/try-compile-fn ctx
|
(backend/try-compile-fn ctx
|
||||||
(array/concat @[{:jolt/type :symbol :ns nil :name "fn*"} args-form] body))))
|
(array/concat @[{:jolt/type :symbol :ns nil :name "fn"} args-form] body))))
|
||||||
|
|
||||||
(defn normalize-pvecs
|
(defn normalize-pvecs
|
||||||
"Deep-convert any sequential (pvec/tuple/array) to a Janet tuple. Test helper
|
"Deep-convert any sequential (pvec/tuple/array) to a Janet tuple. Test helper
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,10 @@
|
||||||
(def r (protect (eval (compiled 1) (comp/ctx-janet-env ctx))))
|
(def r (protect (eval (compiled 1) (comp/ctx-janet-env ctx))))
|
||||||
(when (r 0) (r 1)))))
|
(when (r 0) (r 1)))))
|
||||||
|
|
||||||
(def- fn*-sym {:jolt/type :symbol :ns nil :name "fn*"})
|
# Wrap expanders in the `fn` MACRO, not the `fn*` primitive: `fn` desugars a
|
||||||
|
# destructured macro arglist (`[a & [b]]`, `[& {:keys [x]}]`) before lowering,
|
||||||
|
# whereas raw fn* punts on a destructuring rest param.
|
||||||
|
(def- fn-sym {:jolt/type :symbol :ns nil :name "fn"})
|
||||||
|
|
||||||
(defn recompile-macros!
|
(defn recompile-macros!
|
||||||
"Staged-bootstrap second pass: once the self-hosted analyzer is alive, replace
|
"Staged-bootstrap second pass: once the self-hosted analyzer is alive, replace
|
||||||
|
|
@ -398,7 +401,7 @@
|
||||||
(not (v :macro-uses-env)))
|
(not (v :macro-uses-env)))
|
||||||
(def [args-form body] (v :macro-src))
|
(def [args-form body] (v :macro-src))
|
||||||
(def compiled
|
(def compiled
|
||||||
(try-compile-fn ctx (array/concat @[fn*-sym args-form] body)))
|
(try-compile-fn ctx (array/concat @[fn-sym args-form] body)))
|
||||||
(when compiled
|
(when compiled
|
||||||
(bind-root v compiled)
|
(bind-root v compiled)
|
||||||
(put v :macro-compiled true)
|
(put v :macro-compiled true)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue