Fix JIT server hang: compiled compiler helpers loop on complex ASTs
Root cause: pre-compiled compiler helper functions (compile-expr, compile-cond, etc.) produce bytecode that loops when processing deeply nested ASTs like tw-resolve-style. The test suite passes because _jit_compiling prevents compiled function execution during compilation — all functions run via CEK. The server pre-compiled helpers, so they ran as bytecode during compilation, triggering loops. Fix: - _jit_compiling guard on the "already compiled" hook branch prevents compiled functions from running during JIT compilation. Compilation always uses CEK (correct for all AST sizes). Normal execution uses bytecode (fast). - "compile" itself marked as jit_failed_sentinel — never JIT compiled. Runs via CEK, while its helpers use bytecode for normal (non-compile) execution. - Server hook uses call_closure (own VM per call) for IO suspension safety. MCP uses call_closure_reuse (fast, no IO needed). The underlying bytecode bug in the compiled helpers remains — fixing it requires diagnosing which specific helper loops and why. This is tracked as a separate issue. Server now starts in ~30s (pre-warm) and serves pages correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -936,11 +936,11 @@ let register_jit_hook env =
|
|||||||
| Lambda l ->
|
| Lambda l ->
|
||||||
(match l.l_compiled with
|
(match l.l_compiled with
|
||||||
| Some cl when not (Sx_vm.is_jit_failed cl) ->
|
| Some cl when not (Sx_vm.is_jit_failed cl) ->
|
||||||
(* Cached bytecode — run via VM. Skip during compilation to avoid
|
(* Skip during compilation — compiled helpers loop on complex ASTs.
|
||||||
compiled-compiler bytecode bugs on complex nested forms. *)
|
Normal execution uses bytecode (fast). *)
|
||||||
if !(Sx_vm._jit_compiling) then None
|
if !(Sx_vm._jit_compiling) then None
|
||||||
else
|
else
|
||||||
(try Some (Sx_vm.call_closure_reuse cl args)
|
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
|
||||||
with
|
with
|
||||||
| Sx_vm.VmSuspended (request, saved_vm) ->
|
| Sx_vm.VmSuspended (request, saved_vm) ->
|
||||||
Some (make_vm_suspend_marker request saved_vm)
|
Some (make_vm_suspend_marker request saved_vm)
|
||||||
@@ -966,7 +966,7 @@ let register_jit_hook env =
|
|||||||
match compiled with
|
match compiled with
|
||||||
| Some cl ->
|
| Some cl ->
|
||||||
l.l_compiled <- Some cl;
|
l.l_compiled <- Some cl;
|
||||||
(try Some (Sx_vm.call_closure_reuse cl args)
|
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
|
||||||
with
|
with
|
||||||
| Sx_vm.VmSuspended (request, saved_vm) ->
|
| Sx_vm.VmSuspended (request, saved_vm) ->
|
||||||
Some (make_vm_suspend_marker request saved_vm)
|
Some (make_vm_suspend_marker request saved_vm)
|
||||||
@@ -1185,8 +1185,9 @@ let rec dispatch env cmd =
|
|||||||
register_jit_hook env;
|
register_jit_hook env;
|
||||||
let t0 = Unix.gettimeofday () in
|
let t0 = Unix.gettimeofday () in
|
||||||
let count = ref 0 in
|
let count = ref 0 in
|
||||||
|
(* Pre-compile helpers, NOT "compile" itself (loops on complex ASTs) *)
|
||||||
let compiler_names = [
|
let compiler_names = [
|
||||||
"compile"; "compile-module"; "compile-expr"; "compile-symbol";
|
"compile-module"; "compile-expr"; "compile-symbol";
|
||||||
"compile-dict"; "compile-list"; "compile-if"; "compile-when";
|
"compile-dict"; "compile-list"; "compile-if"; "compile-when";
|
||||||
"compile-and"; "compile-or"; "compile-begin"; "compile-let";
|
"compile-and"; "compile-or"; "compile-begin"; "compile-let";
|
||||||
"compile-letrec"; "compile-lambda"; "compile-define"; "compile-set";
|
"compile-letrec"; "compile-lambda"; "compile-define"; "compile-set";
|
||||||
@@ -1208,6 +1209,11 @@ let rec dispatch env cmd =
|
|||||||
| None -> ())
|
| None -> ())
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
) compiler_names;
|
) compiler_names;
|
||||||
|
(* Mark "compile" as jit-failed — loops on complex ASTs as bytecode *)
|
||||||
|
(try match Hashtbl.find_opt env.bindings (Sx_types.intern "compile") with
|
||||||
|
| Some (Lambda l) -> l.l_compiled <- Some Sx_vm.jit_failed_sentinel
|
||||||
|
| _ -> ()
|
||||||
|
with _ -> ());
|
||||||
let dt = Unix.gettimeofday () -. t0 in
|
let dt = Unix.gettimeofday () -. t0 in
|
||||||
Printf.eprintf "[jit] Pre-compiled %d compiler functions in %.3fs (lazy JIT active for all)\n%!" !count dt;
|
Printf.eprintf "[jit] Pre-compiled %d compiler functions in %.3fs (lazy JIT active for all)\n%!" !count dt;
|
||||||
send_ok ()
|
send_ok ()
|
||||||
@@ -2664,14 +2670,23 @@ let http_mode port =
|
|||||||
| _ -> raise (Eval_error "component-source: expected (name)"));
|
| _ -> raise (Eval_error "component-source: expected (name)"));
|
||||||
let jt0 = Unix.gettimeofday () in
|
let jt0 = Unix.gettimeofday () in
|
||||||
let count = ref 0 in
|
let count = ref 0 in
|
||||||
|
(* Pre-compile compiler HELPERS but NOT "compile" itself.
|
||||||
|
"compile" runs via CEK (correct for all AST sizes).
|
||||||
|
Its internal calls to compile-expr/emit-byte/etc use bytecode (fast).
|
||||||
|
Pre-compiling "compile" causes it to loop on complex nested forms. *)
|
||||||
let compiler_names = [
|
let compiler_names = [
|
||||||
"compile"; "compile-module"; "compile-expr"; "compile-symbol";
|
"compile-module"; "compile-expr"; "compile-symbol";
|
||||||
"compile-dict"; "compile-list"; "compile-if"; "compile-when";
|
"compile-dict"; "compile-list"; "compile-if"; "compile-when";
|
||||||
"compile-and"; "compile-or"; "compile-begin"; "compile-let";
|
"compile-and"; "compile-or"; "compile-begin"; "compile-let";
|
||||||
"compile-letrec"; "compile-lambda"; "compile-define"; "compile-set";
|
"compile-letrec"; "compile-lambda"; "compile-define"; "compile-set";
|
||||||
"compile-quote"; "compile-cond"; "compile-case"; "compile-case-clauses";
|
"compile-quote"; "compile-cond"; "compile-case"; "compile-case-clauses";
|
||||||
"compile-thread"; "compile-thread-step"; "compile-defcomp";
|
"compile-thread"; "compile-thread-step"; "compile-defcomp";
|
||||||
"compile-defisland"; "compile-defmacro";
|
"compile-defisland"; "compile-defmacro";
|
||||||
|
"compile-quasiquote"; "compile-qq-expr"; "compile-qq-list"; "compile-call";
|
||||||
|
"make-emitter"; "make-pool"; "make-scope"; "pool-add";
|
||||||
|
"scope-define-local"; "scope-resolve";
|
||||||
|
"emit-byte"; "emit-u16"; "emit-i16"; "emit-op"; "emit-const";
|
||||||
|
"current-offset"; "patch-i16";
|
||||||
] in
|
] in
|
||||||
List.iter (fun name ->
|
List.iter (fun name ->
|
||||||
try
|
try
|
||||||
@@ -2684,6 +2699,12 @@ let http_mode port =
|
|||||||
| _ -> ()
|
| _ -> ()
|
||||||
with _ -> ()
|
with _ -> ()
|
||||||
) compiler_names;
|
) compiler_names;
|
||||||
|
(* Mark "compile" as jit-failed — its compiled bytecode loops on complex
|
||||||
|
ASTs. It runs via CEK (correct), while its helpers run as bytecode (fast). *)
|
||||||
|
(try match env_get env "compile" with
|
||||||
|
| Lambda l -> l.l_compiled <- Some Sx_vm.jit_failed_sentinel
|
||||||
|
| _ -> ()
|
||||||
|
with _ -> ());
|
||||||
let jt1 = Unix.gettimeofday () in
|
let jt1 = Unix.gettimeofday () in
|
||||||
Printf.eprintf "[sx-http] JIT pre-compiled %d compiler fns in %.3fs\n%!" !count (jt1 -. jt0);
|
Printf.eprintf "[sx-http] JIT pre-compiled %d compiler fns in %.3fs\n%!" !count (jt1 -. jt0);
|
||||||
(* Re-bind native primitives that stdlib.sx may have overwritten with
|
(* Re-bind native primitives that stdlib.sx may have overwritten with
|
||||||
|
|||||||
Reference in New Issue
Block a user