# Nanovm Opcode Debugging

> Diagnose NanoVM opcode, stack, value, and FFI failures with the optional NANO_VM_TRACE instrumentation.

- Skill: `jordanhubbard/nanovm-opcode-debugging` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jordanhubbard/nanovm-opcode-debugging`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jordanhubbard/nanovm-opcode-debugging/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: jordanhubbard (https://skillmd.com/u/jordanhubbard)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jordanhubbard/nanovm-opcode-debugging

---


# NanoVM Opcode Debugging

I keep opcode tracing disabled by default. Enable it only while diagnosing a
specific VM failure; tracing writes one record per retired instruction and can
produce substantial output.

## Enable tracing

Build the normal VM, then set the environment variable before starting it:

```bash
NANO_VM_TRACE=1 ./bin/nano_vm failing.nvm
```

The VM reads `NANO_VM_TRACE` once during `vm_init`. The instruction dispatch
path checks only the cached boolean. Any non-empty value other than `0` enables
tracing. `NANO_VM_TRACE=0` disables it.

To inspect a NanoLang source program through NanoVM:

```bash
NANO_VM_TRACE=1 ./bin/nano_virt program.nano --emit-nvm -o /tmp/program.nvm
NANO_VM_TRACE=1 ./bin/nano_vm /tmp/program.nvm
```

## Read the trace

Each record includes the function index, function name, bytecode offset,
opcode, stack depth before and after execution, and the top stack values. Heap
values include their tag, address, length where applicable, and printable
contents. FFI records include the import, C symbol, argument count, return tag,
and marshaled result.

Use the first divergent record, not the last error message, as the starting
point. For string failures compare the `PUSH_STR`, FFI return, and equality
records. For stack failures compare the stack depth around the first operation
whose declared effect differs from the observed effect.

Tracing is diagnostic evidence, not a correctness proof. Reproduce the failure
with tracing disabled after the fix and run the relevant NanoVM, NanoVirt, and
benchmark quality gates.

## Generated C diagnostics

Generated-C timing instrumentation is controlled once at process startup by
`NANO_PROFILE`. When an executable was built with `--profile`, use
`NANO_PROFILE=1` to collect timing data or `NANO_PROFILE=0` to turn the hooks
off without rebuilding. The generated hook checks only its cached boolean on
the hot path.

The generated-C toggle is separate from `NANO_VM_TRACE`. `NANO_PROFILE` only
controls timing and flamegraph data emitted by a `--profile` build; it does not
turn on opcode tracing in NanoVM. Run both variables when a failure crosses the
compiler and VM boundary.

