Fix JIT closure isolation, SX wire format, server diagnostics
Root cause: _env_bind_hook mirrored ALL env_bind calls (including
lambda parameter bindings) to the shared VM globals table. Factory
functions like make-page-fn that return closures capturing different
values for the same param names (default-name, prefix, suffix) would
have the last call's values overwrite all previous closures' captured
state in globals. OP_GLOBAL_GET reads globals first, so all closures
returned the last factory call's values.
Fix: only sync root-env bindings (parent=None) to VM globals. Lambda
parameter bindings stay in their local env, found via vm_closure_env
fallback in OP_GLOBAL_GET.
Also in this commit:
- OP_CLOSURE propagates parent vm_closure_env to child closures
- Remove JIT globals injection (closure vars found via env chain)
- sx_server.ml: SX-Request header → returns text/sx (aser only)
- sx_server.ml: diagnostic endpoint GET /sx/_debug/{env,eval,route}
- sx_server.ml: page helper stubs for deep page rendering
- sx_server.ml: skip client-libs/ dir (browser-only definitions)
- adapter-html.sx: unknown components → HTML comment (not error)
- sx-platform.js: .sxbc fallback loader for bytecode modules
- Delete sx_http.ml (standalone HTTP server, unused)
- Delete stale .sxbc.json files (arity=0 bug, replaced by .sxbc)
- 7 new closure isolation tests in test-closure-isolation.sx
- mcp_tree.ml: emit arity + upvalue-count in .sxbc.json output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -371,7 +371,7 @@ and run vm =
|
||||
frame.closure.vm_upvalues.(index)
|
||||
) in
|
||||
let cl = { vm_code = code; vm_upvalues = upvalues; vm_name = None;
|
||||
vm_env_ref = vm.globals; vm_closure_env = None } in
|
||||
vm_env_ref = vm.globals; vm_closure_env = frame.closure.vm_closure_env } in
|
||||
push vm (VmClosure cl)
|
||||
| 52 (* OP_CALL_PRIM *) ->
|
||||
let idx = read_u16 frame in
|
||||
@@ -578,32 +578,13 @@ let jit_compile_lambda (l : lambda) globals =
|
||||
let fn_expr = List [Symbol "fn"; param_syms; l.l_body] in
|
||||
let quoted = List [Symbol "quote"; fn_expr] in
|
||||
let result = Sx_ref.eval_expr (List [compile_fn; quoted]) (Env (make_env ())) in
|
||||
(* If the lambda has closure-captured variables, merge them into 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
|
||||
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
|
||||
(* Closure vars are accessible via vm_closure_env (set on the VmClosure
|
||||
at line ~617). OP_GLOBAL_GET falls back to vm_closure_env when vars
|
||||
aren't in globals. No injection into the shared globals table —
|
||||
that would break closure isolation for factory functions like
|
||||
make-page-fn where multiple closures capture different values
|
||||
for the same variable names. *)
|
||||
let effective_globals = globals in
|
||||
(match result with
|
||||
| Dict d when Hashtbl.mem d "bytecode" ->
|
||||
let outer_code = code_from_value result in
|
||||
@@ -798,7 +779,7 @@ let trace_run src globals =
|
||||
) in
|
||||
let inner_code = code_from_value code_val2 in
|
||||
let c = { vm_code = inner_code; vm_upvalues = upvalues; vm_name = None;
|
||||
vm_env_ref = vm.globals; vm_closure_env = None } in
|
||||
vm_env_ref = vm.globals; vm_closure_env = frame.closure.vm_closure_env } in
|
||||
push vm (VmClosure c)
|
||||
| 52 -> let idx = read_u16 frame in let argc = read_u8 frame in
|
||||
let name = match frame.closure.vm_code.vc_constants.(idx) with String s -> s | _ -> "" in
|
||||
|
||||
Reference in New Issue
Block a user