From b4107fa52b1b61edf83f29cb421f9e6f72c8ce73 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 29 Mar 2026 21:19:35 +0000 Subject: [PATCH] =?UTF-8?q?Restore=20JIT=20closure=20injection=20=E2=80=94?= =?UTF-8?q?=20needed=20for=20top-level=20function=20JIT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removing injection broke GLOBAL_GET for all JIT-compiled functions, not just mutable closures. Top-level functions like render-to-html need their referenced bindings in the VM globals table. Restore the original injection (only injects values not already in globals). Mutable closure vars (parser's pos etc.) still get stale snapshots and fall back to CEK — that's the known limitation to fix with cell-based boxing later. 1166 passed, 0 failed. Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/lib/sx_vm.ml | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/hosts/ocaml/lib/sx_vm.ml b/hosts/ocaml/lib/sx_vm.ml index c9b32647..ef0344af 100644 --- a/hosts/ocaml/lib/sx_vm.ml +++ b/hosts/ocaml/lib/sx_vm.ml @@ -582,10 +582,30 @@ let jit_compile_lambda (l : lambda) globals = | _ -> (* Compiler loaded from source — call through CEK *) Sx_ref.eval_expr (List [compile_fn; quoted]) (Env (make_env ())) in - (* Don't inject closure bindings into globals — GLOBAL_GET falls through - to vm_closure_env which has LIVE bindings. Injecting creates stale - snapshots that break mutable closure variables (set! on pos, etc.). *) - let effective_globals = globals in + (* Inject closure bindings into globals so GLOBAL_GET can find them. + Only injects values not already present in globals (preserves + existing defines). Mutable closure vars get stale snapshots here + but GLOBAL_SET writes back to vm_closure_env, and GLOBAL_GET + falls through to vm_closure_env if the global is stale. *) + let effective_globals = + let closure = l.l_closure in + let count = ref 0 in + let rec inject env = + Hashtbl.iter (fun id v -> + let name = Sx_types.unintern id in + if not (Hashtbl.mem globals name) then begin + Hashtbl.replace globals name v; + incr count + end + ) env.bindings; + match env.parent with Some p -> inject p | None -> () + in + if Hashtbl.length closure.bindings > 0 || closure.parent <> None then + inject closure; + if !count > 0 then + Printf.eprintf "[jit] %s: injected %d closure bindings\n%!" fn_name !count; + globals + in (match result with | Dict d when Hashtbl.mem d "bytecode" -> let outer_code = code_from_value result in