JIT: use live globals, don't copy — fixes CSSX Not callable: nil

jit_compile_lambda now uses the live globals table directly instead
of Hashtbl.copy. Closure bindings that aren't already in globals are
injected into the live table. This ensures GLOBAL_GET always sees
the latest define values.

Previously: Hashtbl.copy created a stale snapshot. Functions defined
after the copy (like cssx-process-token from cssx.sx) resolved to nil
in JIT-compiled closures.

JIT error test now passes — 0 CSSX errors during navigation.
7/8 navigation tests pass. Remaining: back button content update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 01:05:48 +00:00
parent b708b210eb
commit f3a437ee87
2 changed files with 178 additions and 116 deletions

View File

@@ -580,23 +580,27 @@ let jit_compile_lambda (l : lambda) globals =
so the VM can find them via GLOBAL_GET. The compiler doesn't know
about the enclosing scope, so closure vars get compiled as globals. *)
let effective_globals =
(* Use the LIVE globals table directly. Inject only truly local
closure bindings (not already in globals) into the live table.
This ensures GLOBAL_GET always sees the latest define values.
Previous approach copied globals, creating a stale snapshot. *)
let closure = l.l_closure in
if Hashtbl.length closure.bindings = 0 && closure.parent = None then
globals (* no closure vars — use globals directly *)
else begin
(* Merge: closure bindings layered on top of globals.
Use a shallow copy so we don't pollute the real globals. *)
let merged = Hashtbl.copy globals in
let rec inject env =
Hashtbl.iter (fun id v -> Hashtbl.replace merged (Sx_types.unintern id) v) env.bindings;
match env.parent with Some p -> inject p | None -> ()
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;
let n = Hashtbl.length merged - Hashtbl.length globals in
if n > 0 then
Printf.eprintf "[jit] %s: injected %d closure bindings\n%!" fn_name n;
merged
end
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" ->

File diff suppressed because one or more lines are too long