JIT allowlist + integration tests + --test mode + clean up debug logging

JIT allowlist (sx_server.ml):
- Replace try-every-lambda strategy with StringSet allowlist. Only
  functions in the list get JIT compiled (compiler, parser, pure transforms).
  Render functions that need dynamic scope skip JIT entirely — no retry
  overhead, no silent fallbacks.
- Add (jit-allow name) command for dynamic expansion from Python bridge.
- JIT failures log once with "[jit] DISABLED fn — reason" then go silent.

Standalone --test mode (sx_server.ml):
- New --test flag loads full env (spec + adapters + compiler + signals),
  supports --eval and --load flags. Quick kernel testing without Docker.
  Example: dune exec bin/sx_server.exe -- --test --eval '(len HTML_TAGS)'

Integration tests (integration_tests.ml):
- New binary exercising the full rendering pipeline: loads spec + adapters
  into a server-like env, renders HTML via both native and SX adapter paths.
- 26 tests: HTML tags, special forms (when/if/let), letrec with side
  effects, component rendering, eval-expr with HTML tag functions.
- Would have caught the "Undefined symbol: div/lake/init" issues from
  the previous commit immediately without Docker.

VM cleanup (sx_vm.ml):
- Remove temporary debug logging (insn counter, call_closure counter,
  VmClosure depth tracking) added during debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 23:58:40 +00:00
parent dd057247a5
commit 5270d2e956
8 changed files with 573 additions and 77 deletions

View File

@@ -908,6 +908,32 @@ let make_server_env () =
CEK, cache result). cek_call checks this before CEK dispatch. *)
let _jit_compiling = ref false (* re-entrancy guard *)
(** Functions allowed for JIT compilation. Others go straight to CEK.
Populated with compiler internals at registration time; extended
dynamically via (jit-allow name) command. *)
module StringSet = Set.Make(String)
let jit_allowlist = ref (StringSet.of_list [
(* Compiler internals *)
"compile"; "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-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";
(* Parser *)
"sx-parse"; "sx-serialize"; "sx-serialize-dict";
(* Pure transforms *)
"aser"; "resolve-nav-path"; "tw"; "serialize";
"render-to-html"; "render-attrs"; "render-html-element";
"merge-spread-attrs"; "cssx-process-token";
"signal"; "computed"; "freeze-scope"; "freeze-signal";
])
let register_jit_hook env =
Sx_ref.jit_call_hook := Some (fun f args ->
@@ -920,29 +946,28 @@ let register_jit_hook env =
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
with e ->
let fn_name = match l.l_name with Some n -> n | None -> "?" in
Printf.eprintf "[jit-hook] VM FAIL %s: %s (disabling JIT)\n%!" fn_name (Printexc.to_string e);
Printf.eprintf "[jit] DISABLED %s %s\n%!" fn_name (Printexc.to_string e);
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
None)
| Some _ -> None (* compile failed — CEK handles *)
| Some _ -> None (* compile failed or disabled — CEK handles *)
| None ->
let fn_name = match l.l_name with Some n -> n | None -> "?" in
if !_jit_compiling then None
else if not (StringSet.mem fn_name !jit_allowlist) then None
else begin
let fn_name = match l.l_name with Some n -> n | None -> "?" in
_jit_compiling := true;
let t0 = Unix.gettimeofday () in
let compiled = Sx_vm.jit_compile_lambda l env.bindings in
let dt = Unix.gettimeofday () -. t0 in
_jit_compiling := false;
Printf.eprintf "[jit-hook] %s compile %s in %.3fs\n%!"
Printf.eprintf "[jit] %s compile %s in %.3fs\n%!"
fn_name (match compiled with Some _ -> "OK" | None -> "FAIL") dt;
match compiled with
| Some cl ->
l.l_compiled <- Some cl;
(* Run on VM, fall back to CEK on runtime error *)
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
with e ->
let fn_name = match l.l_name with Some n -> n | None -> "?" in
Printf.eprintf "[jit-hook] VM FAIL (first call) %s: %s (disabling JIT)\n%!" fn_name (Printexc.to_string e);
Printf.eprintf "[jit] DISABLED %s — %s\n%!" fn_name (Printexc.to_string e);
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
None)
| None -> None
@@ -1106,6 +1131,10 @@ let rec dispatch env cmd =
Printf.eprintf "[jit] Pre-compiled %d compiler functions in %.3fs\n%!" !count dt;
send_ok ()
| List [Symbol "jit-allow"; String name] ->
jit_allowlist := StringSet.add name !jit_allowlist;
send_ok ()
| List [Symbol "aser-slot"; String src] ->
(* Expand ALL components server-side. Uses batch IO mode.
Calls aser via CEK — the JIT hook compiles it on first call. *)
@@ -1449,10 +1478,69 @@ let cli_mode mode =
Printf.eprintf "Error: %s\n" (Printexc.to_string exn); exit 1)
let test_mode () =
let env = make_server_env () in
(* Load full spec + adapter stack *)
let base = try Sys.getenv "SX_SPEC_DIR" with Not_found -> "spec" in
let web_base = try Sys.getenv "SX_WEB_DIR" with Not_found -> "web" in
let files = [
Filename.concat base "parser.sx";
Filename.concat base "render.sx";
Filename.concat base "compiler.sx";
Filename.concat web_base "signals.sx";
Filename.concat web_base "adapter-html.sx";
Filename.concat web_base "adapter-sx.sx";
] in
cli_load_files env files;
(* Register JIT *)
register_jit_hook env;
(* Load any --load files *)
let load_files = ref [] in
let eval_exprs = ref [] in
let args = Array.to_list Sys.argv in
let rec scan = function
| "--load" :: path :: rest -> load_files := path :: !load_files; scan rest
| "--eval" :: expr :: rest -> eval_exprs := expr :: !eval_exprs; scan rest
| _ :: rest -> scan rest
| [] -> ()
in scan args;
cli_load_files env (List.rev !load_files);
if !eval_exprs <> [] then
List.iter (fun src ->
try
let exprs = Sx_parser.parse_all src in
let result = List.fold_left (fun _ e ->
Sx_ref.eval_expr e (Env env)) Nil exprs in
Printf.printf "%s\n%!" (serialize_value result)
with
| Eval_error msg -> Printf.eprintf "Error: %s\n%!" msg; exit 1
| exn -> Printf.eprintf "Error: %s\n%!" (Printexc.to_string exn); exit 1
) (List.rev !eval_exprs)
else begin
(* Read from stdin *)
let buf = Buffer.create 4096 in
(try while true do
let line = input_line stdin in
Buffer.add_string buf line; Buffer.add_char buf '\n'
done with End_of_file -> ());
let src = String.trim (Buffer.contents buf) in
if src <> "" then begin
try
let exprs = Sx_parser.parse_all src in
let result = List.fold_left (fun _ e ->
Sx_ref.eval_expr e (Env env)) Nil exprs in
Printf.printf "%s\n%!" (serialize_value result)
with
| Eval_error msg -> Printf.eprintf "Error: %s\n%!" msg; exit 1
| exn -> Printf.eprintf "Error: %s\n%!" (Printexc.to_string exn); exit 1
end
end
let () =
(* Check for CLI mode flags *)
let args = Array.to_list Sys.argv in
if List.mem "--render" args then cli_mode "render"
if List.mem "--test" args then test_mode ()
else if List.mem "--render" args then cli_mode "render"
else if List.mem "--aser-slot" args then cli_mode "aser-slot"
else if List.mem "--aser" args then cli_mode "aser"
else begin