compiler: lower syntax-quote to construction code so backtick compiles

Following the canonical read->macroexpand->compile model (Clojure LispReader /
tools.reader): a syntax-quote is lowered to plain construction code instead of
being interpreted. Adds form builders (__sqcat -> array, __sqvec -> tuple,
__sqmap, __sq1) and syntax-quote-lower (mirrors the interpreter's syntax-quote*/
sq-symbol exactly: foo# auto-gensym, core/special unqualified, else qualify;
~ -> expr, ~@ -> splice). The analyzer now handles syntax-quote as a special:
lower then analyze the result. The interpreter keeps the self-contained
syntax-quote* (no core dependency, works in a bare ctx); the two are cross-checked
by conformance interpret-vs-compile.

Effect: a backtick body compiles (1.96s -> 1.19s/200k) instead of falling back to
the interpreter. Next: compile defmacro expanders for the full macro speedup.

conformance 228/228 x3, full suite green.
This commit is contained in:
Yogthos 2026-06-07 09:32:52 -04:00
parent ed118b9ce6
commit b438835cf6
4 changed files with 83 additions and 2 deletions

View file

@ -22,12 +22,13 @@
form-literal? form-elements form-vec-items form-literal? form-elements form-vec-items
form-map-pairs form-special? compile-ns form-map-pairs form-special? compile-ns
form-macro? form-expand-1 resolve-global form-macro? form-expand-1 resolve-global
form-sym-meta host-intern!]])) form-sym-meta host-intern! form-syntax-quote-lower]]))
(declare analyze) (declare analyze)
(def ^:private handled (def ^:private handled
#{"quote" "if" "do" "def" "fn*" "let*" "loop*" "recur" "throw" "try"}) #{"quote" "if" "do" "def" "fn*" "let*" "loop*" "recur" "throw" "try"
"syntax-quote"})
(defn- uncompilable [why] (defn- uncompilable [why]
(throw (str "jolt/uncompilable: " why))) (throw (str "jolt/uncompilable: " why)))
@ -159,6 +160,9 @@
:args (mapv #(analyze ctx % env) (rest items))}) :args (mapv #(analyze ctx % env) (rest items))})
"try" (analyze-try ctx items env) "try" (analyze-try ctx items env)
"fn*" (analyze-fn ctx items env) "fn*" (analyze-fn ctx items env)
;; Lower the backtick to construction code (zero runtime cost), then analyze
;; it — the macroexpand/compile-time step, per read -> macroexpand -> compile.
"syntax-quote" (analyze ctx (form-syntax-quote-lower ctx (second items)) env)
(uncompilable (str "special form " op)))) (uncompilable (str "special form " op))))
(defn- analyze-symbol [ctx form env] (defn- analyze-symbol [ctx form env]

View file

@ -124,6 +124,26 @@
items) items)
c)) c))
# Syntax-quote form builders. The syntax-quote lowering (evaluator) emits calls to
# these so a `(...)/`[...] body is plain compilable code instead of an interpreted
# special form. A list FORM is a Janet array, a vector FORM a tuple (the reader's
# representation), so these build those types. Each concat part is either a 1-elem
# wrap (__sq1, a non-spliced item) or a spliced seq (~@), flattened in order.
(defn core-sq1 [x] @[x])
(defn core-sqcat [& parts]
(def r @[])
(each p parts (each x (realize-for-iteration p) (array/push r x)))
r)
(defn core-sqvec [& parts]
(def r @[])
(each p parts (each x (realize-for-iteration p) (array/push r x)))
(tuple/slice r))
# Map builder: parts are alternating k v (no splicing in map syntax-quote).
(defn core-sqmap [& parts] (kvs->map (array ;parts)))
# ============================================================ # ============================================================
# Predicates # Predicates
# ============================================================ # ============================================================
@ -3289,6 +3309,10 @@
"cons" core-cons "cons" core-cons
"seq" core-seq "seq" core-seq
"vec" core-vec "vec" core-vec
"__sq1" core-sq1
"__sqcat" core-sqcat
"__sqvec" core-sqvec
"__sqmap" core-sqmap
"into" core-into "into" core-into
"merge" core-merge "merge" core-merge
"merge-with" core-merge-with "merge-with" core-merge-with

View file

