From 4d1079aa5eb566fd543c3e0fa101a3ddb850776d Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 7 Apr 2026 22:17:51 +0000 Subject: [PATCH] Fix JIT server hang: compiled compiler helpers loop on complex ASTs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- hosts/ocaml/bin/sx_server.ml | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/hosts/ocaml/bin/sx_server.ml b/hosts/ocaml/bin/sx_server.ml index 1796b5a5..4a769b64 100644 --- a/hosts/ocaml/bin/sx_server.ml +++ b/hosts/ocaml/bin/sx_server.ml @@ -936,11 +936,11 @@ let register_jit_hook env = | Lambda l -> (match l.l_compiled with | Some cl when not (Sx_vm.is_jit_failed cl) -> - (* Cached bytecode — run via VM. Skip during compilation to avoid - compiled-compiler bytecode bugs on complex nested forms. *) + (* Skip during compilation — compiled helpers loop on complex ASTs. + Normal execution uses bytecode (fast). *) if !(Sx_vm._jit_compiling) then None else - (try Some (Sx_vm.call_closure_reuse cl args) + (try Some (Sx_vm.call_closure cl args cl.vm_env_ref) with | Sx_vm.VmSuspended (request, saved_vm) -> Some (make_vm_suspend_marker request saved_vm) @@ -966,7 +966,7 @@ let register_jit_hook env = match compiled with | 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 | Sx_vm.VmSuspended (request, saved_vm) -> Some (make_vm_suspend_marker request saved_vm) @@ -1185,8 +1185,9 @@ let rec dispatch env cmd = register_jit_hook env; let t0 = Unix.gettimeofday () in let count = ref 0 in + (* Pre-compile helpers, NOT "compile" itself (loops on complex ASTs) *) 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-and"; "compile-or"; "compile-begin"; "compile-let"; "compile-letrec"; "compile-lambda"; "compile-define"; "compile-set"; @@ -1208,6 +1209,11 @@ let rec dispatch env cmd = | None -> ()) | _ -> () ) 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 Printf.eprintf "[jit] Pre-compiled %d compiler functions in %.3fs (lazy JIT active for all)\n%!" !count dt; send_ok () @@ -2664,14 +2670,23 @@ let http_mode port = | _ -> raise (Eval_error "component-source: expected (name)")); let jt0 = Unix.gettimeofday () 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 = [ - "compile"; "compile-module"; "compile-expr"; "compile-symbol"; + "compile-module"; "compile-expr"; "compile-symbol"; "compile-dict"; "compile-list"; "compile-if"; "compile-when"; "compile-and"; "compile-or"; "compile-begin"; "compile-let"; "compile-letrec"; "compile-lambda"; "compile-define"; "compile-set"; "compile-quote"; "compile-cond"; "compile-case"; "compile-case-clauses"; "compile-thread"; "compile-thread-step"; "compile-defcomp"; "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 List.iter (fun name -> try @@ -2684,6 +2699,12 @@ let http_mode port = | _ -> () with _ -> () ) 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 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