From 2ee4d4324a34c81ae6193345fada8c34947f2642 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 27 Mar 2026 01:32:55 +0000 Subject: [PATCH] Document VM debugging tools and island authoring rules in CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tools: vm-trace, bytecode-inspect, deps-check, prim-check, test_boot.sh, sx-build-all.sh — with usage examples. Island rules: (do ...) for multi-expression bodies, nested let for cross-references, (deref (computed ...)) for reactive derived text, effects in inner let for OCaml SSR compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index ff8db7a9..c407c161 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -226,3 +226,70 @@ Dev bind mounts in `docker-compose.dev.yml` must mirror the Docker image's COPY - Use Context7 MCP for up-to-date library documentation - Playwright MCP is available for browser automation/testing + +### VM / Bytecode Debugging Tools + +These are OCaml server commands sent via the epoch protocol (`printf '(epoch N)\n(command args)\n' | sx_server.exe`). They're available in any context where the OCaml kernel is running (dev server, CLI, tests). + +```bash +# Full build pipeline — OCaml + JS browser + JS test + run tests +./scripts/sx-build-all.sh + +# WASM boot test — verify sx_browser.bc.js loads in Node.js without a browser +bash hosts/ocaml/browser/test_boot.sh +``` + +#### `(vm-trace "")` +Step through bytecode execution. Returns a list of trace entries, each with: +- `:opcode` — instruction name (CONST, CALL, JUMP_IF_FALSE, etc.) +- `:stack` — top 5 values on the stack at that point +- `:depth` — frame nesting depth + +Requires the compiler to be loaded (`lib/compiler.sx`). Use this to debug unexpected VM behavior — it shows exactly what the bytecode does step by step. + +```bash +printf '(epoch 1)\n(load "lib/compiler.sx")\n(epoch 2)\n(vm-trace "(+ 1 2)")\n' | sx_server.exe +``` + +#### `(bytecode-inspect "")` +Disassemble a compiled function's bytecode. Returns a dict with: +- `:arity` — number of parameters +- `:num_locals` — stack frame size +- `:constants` — constant pool (strings, numbers, symbols) +- `:bytecode` — list of instructions, each with `:offset`, `:opcode`, `:operands` + +Only works on functions that have been JIT-compiled (have a `vm_closure`). Use this to verify the compiler emits correct bytecode. + +```bash +printf '(epoch 1)\n(bytecode-inspect "my-function")\n' | sx_server.exe +``` + +#### `(deps-check "")` +Strict symbol resolution checker. Parses the source, walks the AST, and checks every symbol reference against: +- Environment bindings (defines, let bindings) +- Primitive functions table +- Special form names (if, when, cond, let, define, etc.) + +Returns `{:resolved (...) :unresolved (...)}`. Run this on `.sx` files before compilation to catch typos and missing imports (e.g., `extract-verb-info` vs `get-verb-info`). + +```bash +printf '(epoch 1)\n(deps-check "(defcomp ~my-comp () (div (frobnicate x)))")\n' | sx_server.exe +# => {:resolved ("defcomp" "div") :unresolved ("frobnicate" "x")} +``` + +#### `(prim-check "")` +Scan compiled bytecode for `CALL_PRIM` instructions and verify each primitive name exists in the runtime. Returns `{:valid (...) :invalid (...)}`. Catches mismatches like `length` vs `len` that would crash at runtime. + +```bash +printf '(epoch 1)\n(prim-check "my-compiled-fn")\n' | sx_server.exe +# => {:valid ("+" "len" "first") :invalid ("length")} +``` + +### SX Island Authoring Rules + +Key patterns discovered from the reactive runtime demos (see `sx/sx/reactive-runtime.sx`): + +1. **Multi-expression bodies need `(do ...)`** — `fn`, `let`, and `when` bodies evaluate only the last expression. Wrap multiples in `(do expr1 expr2 expr3)`. +2. **`let` is parallel, not sequential** — bindings in the same `let` can't reference each other. Use nested `let` blocks when functions need to reference signals defined earlier. +3. **Reactive text needs `(deref (computed ...))`** — bare `(len (deref items))` is NOT reactive. Wrap in `(deref (computed (fn () (len (deref items)))))`. +4. **Effects go in inner `let`** — signals in outer `let`, functions and effects in inner `let`. The OCaml SSR evaluator can't resolve outer `let` bindings from same-`let` lambdas.