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:
@@ -55,10 +55,15 @@ let trampoline v = !trampoline_fn v
|
||||
let _strict_ref = ref (Bool false)
|
||||
let _prim_param_types_ref = ref Nil
|
||||
|
||||
(* JIT call hook — cek_call checks this before CEK dispatch for named
|
||||
lambdas. Registered by sx_server.ml after compiler loads. Tests
|
||||
run with hook = None (pure CEK, no compilation dependency). *)
|
||||
let jit_call_hook : (value -> value list -> value option) option ref = ref None
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# OCaml fixups — wire up trampoline + iterative CEK run
|
||||
# OCaml fixups — wire up trampoline + iterative CEK run + JIT hook
|
||||
FIXUPS = """\
|
||||
|
||||
(* Wire up trampoline to resolve thunks via the CEK machine *)
|
||||
@@ -75,6 +80,8 @@ let cek_run_iterative state =
|
||||
done;
|
||||
cek_value !s
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -200,9 +207,6 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str:
|
||||
return '\n'.join(fixed)
|
||||
output = fix_mutable_reads(output)
|
||||
|
||||
# Fix cek_call: the spec passes (make-env) as the env arg to
|
||||
# continue_with_call, but the transpiler evaluates it at transpile
|
||||
# time (it's a primitive), producing Dict instead of Env.
|
||||
# Fix cek_call: the spec passes (make-env) as the env arg to
|
||||
# continue_with_call, but the transpiler evaluates make-env at
|
||||
# transpile time (it's a primitive), producing Dict instead of Env.
|
||||
@@ -211,6 +215,29 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str:
|
||||
"(Env (Sx_types.make_env ())) (a) ((List []))",
|
||||
)
|
||||
|
||||
# Inject JIT dispatch into continue_with_call's lambda branch.
|
||||
# After params are bound, check jit_call_hook before creating CEK state.
|
||||
lambda_body_pattern = (
|
||||
'(prim_call "slice" [params; (len (args))])); Nil)) in '
|
||||
'(make_cek_state ((lambda_body (f))) (local) (kont))'
|
||||
)
|
||||
lambda_body_jit = (
|
||||
'(prim_call "slice" [params; (len (args))])); Nil)) in '
|
||||
'(match !jit_call_hook, f with '
|
||||
'| Some hook, Lambda l when l.l_name <> None -> '
|
||||
'let args_list = match args with '
|
||||
'List a | ListRef { contents = a } -> a | _ -> [] in '
|
||||
'(match hook f args_list with '
|
||||
'Some result -> make_cek_value result local kont '
|
||||
'| None -> make_cek_state (lambda_body f) local kont) '
|
||||
'| _ -> make_cek_state ((lambda_body (f))) (local) (kont))'
|
||||
)
|
||||
if lambda_body_pattern in output:
|
||||
output = output.replace(lambda_body_pattern, lambda_body_jit, 1)
|
||||
else:
|
||||
import sys
|
||||
print("WARNING: Could not find lambda body pattern for JIT injection", file=sys.stderr)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user