From 2727a2ed8c109ba90bc6bda9155332429ba84eff Mon Sep 17 00:00:00 2001 From: giles Date: Mon, 23 Mar 2026 18:45:40 +0000 Subject: [PATCH] Skip JIT for lambdas with closure bindings The closure merging in jit_compile_lambda (copying globals + injecting closure bindings into vm_env_ref) produces incorrect variable resolution for inner functions. Symptoms: sx-parse's read-list-loop mishandles closing parens (siblings become children), parser produces wrong ASTs. Fix: vm_call skips JIT compilation for lambdas with non-empty closures. These run on CEK which handles closures correctly. Top-level defines (empty closure) are still JIT-compiled. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/lib/sx_vm.ml | 8 +++++++- shared/sx/ocaml_bridge.py | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hosts/ocaml/lib/sx_vm.ml b/hosts/ocaml/lib/sx_vm.ml index c8fd693..6210e7e 100644 --- a/hosts/ocaml/lib/sx_vm.ml +++ b/hosts/ocaml/lib/sx_vm.ml @@ -327,7 +327,13 @@ and vm_call vm f args = (* Compile failed — CEK *) push vm (Sx_ref.cek_call f (List args)) | None -> - if l.l_name <> None then begin + if l.l_name <> None + (* Skip JIT for lambdas with closure bindings — the closure + merging into vm_env_ref produces incorrect variable resolution + for inner functions (e.g. parser's read-list-loop). *) + && Hashtbl.length l.l_closure.bindings = 0 + && l.l_closure.parent = None + then begin (* Pre-mark before compile attempt to prevent re-entrancy *) l.l_compiled <- Some jit_failed_sentinel; match !jit_compile_ref l vm.globals with diff --git a/shared/sx/ocaml_bridge.py b/shared/sx/ocaml_bridge.py index bbc13d3..184eb3e 100644 --- a/shared/sx/ocaml_bridge.py +++ b/shared/sx/ocaml_bridge.py @@ -439,6 +439,7 @@ class OcamlBridge: skipped += 1 _logger.warning("OCaml load skipped %s: %s", filepath, e) + # SSR overrides: effect is a no-op on the server (prevents # reactive loops during island SSR — effects are DOM side-effects) try: