compiler: interpreter fallback is deliberate-only (Stage 2 Task 3)
compile-and-eval wrapped the compile step in a blanket protect — any failure,
including a genuine compiler bug, silently fell back to the interpreter and
hid behind a correct-looking result. Now only the analyzer's deliberate punt
signal (jolt/uncompilable: …) falls back; any other compile-step error
propagates.
Tightening this surfaced one accidental dependency: pre-kernel overlay forms
(00-syntax's destructure defn) trigger a compile while ensure-analyzer is
still gated, and the missing analyzer crashed var-get with a cryptic indexing
error that the blanket catch happened to convert into the designed interpret
fallback. That path now punts explicitly ("jolt/uncompilable: analyzer not
built (pre-kernel bootstrap)").
New unit test stubs jolt.analyzer/analyze to prove a non-punt error
propagates while the punt channel still falls back.
Gate: conformance 269x3, fallback-zero, fixpoint stage1==2==3, self-host,
sci-bootstrap, staged-bootstrap, all specs+unit, suite 4046>=4034 (5 timeouts);
core-bench A/B neutral (4562 vs 4572 ms).
This commit is contained in:
parent
3e9fe8d0fb
commit
29fdd8ff7f
2 changed files with 61 additions and 4 deletions
|
|
@ -352,20 +352,36 @@
|
|||
# while it runs — so h/current-ns must read this instead of ctx-current-ns.
|
||||
(put (ctx :env) :compile-ns (ctx-current-ns ctx))
|
||||
(def av (ns-find (ctx-find-ns ctx "jolt.analyzer") "analyze"))
|
||||
# Pre-kernel bootstrap: ensure-analyzer is gated until the kernel tier loads
|
||||
# (see api/load-core-overlay!), so a compile request from an earlier tier (e.g.
|
||||
# 00-syntax's destructure defn) finds no analyzer. That fallback is DESIGNED —
|
||||
# route it through the sanctioned punt channel rather than crashing on a nil var.
|
||||
(unless av (error "jolt/uncompilable: analyzer not built (pre-kernel bootstrap)"))
|
||||
(def r ((var-get av) ctx form))
|
||||
(put (ctx :env) :compile-ns nil)
|
||||
r)
|
||||
|
||||
# The analyzer's deliberate punt signal — (uncompilable why) throws the string
|
||||
# "jolt/uncompilable: <why>". Anything else escaping the compile step is an
|
||||
# unexpected compiler error, not a punt.
|
||||
(defn- uncompilable-error? [err]
|
||||
(and (or (string? err) (buffer? err))
|
||||
(string/has-prefix? "jolt/uncompilable" (string err))))
|
||||
|
||||
(defn compile-and-eval
|
||||
"Self-hosted compile path: analyze (portable Clojure) -> IR -> Janet -> eval.
|
||||
Hybrid: only the compile step (analyze+emit) is guarded — a form the analyzer
|
||||
can't handle throws and falls back to the interpreter; runtime errors in
|
||||
compiled code propagate (no double-eval, no hidden errors)."
|
||||
The interpreter fallback is DELIBERATE-ONLY (Stage 2): only an analyzer punt
|
||||
(jolt/uncompilable — the curated stateful/letrec set) falls back; any other
|
||||
compile-step error is a compiler bug and propagates rather than being silently
|
||||
hidden by interpretation. Runtime errors in compiled code propagate as before
|
||||
(no double-eval, no hidden errors)."
|
||||
[ctx form]
|
||||
(def compiled (protect (emit-ir ctx (analyze-form ctx form))))
|
||||
(if (compiled 0)
|
||||
(eval (compiled 1) (comp/ctx-janet-env ctx))
|
||||
(eval-form ctx @{} form)))
|
||||
(if (uncompilable-error? (compiled 1))
|
||||
(eval-form ctx @{} form)
|
||||
(error (compiled 1)))))
|
||||
|
||||
(defn analyzer-built? [ctx]
|
||||
(> (length ((ctx-find-ns ctx "jolt.analyzer") :mappings)) 0))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue