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:
@@ -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. *)
|
||||
|
||||
Reference in New Issue
Block a user