Wire the optimization pass pipeline into compile + build

The fold/inline/types passes and the jolt.passes façade were baked into neither
seed half and never invoked: compile-eval and build went analyze -> emit directly,
and `jolt build --opt` flipped an optimize flag that nothing consumed.

- Compile the passes into the image (emit-image manifest): fold, inline, types,
  then the jolt.passes façade, after jolt.ir.
- compile-eval and build.ss now run jolt.passes/run-passes between analyze and
  emit. Off the direct-link path it is a pure const-fold; `jolt build --opt`
  turns on inline + flatten + scalar-replace + type inference (it sets
  hc-optimize?, which inline-enabled? reads).
- The seed minter (emit-image) stays analyze -> emit, so the seed is built
  un-optimized and the self-host fixpoint is unaffected.

build-smoke already exercised --opt; it now actually optimizes and still matches
the release binary's output. Corpus floor and the fixpoint are green.
This commit is contained in:
Yogthos 2026-06-23 01:14:14 -04:00
parent e93006b4be
commit 2953320599
4 changed files with 362 additions and 97 deletions

View file

@ -152,7 +152,7 @@
((ce-macro-form? f)
(let-values (((nm fn-form) (ce-defmacro->fn f)))
(let ((scm (let ((ctx (make-analyze-ctx ns-name)))
(jolt-ce-emit (jolt-ce-analyze ctx fn-form)))))
(jolt-ce-emit (jolt-ce-run-passes (jolt-ce-analyze ctx fn-form) ctx)))))
(loop (cdr forms)
(cons (string-append
"(def-var! " (ei-str-lit ns-name) " " (ei-str-lit nm) "\n "
@ -161,13 +161,14 @@
acc)))))
(else
(let* ((ctx (make-analyze-ctx ns-name))
(scm (jolt-ce-emit (jolt-ce-analyze ctx f))))
(scm (jolt-ce-emit (jolt-ce-run-passes (jolt-ce-analyze ctx f) ctx))))
(loop (cdr forms) (cons scm acc)))))))))
;; --- the build --------------------------------------------------------------
;; entry-ns: the app's main namespace (a string). out-path: the binary to write.
;; mode: "dev" | "release" | "optimized" (recorded; optimization passes wired in a
;; later stage). Deps + source roots are already applied by the caller.
;; mode: "dev" | "release" | "optimized". Every form runs through jolt.passes/
;; run-passes (const-fold always; inline + type inference when optimized turns on
;; direct-linking). Deps + source roots are already applied by the caller.
(define (build-binary entry-ns out-path mode)
(bld-check-toolchain)
;; 1. record app namespaces in dependency order as they finish loading.
@ -181,9 +182,7 @@
(error 'jolt-build (string-append "no source namespace loaded for " entry-ns
" — is it on the source roots?")))
;; 2. emit each app namespace. `optimized` turns on the inference + flatten
;; + scalar-replace passes (closed world). release/dev stay on proven
;; var-deref codegen until those passes are validated on Chez — they were
;; dormant before `jolt build` (inline-enabled? was hardwired off).
;; + scalar-replace passes (closed world); release/dev get const-fold only.
(set-optimize! (string=? mode "optimized"))
(let* ((app-strs (apply append
(map (lambda (nf) (bld-emit-ns (car nf) (read-file-string (cdr nf))))

View file

@ -11,6 +11,10 @@
(define jolt-ce-analyze (var-deref "jolt.analyzer" "analyze"))
(define jolt-ce-emit (var-deref "jolt.backend-scheme" "emit"))
;; jolt.passes/run-passes: const-fold every analyzed form, plus inline + type
;; inference when the unit opted into direct-linking (jolt build --opt). Off that
;; path it is a pure const-fold. Loaded from the compiler image (jolt.passes).
(define jolt-ce-run-passes (var-deref "jolt.passes" "run-passes"))
(define jolt-ce-read (var-deref "clojure.core" "read-string"))
;; The spine ALWAYS runs with the full clojure.core prelude loaded, so a clojure.*
@ -59,7 +63,7 @@
(define (jolt-analyze-emit-form form ns)
(ce-scan-requires! form ns)
(let* ((ctx (make-analyze-ctx ns))
(ir (jolt-ce-analyze ctx form)))
(ir (jolt-ce-run-passes (jolt-ce-analyze ctx form) ctx)))
(jolt-ce-emit ir)))
;; --- runtime defmacro -------------------------------------------------------

View file

@ -69,11 +69,17 @@
;; (printable ASCII identifiers only here).
(define (ei-str-lit s) (with-output-to-string (lambda () (write s))))
;; The compiler namespaces, in load order.
;; The compiler namespaces, in load order. The passes (fold/inline/types + the
;; jolt.passes façade) load after ir so run-passes is available to the back end;
;; fold/inline/types come before the façade that :refers them.
(define ei-compiler-ns-files
(list (cons "jolt.ir" "jolt-core/jolt/ir.clj")
(cons "jolt.analyzer" "jolt-core/jolt/analyzer.clj")
(cons "jolt.backend-scheme" "jolt-core/jolt/backend_scheme.clj")))
(cons "jolt.backend-scheme" "jolt-core/jolt/backend_scheme.clj")
(cons "jolt.passes.fold" "jolt-core/jolt/passes/fold.clj")
(cons "jolt.passes.inline" "jolt-core/jolt/passes/inline.clj")
(cons "jolt.passes.types" "jolt-core/jolt/passes/types.clj")
(cons "jolt.passes" "jolt-core/jolt/passes.clj")))
;; The clojure.core tiers + stdlib namespaces, in load order.
;; Re-emitting these on Chez is the

File diff suppressed because one or more lines are too long