Add 6 VM/bytecode debugging and build tools

OCaml server commands:
- vm-trace: step-by-step bytecode execution trace (opcode, stack, depth)
- bytecode-inspect: disassemble compiled function (opcodes, constants, arity)
- deps-check: strict symbol resolution (resolved vs unresolved symbols)
- prim-check: verify CALL_PRIM opcodes match real primitives

Scripts:
- hosts/ocaml/browser/test_boot.sh: WASM boot test in Node.js
- scripts/sx-build-all.sh: full pipeline (OCaml + JS + tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 01:23:55 +00:00
parent c0c6787189
commit dab81fc571
4 changed files with 459 additions and 0 deletions

View File

@@ -1078,6 +1078,122 @@ let rec dispatch env cmd =
| Eval_error msg -> send_error msg
| exn -> send_error (Printexc.to_string exn))
(* ---- Debugging / introspection commands ---- *)
| List [Symbol "vm-trace"; String src] ->
(* Compile and trace-execute an SX expression, returning step-by-step
trace entries with opcode names, stack snapshots, and frame depth. *)
(try
let result = Sx_vm.trace_run src env.bindings in
send_ok_value result
with
| Eval_error msg -> send_error msg
| exn -> send_error (Printexc.to_string exn))
| List [Symbol "bytecode-inspect"; String name] ->
(* Disassemble a named function's compiled bytecode.
Returns a dict with arity, num_locals, constants, bytecode instructions. *)
(try
let v = try Hashtbl.find env.bindings name
with Not_found -> raise (Eval_error ("bytecode-inspect: not found: " ^ name)) in
let code = match v with
| Lambda l ->
(match l.l_compiled with
| Some cl when not (Sx_vm.is_jit_failed cl) -> cl.vm_code
| _ -> raise (Eval_error ("bytecode-inspect: " ^ name ^ " has no compiled bytecode")))
| VmClosure cl -> cl.vm_code
| NativeFn _ -> raise (Eval_error ("bytecode-inspect: " ^ name ^ " is a native function"))
| _ -> raise (Eval_error ("bytecode-inspect: " ^ name ^ " is not a function"))
in
send_ok_value (Sx_vm.disassemble code)
with
| Eval_error msg -> send_error msg
| exn -> send_error (Printexc.to_string exn))
| List [Symbol "deps-check"; String src] ->
(* Walk parsed AST to find all symbol references and check resolution. *)
(try
let exprs = Sx_parser.parse_all src in
let special_forms = [
"if"; "when"; "cond"; "case"; "let"; "let*"; "lambda"; "fn";
"define"; "defcomp"; "defisland"; "defmacro";
"quote"; "quasiquote"; "begin"; "do"; "set!"; "->"; "and"; "or"
] in
let seen = Hashtbl.create 64 in
let rec walk = function
| Symbol s ->
if not (Hashtbl.mem seen s) then Hashtbl.replace seen s true
| List items | ListRef { contents = items } ->
List.iter walk items
| Dict d -> Hashtbl.iter (fun _ v -> walk v) d
| _ -> ()
in
List.iter walk exprs;
let resolved = ref [] in
let unresolved = ref [] in
Hashtbl.iter (fun name _ ->
if List.mem name special_forms
|| Hashtbl.mem env.bindings name
|| Hashtbl.mem Sx_primitives.primitives name
|| name = "true" || name = "false" || name = "nil"
then resolved := String name :: !resolved
else unresolved := String name :: !unresolved
) seen;
let result = Hashtbl.create 2 in
Hashtbl.replace result "resolved" (List !resolved);
Hashtbl.replace result "unresolved" (List !unresolved);
send_ok_value (Dict result)
with
| Eval_error msg -> send_error msg
| exn -> send_error (Printexc.to_string exn))
| List [Symbol "prim-check"; String name] ->
(* Scan a compiled function's bytecode for CALL_PRIM opcodes
and verify each referenced primitive exists. *)
(try
let v = try Hashtbl.find env.bindings name
with Not_found -> raise (Eval_error ("prim-check: not found: " ^ name)) in
let code = match v with
| Lambda l ->
(match l.l_compiled with
| Some cl when not (Sx_vm.is_jit_failed cl) -> cl.vm_code
| _ -> raise (Eval_error ("prim-check: " ^ name ^ " has no compiled bytecode")))
| VmClosure cl -> cl.vm_code
| _ -> raise (Eval_error ("prim-check: " ^ name ^ " is not a compiled function"))
in
let bc = code.vc_bytecode in
let consts = code.vc_constants in
let len = Array.length bc in
let valid = ref [] in
let invalid = ref [] in
let ip = ref 0 in
while !ip < len do
let op = bc.(!ip) in
ip := !ip + 1;
if op = 52 (* OP_CALL_PRIM *) && !ip + 2 < len then begin
let lo = bc.(!ip) in let hi = bc.(!ip + 1) in
let idx = lo lor (hi lsl 8) in
let _argc = bc.(!ip + 2) in
ip := !ip + 3;
let prim_name = if idx < Array.length consts
then (match consts.(idx) with String s -> s | _ -> "?") else "?" in
if Hashtbl.mem Sx_primitives.primitives prim_name
then valid := String prim_name :: !valid
else invalid := String prim_name :: !invalid
end else begin
(* Skip operand bytes for other opcodes *)
let skip = Sx_vm.opcode_operand_size op in
ip := !ip + skip
end
done;
let result = Hashtbl.create 2 in
Hashtbl.replace result "valid" (List !valid);
Hashtbl.replace result "invalid" (List !invalid);
send_ok_value (Dict result)
with
| Eval_error msg -> send_error msg
| exn -> send_error (Printexc.to_string exn))
| List [Symbol "reset"] ->
(* Clear all bindings and rebuild env.
We can't reassign env, so clear and re-populate. *)