init in compile mode is ~2.4 s (tier loading, analyzer self-compile, macro recompilation), paid by every process that builds a ctx from source — each jpm-test file, embedders, workers. init-cached marshals the built ctx to a disk image (same root-env dicts as snapshot/fork) and later processes unmarshal it in ~5 ms, any process: nothing from the baking process is needed at load. The cache key fingerprints the embedded .clj stdlib (which covers jolt-core: analyzer, IR, core tiers), the .janet seed sources next to the module, the janet version, the init opts, and the env knobs that shape a ctx (JOLT_PATH/ MUTABLE/AOT_CORE/FEATURES) — any change rebuilds. Corrupt or non-ctx images fall back to a rebuild (unmarshal of garbage can 'succeed' with a scalar, so the shape is checked, not just the throw). Writes are atomic (tmp + rename) so racing cold starts never publish a torn image. JOLT_NO_IMAGE_CACHE=1 opts out; JOLT_IMAGE_CACHE_DIR overrides the location (default TMPDIR). Test consumers switch to init-cached (harness, suite-worker, conformance, the behavioral unit/integration tests); tests that validate the bootstrap itself (bootstrap-fixpoint, staged-bootstrap, aot round-trip, direct-linking) and the deps tests (tmp-dir :paths would fragment the key) keep real init. Full jpm test: 2:46 -> 1:58 (~29%). New ctx-image-test covers cold/warm, cross-process load (subprocess runs defn/redef/macros/protocols/multimethods off the baked image), per-opts keying, and corrupt-image fallback.
41 lines
2 KiB
Text
41 lines
2 KiB
Text
# Stage 2 Task 3: the compile path's interpreter fallback is DELIBERATE-ONLY.
|
|
#
|
|
# compile-and-eval used to wrap the compile step in a blanket protect — ANY
|
|
# failure (including a genuine compiler bug) silently fell back to the
|
|
# interpreter, hiding the bug behind a correct-looking result. Now only the
|
|
# analyzer's deliberate punt signal ("jolt/uncompilable: …", raised for the
|
|
# curated stateful/letrec set) may fall back; any other compile-step error
|
|
# propagates. Verified here by stubbing jolt.analyzer/analyze.
|
|
|
|
(use ../../src/jolt/types)
|
|
(use ../../src/jolt/api)
|
|
(use ../../src/jolt/reader)
|
|
(import ../../src/jolt/backend :as backend)
|
|
|
|
(def ctx (init-cached))
|
|
|
|
# 1. A deliberate punt (letfn needs letrec IR) falls back and evaluates correctly.
|
|
(assert (= 3 (backend/compile-and-eval ctx (parse-string "(letfn [(f [n] (+ n 1))] (f 2))")))
|
|
"deliberate uncompilable punt falls back to the interpreter")
|
|
|
|
(assert (backend/analyzer-built? ctx) "analyzer built")
|
|
(def analyze-var (ns-find (ctx-find-ns ctx "jolt.analyzer") "analyze"))
|
|
(def real-analyze (var-get analyze-var))
|
|
|
|
# 2. A NON-punt compile error must propagate — even though the interpreter could
|
|
# evaluate the form fine, it must NOT be silently used (that hides compiler bugs).
|
|
(var-set analyze-var (fn [ctx form] (error "boom: simulated compiler bug")))
|
|
(def r (protect (backend/compile-and-eval ctx (parse-string "(+ 1 2)"))))
|
|
(assert (not (r 0)) "non-uncompilable compile error must propagate, not silently interpret")
|
|
(assert (string/find "boom" (string (r 1))) "the original error is surfaced")
|
|
|
|
# 3. The punt marker is the one sanctioned fallback channel.
|
|
(var-set analyze-var (fn [ctx form] (error "jolt/uncompilable: stubbed")))
|
|
(assert (= 3 (backend/compile-and-eval ctx (parse-string "(+ 1 2)")))
|
|
"uncompilable punt falls back to the interpreter")
|
|
|
|
# Restore the real analyzer and confirm the pipeline still works.
|
|
(var-set analyze-var real-analyze)
|
|
(assert (= 7 (backend/compile-and-eval ctx (parse-string "(+ 3 4)"))) "restored analyzer compiles")
|
|
|
|
(print "All compile-fallback tests passed!")
|