jolt build: compile an app to a standalone binary (Phase 4 stages 1-2)

Restores the standalone-binary capability the Janet host had. `bin/joltc build
-m NS -o OUT` AOT-compiles an app into a single self-contained executable — the
whole runtime, clojure.core, stdlib and compiler embedded, no Chez install or
jolt source needed at runtime.

Pipeline (host/chez/build.ss, host primitive jolt.host/build-binary driven by
jolt.main's build command): resolve deps, load the entry namespace recording the
app namespaces in dependency order, re-emit each to Scheme, textually inline the
cli.ss runtime load sequence into one flat source + the app + a launcher, then
compile-file -> make-boot-file -> embed the boot as C bytes -> cc-link against
libkernel.a.

Two non-obvious bits: the compile pass runs in a fresh Chez, not the loaded
runtime (regex.ss shadows top-level `error`, which otherwise bakes a broken
reference into the boot); and the launcher installs scheme-start rather than
running -main at top level, since boot top-level forms execute during heap build
before argv is set, so args only reach -main through scheme-start.

Loader: a require of an in-memory namespace with no source file now no-ops, so
AOT'd app namespaces satisfy require in a built binary.

Mode flags (--opt/--dev, default release) are plumbed; the optimization passes
they gate come in a later stage. RFC 0007 has the design. Gated by `make
buildsmoke`.
This commit is contained in:
Yogthos 2026-06-22 23:01:36 -04:00
parent 33eff7c7d8
commit 43778eafd7
10 changed files with 554 additions and 12 deletions

View file

@ -0,0 +1 @@
{:paths ["src"]}

View file

@ -0,0 +1,7 @@
(ns app.core
(:require [app.util :as util]))
(defn -main [& args]
(util/twice (println (util/shout "hello from a built binary")))
(println "args:" (vec args))
(println "sum:" (reduce + (map count args))))

View file

@ -0,0 +1,8 @@
(ns app.util
(:require [clojure.string :as str]))
(defn shout [s]
(str/upper-case (str s "!")))
(defmacro twice [x]
`(do ~x ~x))