@ -163,6 +163,49 @@
(array/push kvs (syntax-quote* ctx bindings (get form k) gsmap))) (struct ;kvs)) (array/push kvs (syntax-quote* ctx bindings (get form k) gsmap))) (struct ;kvs))
form)) form))
# Syntax-quote LOWERING: instead of evaluating a `(...) form to a value (what
# syntax-quote* does), produce equivalent CONSTRUCTION CODE so a backtick body is
# plain compilable code (read -> macroexpand -> compile, zero runtime cost).
# Mirrors syntax-quote*/sq-symbol exactly; the canonical algorithm is
# tools.reader's syntax-quote*/expand-list. List forms build via __sqcat (-> array),
# vectors via __sqvec (-> tuple), maps via __sqmap; symbols become (quote resolved);
# ~ leaves the expr in place, ~@ passes the seq straight to __sqcat for splicing.
(defn- sqsym* [nm] {:jolt/type :symbol :ns nil :name nm})
(var syntax-quote-lower nil)
(defn- sq-lower-part [ctx item gsmap]
(if (and (array? item) (> (length item) 0) (sym-name? (first item) "unquote-splicing"))
(in item 1)
@[(sqsym* "__sq1") (syntax-quote-lower ctx item gsmap)]))
(set syntax-quote-lower
(fn syntax-quote-lower [ctx form &opt gsmap]
(default gsmap @{})
(cond
(and (array? form) (> (length form) 0) (sym-name? (first form) "unquote"))
(in form 1)
(and (array? form) (> (length form) 0) (sym-name? (first form) "unquote-splicing"))
(error "~@ used outside of a list or vector in syntax-quote")
(or (number? form) (string? form) (keyword? form) (nil? form) (= true form) (= false form))
form
(and (struct? form) (= :symbol (form :jolt/type)))
@[(sqsym* "quote") (sq-symbol ctx form gsmap)]
(array? form)
(array/concat @[(sqsym* "__sqcat")] (map (fn [it] (sq-lower-part ctx it gsmap)) form))
(tuple? form)
(array/concat @[(sqsym* "__sqvec")] (map (fn [it] (sq-lower-part ctx it gsmap)) form))
# tagged structs (sets/chars): syntax-quote* returns them as-is (no recursion)
(and (struct? form) (get form :jolt/type))
@[(sqsym* "quote") form]
(struct? form)
(do (var parts @[(sqsym* "__sqmap")])
(each k (keys form)
(array/push parts (syntax-quote-lower ctx k gsmap))
(array/push parts (syntax-quote-lower ctx (get form k) gsmap)))
parts)
@[(sqsym* "quote") form])))
(defn resolve-var (defn resolve-var
[ctx bindings sym-s] [ctx bindings sym-s]
(let [name (sym-s :name) ns (sym-s :ns)] (let [name (sym-s :name) ns (sym-s :ns)]
@ -621,6 +664,10 @@
nil)) nil))
(match name (match name
"quote" (in form 1) "quote" (in form 1)
# Interpreter builds the form directly (self-contained, no core dependency).
# The COMPILE path instead lowers syntax-quote to construction code (via
# syntax-quote-lower) so a backtick body is compilable; the two are kept in
# sync and cross-checked by conformance (interpret vs compile modes).
"syntax-quote" (syntax-quote* ctx bindings (in form 1)) "syntax-quote" (syntax-quote* ctx bindings (in form 1))
"unquote" (error "Unquote not valid outside of syntax-quote") "unquote" (error "Unquote not valid outside of syntax-quote")
"unquote-splicing" (error "Unquote-splicing not valid outside of syntax-quote") "unquote-splicing" (error "Unquote-splicing not valid outside of syntax-quote")

View file

@ -143,6 +143,11 @@
# Form predicates use `form-*` names (not list?/vector?/map?/set?/char?) so the # Form predicates use `form-*` names (not list?/vector?/map?/set?/char?) so the
# analyzer can refer them unqualified without the bootstrap's core-renames # analyzer can refer them unqualified without the bootstrap's core-renames
# intercepting them as the value-level predicates. # intercepting them as the value-level predicates.
# Lower a syntax-quote's inner form to construction code (so the analyzer can
# compile it). The portable analyzer calls this and analyzes the result.
(defn h-syntax-quote-lower [ctx inner]
(syntax-quote-lower ctx inner))
(def- exports (def- exports
{"form-sym?" h-sym? "form-sym-name" h-sym-name "form-sym-ns" h-sym-ns {"form-sym?" h-sym? "form-sym-name" h-sym-name "form-sym-ns" h-sym-ns
"form-sym-meta" h-sym-meta "form-sym-meta" h-sym-meta
@ -152,6 +157,7 @@
"form-map-pairs" h-map-pairs "form-set-items" h-set-items "form-map-pairs" h-map-pairs "form-set-items" h-set-items
"form-special?" h-special? "compile-ns" h-current-ns "form-macro?" h-macro? "form-special?" h-special? "compile-ns" h-current-ns "form-macro?" h-macro?
"form-expand-1" h-expand-1 "resolve-global" h-resolve-global "form-expand-1" h-expand-1 "resolve-global" h-resolve-global
"form-syntax-quote-lower" h-syntax-quote-lower
"host-intern!" h-intern!}) "host-intern!" h-intern!})
(defn install! [ctx] (defn install! [ctx]