Fix WASM browser click handlers: 8 bugs, 50 new VM tests
The sx-get links were doing full page refreshes because click handlers never attached. Root causes: VM frame management bug, missing primitives, CEK/VM type dispatch mismatch, and silent error swallowing. Fixes: - VM frame exhaustion: frames <- [] now properly pops to rest_frames - length primitive: add alias for len in OCaml primitives - call_sx_fn: use sx_call directly instead of eval_expr (CEK checks for type "lambda" but VmClosure reports "function") - Boot error surfacing: Sx.init() now has try/catch + failure summary - Callback error surfacing: catch-all handler for non-Eval_error exceptions - Silent JIT failures: log before CEK fallback instead of swallowing - vm→env sync: loadModule now calls sync_vm_to_env() - sx_build_bytecode MCP tool added for bytecode compilation Tests: 50 new tests across test-vm.sx and test-vm-primitives.sx covering nested VM calls, frame integrity, CEK bridge, primitive availability, cross-module symbol resolution, and callback dispatch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -362,6 +362,7 @@ let () =
|
||||
| [Macro _] | [Thunk _] | [Keyword _] | [Symbol _] -> Number 0.0
|
||||
| _ -> raise (Eval_error (Printf.sprintf "len: %d args"
|
||||
(List.length args))));
|
||||
register "length" (Hashtbl.find primitives "len");
|
||||
register "first" (fun args ->
|
||||
match args with
|
||||
| [List (x :: _)] | [ListRef { contents = x :: _ }] -> x
|
||||
|
||||
@@ -148,7 +148,11 @@ and vm_call vm f args =
|
||||
(* Cached bytecode — run on VM using the closure's captured env,
|
||||
not the caller's globals. Closure vars were merged at compile time. *)
|
||||
(try push vm (call_closure cl args cl.vm_env_ref)
|
||||
with _ -> push vm (Sx_ref.cek_call f (List args)))
|
||||
with e ->
|
||||
let msg = match e with Eval_error m -> m | e -> Printexc.to_string e in
|
||||
Printf.eprintf "[vm] JIT call failed for %s: %s — falling back to CEK\n%!"
|
||||
(match l.l_name with Some n -> n | None -> "<anon>") msg;
|
||||
push vm (Sx_ref.cek_call f (List args)))
|
||||
| Some _ ->
|
||||
(* Compile failed — CEK *)
|
||||
push vm (Sx_ref.cek_call f (List args))
|
||||
@@ -161,7 +165,10 @@ and vm_call vm f args =
|
||||
| Some cl ->
|
||||
l.l_compiled <- Some cl;
|
||||
(try push vm (call_closure cl args cl.vm_env_ref)
|
||||
with _ ->
|
||||
with e ->
|
||||
let msg = match e with Eval_error m -> m | e -> Printexc.to_string e in
|
||||
Printf.eprintf "[vm] JIT first-call failed for %s: %s — marking failed, falling back to CEK\n%!"
|
||||
(match l.l_name with Some n -> n | None -> "<anon>") msg;
|
||||
l.l_compiled <- Some jit_failed_sentinel;
|
||||
push vm (Sx_ref.cek_call f (List args)))
|
||||
| None ->
|
||||
@@ -187,8 +194,17 @@ and run vm =
|
||||
| frame :: rest_frames ->
|
||||
let bc = frame.closure.vm_code.vc_bytecode in
|
||||
let consts = frame.closure.vm_code.vc_constants in
|
||||
if frame.ip >= Array.length bc then
|
||||
vm.frames <- [] (* bytecode exhausted — stop *)
|
||||
if frame.ip >= Array.length bc then begin
|
||||
(* Bytecode exhausted without explicit RETURN — pop frame like RETURN *)
|
||||
let fn_name = match frame.closure.vm_name with Some n -> n | None -> "?" in
|
||||
Printf.eprintf "[vm] WARN: bytecode exhausted without RETURN in %s (base=%d sp=%d frames=%d)\n%!"
|
||||
fn_name frame.base vm.sp (List.length rest_frames);
|
||||
let result = if vm.sp > frame.base then pop vm else Nil in
|
||||
vm.frames <- rest_frames;
|
||||
vm.sp <- frame.base;
|
||||
if rest_frames <> [] then push vm result
|
||||
(* If no more frames, result stays on stack for call_closure to pop *)
|
||||
end
|
||||
else begin
|
||||
let saved_ip = frame.ip in
|
||||
let op = bc.(frame.ip) in
|
||||
@@ -612,4 +628,5 @@ let jit_compile_lambda (l : lambda) globals =
|
||||
|
||||
(* Wire up forward references *)
|
||||
let () = jit_compile_ref := jit_compile_lambda
|
||||
let () = _vm_call_closure_ref := (fun cl args -> call_closure cl args cl.vm_env_ref)
|
||||
let () = _vm_call_closure_ref := (fun cl args ->
|
||||
call_closure cl args cl.vm_env_ref)
|
||||
|
||||
Reference in New Issue
Block a user