Lazy JIT compilation: lambdas compile to bytecode on first call

Replace AOT adapter compilation with lazy JIT — each named lambda is
compiled to VM bytecode on first call, cached in l_compiled field for
subsequent calls. Compilation failures fall back to CEK gracefully.

VM types (vm_code, vm_upvalue_cell, vm_closure) moved to sx_types.ml
mutual recursion block. Lambda and Component records gain mutable
l_compiled/c_compiled cache fields. jit_compile_lambda in sx_vm.ml
wraps body as (fn (params) body), invokes spec/compiler.sx via CEK,
extracts inner closure from OP_CLOSURE constant.

JIT hooks in both paths:
- vm_call: Lambda calls from compiled VM code
- continue_with_call: Lambda calls from CEK step loop (injected by
  bootstrap.py post-processing)

Pre-mark sentinel prevents re-entrancy (compile function itself was
hanging when JIT'd mid-compilation). VM execution errors caught and
fall back to CEK with sentinel marking.

Also: add kbd/samp/var to HTML_TAGS, rebuild sx-browser.js, add page
URL to sx-page-full-py timing log.

Performance: first page 28s (JIT compiles 17 functions), subsequent
pages 0.31s home / 0.71s wittgenstein (was 2.3s). All 1945 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 08:18:44 +00:00
parent 7628659854
commit 318c818728
10 changed files with 296 additions and 197 deletions

View File

@@ -230,8 +230,8 @@ async def eval_sx_url(raw_path: str) -> Any:
html = await bridge.sx_page_full(
page_source, shell_kwargs, ctx=ocaml_ctx)
_t3 = _time.monotonic()
logger.info("[sx-page-full-py] ctx=%.3fs kwargs=%.3fs ocaml=%.3fs total=%.3fs",
_t1-_t0, _t2-_t1, _t3-_t2, _t3-_t0)
logger.info("[sx-page-full-py] %s ctx=%.3fs kwargs=%.3fs ocaml=%.3fs total=%.3fs",
raw_path, _t1-_t0, _t2-_t1, _t3-_t2, _t3-_t0)
return await make_response(html, 200)
else:
content_sx = await _eval_slot(wrapped_ast, env, ctx)