vm-ext: document guard/PUSH_HANDLER fix + double-exec residual in plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,3 +168,38 @@ Serving-mode JIT is now **opt-in via `SX_SERVING_JIT=1` (default OFF)** in
|
|||||||
page server opts in. This bounds risk: guests are validated and excluded
|
page server opts in. This bounds risk: guests are validated and excluded
|
||||||
incrementally; until then the default protects them. Common-Lisp's advanced
|
incrementally; until then the default protects them. Common-Lisp's advanced
|
||||||
suites still need investigation before CL is opt-in-clean.
|
suites still need investigation before CL is opt-in-clean.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## guard / handler-bind under JIT — central recursive PUSH_HANDLER scan (2026-06-20)
|
||||||
|
|
||||||
|
Combined-binary integration (my JIT + host render-page) surfaced a third
|
||||||
|
JIT-unsafe class beyond guest dispatch cores: **`guard`-based error handling**.
|
||||||
|
The VM's `OP_PUSH_HANDLER` (compiled `guard`) only intercepts a VM-level
|
||||||
|
`RAISE` (opcode 37) — it does NOT catch the OCaml `Eval_error` the `error`
|
||||||
|
primitive throws from a CALL/CALL_PRIM in a callee frame. So a JIT-compiled
|
||||||
|
`guard` silently fails to catch; the thrown error escapes across the JIT frame.
|
||||||
|
|
||||||
|
- SOLID break: `host/wrap-errors -> dream-catch-with` (curried:
|
||||||
|
`(fn (on-error) (fn (next) (fn (req) (guard ...))))`) — middleware suite
|
||||||
|
7/9 under JIT (9/9 CEK), "kaboom" escaped as Unhandled exception, NOT
|
||||||
|
fallback-saved (the guard is in an outer frame, the throw in an inner one).
|
||||||
|
- LATENT (turned out harmless): `host/blog--render-node`'s `guard` — it JIT-
|
||||||
|
failed then the hook RE-RAN it on CEK where the guard caught (pure render, no
|
||||||
|
duplicated effects). This is the double-execution residual firing live.
|
||||||
|
|
||||||
|
Fix: `code_uses_handler` scans a JIT candidate's bytecode **recursively**
|
||||||
|
(including nested closure code in the constant pool) for `OP_PUSH_HANDLER`;
|
||||||
|
`jit_compile_lambda` skips JIT for any match. The recursion is essential —
|
||||||
|
curried `dream-catch-with` has no PUSH_HANDLER in its own body; the guard is in
|
||||||
|
a nested `OP_CLOSURE`. Verified: direct + curried cross-frame guards catch
|
||||||
|
under JIT; host "kaboom" escapes 2 -> 0.
|
||||||
|
|
||||||
|
### Remaining (documented, gated): the double-execution residual
|
||||||
|
The serving hook still re-runs a failed VM execution via CEK (correct result,
|
||||||
|
duplicated side effects if the function is impure and fails mid-run). The guard
|
||||||
|
fix removes the common trigger (guard functions no longer JIT). The clean
|
||||||
|
general fix is propagate-don't-rerun (run_tests' hook semantics) but that
|
||||||
|
surfaces genuine mid-run miscompiles as errors and must land with fixing/
|
||||||
|
excluding those — deferred (shared CEK/VM change). The default-OFF gate makes
|
||||||
|
all of this opt-in, so nothing regresses by default.
|
||||||
|
|||||||
Reference in New Issue
Block a user