From 3620a433c1f3813e1340a956ef3df40fdfd03d51 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 28 Mar 2026 17:02:27 +0000 Subject: [PATCH] sx-http: log JIT fallbacks once per function, not silently or flooding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIT runtime errors now log once per function name via _jit_warned hashtable, then stay quiet for that function. No more silent swallowing (which hid real errors) or per-call flooding (which spammed thousands of lines and blocked the server). VM-level fallbacks (inside JIT-compiled code calling other JIT code) are silent — dedup happens at the hook level. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/bin/sx_server.ml | 18 +++++++++++++++--- hosts/ocaml/lib/sx_vm.ml | 7 +++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/hosts/ocaml/bin/sx_server.ml b/hosts/ocaml/bin/sx_server.ml index 629fdebf..2bc08adf 100644 --- a/hosts/ocaml/bin/sx_server.ml +++ b/hosts/ocaml/bin/sx_server.ml @@ -690,15 +690,24 @@ let _jit_compiling = ref false (* re-entrancy guard *) (* JIT compilation is lazy-only: every named lambda gets one compile attempt on first call. Failures are sentineled (never retried). *) +let _jit_warned : (string, bool) Hashtbl.t = Hashtbl.create 16 + let register_jit_hook env = Sx_ref.jit_call_hook := Some (fun f args -> match f with | Lambda l -> (match l.l_compiled with | Some cl when not (Sx_vm.is_jit_failed cl) -> - (* Cached bytecode — run on VM, fall back to CEK on runtime error. *) + (* Cached bytecode — run on VM, fall back to CEK on runtime error. + Log once per function name, then stay quiet. Don't disable. *) (try Some (Sx_vm.call_closure cl args cl.vm_env_ref) - with _e -> None) (* silent fallback — no disable, no log *) + with e -> + let fn_name = match l.l_name with Some n -> n | None -> "?" in + if not (Hashtbl.mem _jit_warned fn_name) then begin + Hashtbl.replace _jit_warned fn_name true; + Printf.eprintf "[jit] %s runtime fallback to CEK: %s\n%!" fn_name (Printexc.to_string e) + end; + None) | Some _ -> None (* compile failed or disabled — CEK handles *) | None -> let fn_name = match l.l_name with Some n -> n | None -> "?" in @@ -715,7 +724,10 @@ let register_jit_hook env = | Some cl -> l.l_compiled <- Some cl; (try Some (Sx_vm.call_closure cl args cl.vm_env_ref) - with _e -> None) (* silent fallback, keep bytecode *) + with e -> + Printf.eprintf "[jit] %s first-call fallback to CEK: %s\n%!" fn_name (Printexc.to_string e); + Hashtbl.replace _jit_warned fn_name true; + None) | None -> None end) | _ -> None) diff --git a/hosts/ocaml/lib/sx_vm.ml b/hosts/ocaml/lib/sx_vm.ml index d7d24c39..9bff3be1 100644 --- a/hosts/ocaml/lib/sx_vm.ml +++ b/hosts/ocaml/lib/sx_vm.ml @@ -165,7 +165,8 @@ and vm_call vm f args = not the caller's globals. Closure vars were merged at compile time. *) (try push vm (call_closure cl args cl.vm_env_ref) with _e -> - (* Silent fallback to CEK — error is data-dependent, not a JIT bug *) + (* Fallback to CEK — data-dependent error, not a JIT bug. + Dedup logging happens in register_jit_hook. *) push vm (Sx_ref.cek_call f (List args))) | Some _ -> (* Compile failed — CEK *) @@ -179,9 +180,7 @@ and vm_call vm f args = | Some cl -> l.l_compiled <- Some cl; (try push vm (call_closure cl args cl.vm_env_ref) - with _e -> - (* Don't mark failed — error may be data-dependent *) - push vm (Sx_ref.cek_call f (List args))) + with _e -> push vm (Sx_ref.cek_call f (List args))) | None -> push vm (Sx_ref.cek_call f (List args)) end