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

@@ -84,13 +84,10 @@ let closure_to_value cl =
let _vm_insn_count = ref 0
let _vm_call_count = ref 0
let _vm_cek_count = ref 0
let _vm_closure_call_count = ref 0
let _vm_max_depth = ref 0
let vm_reset_counters () = _vm_insn_count := 0; _vm_call_count := 0; _vm_cek_count := 0;
_vm_closure_call_count := 0; _vm_max_depth := 0
let vm_reset_counters () = _vm_insn_count := 0; _vm_call_count := 0; _vm_cek_count := 0
let vm_report_counters () =
Printf.eprintf "[vm-perf] insns=%d calls=%d cek_fallbacks=%d vm_closure=%d max_depth=%d\n%!"
!_vm_insn_count !_vm_call_count !_vm_cek_count !_vm_closure_call_count !_vm_max_depth
Printf.eprintf "[vm-perf] insns=%d calls=%d cek_fallbacks=%d\n%!"
!_vm_insn_count !_vm_call_count !_vm_cek_count
(** Push a VM closure frame onto the current VM — no new VM allocation.
This is the fast path for intra-VM closure calls. *)
@@ -128,9 +125,6 @@ let code_from_value v =
Used for entry points: JIT Lambda calls, module execution, cross-boundary. *)
let rec call_closure cl args globals =
incr _vm_call_count;
if !_vm_call_count mod 10000 = 0 then
Printf.eprintf "[vm-debug] call_closure count=%d name=%s\n%!"
!_vm_call_count (match cl.vm_name with Some n -> n | None -> "anon");
let vm = create globals in
push_closure_frame vm cl args;
(try run vm with e -> raise e);
@@ -144,13 +138,6 @@ and vm_call vm f args =
match f with
| VmClosure cl ->
(* Fast path: push frame on current VM — no allocation, enables TCO *)
incr _vm_closure_call_count;
let depth = List.length vm.frames + 1 in
if depth > !_vm_max_depth then _vm_max_depth := depth;
if !_vm_closure_call_count mod 100000 = 0 then
Printf.eprintf "[vm-debug] VmClosure calls=%d depth=%d name=%s\n%!"
!_vm_closure_call_count depth
(match cl.vm_name with Some n -> n | None -> "anon");
push_closure_frame vm cl args
| NativeFn (_name, fn) ->
let result = fn args in
@@ -202,12 +189,6 @@ and run vm =
if frame.ip >= Array.length bc then
vm.frames <- [] (* bytecode exhausted — stop *)
else begin
incr _vm_insn_count;
if !_vm_insn_count mod 1000000 = 0 then begin
let fn_name = match frame.closure.vm_name with Some n -> n | None -> "?" in
Printf.eprintf "[vm-debug] insns=%dM in=%s ip=%d depth=%d sp=%d\n%!"
(!_vm_insn_count / 1000000) fn_name frame.ip (List.length vm.frames) vm.sp
end;
let saved_ip = frame.ip in
let op = bc.(frame.ip) in
frame.ip <- frame.ip + 1;