Next-session pickup doc. Self-host + conformance spec done; Janet removal gated on runtime eval/macros (jolt-r8ku, do first), concurrency (jolt-byjr), host interop (jolt-cf1q.7), deployment/perf (jolt-cf1q.5), then oracle-off-Janet + Phase 5 delete (jolt-cf1q.6). Includes constraints, verify commands, per-blocker approach with file/line pointers, sequencing, and a file map.
9.3 KiB
Handoff — removing Janet (Chez re-host, Phase 4 → Phase 5)
Branch: spike/chez-bootstrap · LOCAL COMMITS ONLY — NEVER push. This
overrides the session-close git-push protocol.
Epic: jolt-cf1q (Re-host Jolt on Chez, zero Janet). Last session: finished
self-hosting (inc8/inc9) + the portable conformance spec + fixed 4 certified bugs.
Where we are
Done and gated (all local commits, latest 6abbea3):
- Self-hosting proven. The on-Chez compiler reproduces itself (stage2==stage3,
prelude pstage3==pstage4).
bin/joltc+host/chez/bootstrap.ssbuild and run jolt with zero Janet in the loop, off the checked-in seedhost/chez/seed/. - Correctness proven. Both Chez corpus gates at 2534/2756, 0 divergences.
The corpus is a JVM-certified host-neutral spec (
test/conformance/, see SPEC.md): 2670 portable cases, 249 feature-gated.
We are NOT ready to delete Janet. Ripping it out now regresses real capability
(below). The remaining work is Phase 4 (runtime features + deployment/perf) → then
Phase 5 (jolt-cf1q.6) deletes src/jolt/*.janet + the Janet cross-compile.
Hard rules (every batch)
- Never
git push. Local commits only on this branch. - Gate discipline. After any change run, with a real exit code:
jpm build && janet run-tests.janet(full Janet gate; must endAll tests passed.)JOLT_CHEZ_ZEROJANET_CORPUS=1 janet test/chez/run-corpus-zero-janet.janet(fast, ~2s)JOLT_CHEZ_PRELUDE_CORPUS=1 janet test/chez/run-corpus-prelude.janet(~18min; run ISOLATED — no concurrent chez work)clojure -M test/conformance/certify.clj(JVM cert; fails on new/stale divergence)
- If
clojure.coreor any seed source changes, re-mint the Chez seed, ortest/chez/bootstrap-test.janetfails (rebuilt ≠ committed seed). Re-mint:
(run from repo root via a throwaway(import host/chez/driver :as d) (import host/chez/jolt-chez :as jc) (def ctx (d/make-ctx)) (d/mint-chez-seed* (jc/ensure-prelude ctx) (d/ensure-compiler-image ctx "/tmp/stage1.ss") "host/chez/seed/prelude.ss" "host/chez/seed/image.ss")test/chez/_x.janet). Raise the corpus floors when parity rises. - Use beads (
bd ready,bd show <id>,bd update <id> --claim,bd close).bd rememberfor persistent notes — see memorieschez-phase3-progress,conformance-spec. Runbd closeas its own command then verify (close-race).
Verify current state (run these first next session)
git log --oneline -8
bin/joltc -e '(+ 1 2)' # => 3 (pure-Chez runtime works)
bin/joltc -e '(eval (quote (+ 1 2)))' # => FAILS today — that's blocker #1
The blockers, in priority order
1. jolt-r8ku — runtime eval / load-string / defmacro on the Chez spine ⭐ DO FIRST
Why first: biggest functional gap, and mostly wiring — the compiler image is
already resident at runtime on Chez (host/chez/compile-eval.ss loads it). Turns
joltc from a one-shot -e into a real runtime/REPL. Unblocks ~the eval/load-string
corpus crashes too.
Symptoms (tested):
joltc -e '(eval (quote (+ 1 2)))' -> jolt/uncompilable: special form eval
joltc -e '(load-string "(+ 1 2)")' -> not a fn (jolt-nil)
joltc -e '(defmacro m [x] ...) (m 5)' -> jolt/uncompilable: special form defmacro
Why they punt: eval/defmacro are in the special-symbol list, so the analyzer
treats them as special forms and the :else arm punts:
host/chez/host-contract.ss:110and:183—hc-special-symbols/form-special?list (includeseval,defmacro).jolt-core/jolt/analyzer.clj:216analyze-special(case op …),:279(uncompilable (str "special form " op)).
Approach:
- eval / load-string are FUNCTIONS, not special forms. Remove
evalfrom the special-symbol lists so it resolves as a normal var, thendef-var!them in a Chez.ss(e.g. a newhost/chez/eval.ssloaded bycompile-eval.ss):eval=(lambda (form) (jolt-compile-eval-form form (current-ns)))— butjolt-compile-eval(compile-eval.ss) takes a SOURCE STRING and reads it; add a sibling that takes an already-read FORM and runs analyze→emit→eval on it.load-string= read-all + eval each form.- Mirror the Janet seed semantics: check
src/jolt/eval_runtime.janet/eval_base.janetfor whateval/load-stringdo there (the contract to match).
- Mirror the Janet seed semantics: check
- defmacro must stay special (it defines a macro). The prelude already handles
defmacro at build time (
host/chez/emit-image.ss→ei-defmacro->fnemits a def of the expander fn +mark-macro!). For RUNTIME user defmacro, add adefmacrobranch toanalyze-specialthat lowers to the same shape: adefof the(fn …)expander- a runtime
mark-macro!call, so the macro is usable in subsequent forms of the same program. Re-analysis of later forms must see the macro flag (hc-macro?readsvar-macro-table).
- a runtime
- Watch
jolt-lpvi(build-ctx vs eval-ctx isolation) — runtime eval defining vars must land in the user ns, not leak into the compiler image ns.
Verify: joltc -e of all three above; add cases to test/chez/spine-test.janet;
the eval/load-string corpus crashes (660/661/2157/2158/… per the old chez-phase2
notes) should now run; re-run both corpus gates.
2. jolt-byjr — concurrency (future / promise / agent / pmap / pvalues / pcalls)
joltc -e '(deref (future (+ 1 2)))' → Unknown class clojure.core. The Janet
oracle uses isolated-heap SNAPSHOT futures (the :concurrency/snapshot feature in
test/conformance/profile.edn) which neither a sync shim nor raw Chez threads
reproduce exactly. Decision needed: real Chez threads vs a sync shim vs snapshot
emulation. The corpus snapshot cases are oracle-coupled (JVM gives shared-heap
answers, jolt gives snapshot) — already classified as a deliberate delta, so don't
chase JVM parity; match the jolt documented semantics. Revisit with a Chez
thread-safety review of the RT (host/chez/*.ss mutable tables: var-table,
ns-registry, atoms).
3. jolt-cf1q.7 — host/Java interop breadth
~167 runtime crashes in the corpus are host-coupled (profile.edn:
:host/jvm-interop 174, :host/arrays 12, :host/janet 16). Java class shims,
arrays, bean/proxy/definterface. Apply vendor-before-scratch: for
non-trivial interop (date/time, crypto, etc.) vendor a mature Chez lib rather than
hand-rolling. Many of these are intentionally non-portable — decide per case whether
Chez should implement it or it stays a host-gated feature. janet.* cases are N/A on
Chez (delete from corpus in Phase 5).
4. jolt-cf1q.5 — Phase 4: deployment & optimization modes
The optimizing compiler is Janet-only: inference, direct-linking, cgen-to-C,
whole-program, and native binary builds (jpm build, the ring-app example). Chez
emits Scheme and relies on Chez's own native compiler — perf is unmeasured. The
Phase 5 bead literally gates on "perf confirmed." Tasks: benchmark the Chez path
vs the Janet/JVM baselines; decide the Chez deployment story (Chez compile-program/
boot file vs script); decide which Janet optimization modes (if any) must be
reproduced. This is the largest unknown — scope it before committing to deletion.
5. Move the test oracle off Janet, then Phase 5 (jolt-cf1q.6) delete
- Today
build/jolt(Janet) is the oracle fortest/chez/_*.janet, andrun-corpus-preludeanalyzes on Janet. The corpus is now JVM-certified, so the oracle can shift to spec-corpus + JVM. Do this swap before deleting Janet. - Tooling still Janet:
jolt-deps,nrepl-server, uberscript/DCE — port or drop. - Then Phase 5: delete
src/jolt/*.janet(reader, value layer, vars/ns, the tree-walking interpreterevaluator.janet, the Janet backend) ANDhost/chez/emit.janet+ the Janet cross-compile indriver.janet. The tree-walking interpreter (the literal "drop the interpreter" goal) dies here.
Sequencing
jolt-r8ku (eval/macros) ─┐
jolt-byjr (concurrency) ├─► jolt-cf1q.7 (host interop) ─► jolt-cf1q.5 (Phase 4 perf/deploy)
─┘ │
oracle → JVM/corpus ◄───────┘
│
▼
jolt-cf1q.6 (Phase 5: DELETE Janet)
Key file map
| Area | File |
|---|---|
| Pure-Chez runtime CLI | bin/joltc → host/chez/cli.ss |
| Zero-Janet compile spine | host/chez/compile-eval.ss (jolt-compile-eval) |
| Host contract (special syms, resolve, macro?) | host/chez/host-contract.ss |
| On-Chez image/prelude emitter | host/chez/emit-image.ss |
| Pure-Chez self-build + seed | host/chez/bootstrap.ss, host/chez/seed/ |
| Portable analyzer / backend | jolt-core/jolt/analyzer.clj, …/backend_scheme.clj |
| clojure.core overlay | jolt-core/clojure/core/NN-*.clj |
| Janet seed (to delete in Phase 5) | src/jolt/*.janet (reader, evaluator, host_iface, …) |
| Chez RT shims | host/chez/*.ss |
| Conformance spec | test/conformance/ (SPEC.md, certify.clj, profile.edn, known-divergences.edn) |
| Corpus + gates | test/chez/corpus.edn, run-corpus-{zero-janet,prelude}.janet, extract-corpus.janet |
| Self-host gates | test/chez/{spine,fixpoint,bootstrap,cli}-test.janet |