diff --git a/docs/debugger.md b/docs/debugger.md index 8a4e624..85aa934 100644 --- a/docs/debugger.md +++ b/docs/debugger.md @@ -40,7 +40,7 @@ to satisfy all three. "JIT" and "comptime" are **not** the same thing. |---|---|---| | **AOT** (`sx build`) | native machine code in an on-disk binary | pointer to an interned `Frame` | | **JIT** (`sx run`) | ORC-JIT'd machine code in anonymous memory | pointer to an interned `Frame` | -| **Comptime** (`#run`) | the IR interpreter (`interp.zig`) — no machine code | packed `(func_id, ir_offset)` | +| **Comptime** (`#run`) | the IR interpreter (`interp.zig`) — no machine code | packed `(func_id, span.start)` | The crucial constraint: **the same lowered IR runs in the compiled backend *and* the interpreter.** So a value the IR produces (like a trace @@ -92,11 +92,12 @@ so the location — *and the offending source line itself* (`line_text`, for the formatter reads it directly. No PC capture, no DWARF, no symbolizer, no runtime file read. -A comptime frame is instead a packed `(func_id: u32, ir_offset: u32)`, -resolved through the interpreter's in-memory IR/source tables. The -interpreter **never dereferences the compiled `Frame` pointer** — it uses -its own representation — so the compiled and interpreted memory models -never collide. +A comptime frame is instead a packed `(func_id: u32, span.start: u32)` — +where `span.start` is the op's source byte offset — resolved through the +interpreter's in-memory IR/source tables. The interpreter **never +dereferences the compiled `Frame` pointer** — it uses its own +representation — so the compiled and interpreted memory models never +collide. ### The niladic trace-push op @@ -113,9 +114,12 @@ context: (the same mechanism, in the same file, as the tag-name table), and yields its address as the op's value. The lowerer feeds that value to a separate `sx_trace_push` call emitted through the normal call lowering. -- **`interp`:** yields the packed `(func_id, ir_offset)` from its own - execution context as the op's value; no `sx_trace_push` call runs — the - packed value is recovered later by the comptime `.trace_resolve` resolver. +- **`interp`:** yields the packed `(func_id, span.start)` from its own + execution context as the op's value. The separate `sx_trace_push` call + op consuming it is executed by the interp as a foreign call (via + `host_ffi`/dlsym, the same path as any extern), storing the packed value + in the buffer; the comptime `.trace_resolve` resolver later recovers + `file:line:col` from it. The op stays niladic by design: it carries no operand and no `GlobalId`, so no IR-level `Frame` global is ever visible to the interpreter. The @@ -253,7 +257,7 @@ both the trace path and the DWARF path. Items marked ✅ exist today; | [`src/ir/emit_llvm.zig`](../src/ir/emit_llvm.zig) | IR→LLVM orchestrator. Owns `LLVMEmitter` + the source map (`setDebugContext`); dispatches the `.trace_frame` op and the DWARF passes to the helpers below | | [`src/backend/llvm/reflection.zig`](../src/backend/llvm/reflection.zig) | `Reflection`: builds the interned `Frame` table + the tag-name / type-name tables; yields the `.trace_frame` op's value (the `Frame` global's address) — the `sx_trace_push` call itself is emitted by `lower.zig` | | [`src/backend/llvm/debug.zig`](../src/backend/llvm/debug.zig) | `DebugInfo`: builds all DWARF metadata (compile unit, per-function subprograms, per-instruction `DILocation`) | -| [`src/ir/interp.zig`](../src/ir/interp.zig) | Comptime IR interpreter. Lowers the `.trace_frame` op to a packed `(func_id, offset)`; resolves comptime frames | +| [`src/ir/interp.zig`](../src/ir/interp.zig) | Comptime IR interpreter. The `.trace_frame` op yields a packed `(func_id, span.start)`; the separate `sx_trace_push` call op runs as a foreign call (dlsym); `.trace_resolve` recovers comptime frames | | [`src/errors.zig`](../src/errors.zig) | `SourceLoc.compute(source, offset) → {line, col}`; the `import_sources` map type | | [`src/ir/inst.zig`](../src/ir/inst.zig) | `Inst.span`, `Function.source_file`, the `Op` union (home of the `.trace_frame` op) | | [`library/vendors/sx_trace_runtime/sx_trace.c`](../library/vendors/sx_trace_runtime/sx_trace.c) | the thread-local ring buffer + `sx_trace_report_unhandled` | @@ -296,9 +300,11 @@ traces and DWARF can never disagree: `sx_trace_push` call (step 1) consumes. The `sx_trace_push` extern is declared lazily by `getTraceFids()` (which sets `needs_trace_runtime`). 3. **Interpreter** (`interp.zig`, same op): pack `(current_func_id, - ir_offset)` into a `u64` and return it as the op's value. The interp - never calls `sx_trace_push`; the packed value is recovered later by the - comptime `.trace_resolve` resolver, which turns it back into + span.start)` into a `u64` and return it as the op's value. The separate + `sx_trace_push` call op is then executed by the interp as a foreign call + (`callForeign` → `host_ffi.lookupSymbol`/dlsym, the same path as any + extern), storing the packed value in the buffer. The comptime + `.trace_resolve` resolver later turns each packed value back into `file:line:col` via the IR/source tables. **Buffer (run time) ✅** — `sx_trace.c` stores the `u64`s. Linked into the @@ -310,7 +316,7 @@ compiler so the JIT resolves `sx_trace_*` via `dlsym`; auto-injected as a a **read-side context-split primitive** (the mirror of the `.trace_frame` op): - compiled: cast the `u64` → `*Frame`, load the fields. -- comptime: unpack `(func_id, offset)`, resolve via the interpreter's +- comptime: unpack `(func_id, span.start)`, resolve via the interpreter's IR/source tables → a `Frame`. The same `trace.sx` source works in both because it runs in the matching @@ -474,7 +480,7 @@ a Mach-O debug map, never register JIT DWARF. | IR instructions carry source spans | ✅ done — E3.0 slice 1 (`b44a5d0`) | | DWARF emission (compile unit / subprogram / line table) | ✅ done — E3.0 slice 2 (`c32d694`) | | Niladic trace-push op + interned `Frame` table (runtime) | ✅ done — E3.3 slice 3a (`1b6cbc1`) | -| Comptime resolver (`func_id, ir_offset` → location) | ✅ done — slice 3b | +| Comptime resolver (`func_id, span.start` → location) | ✅ done — slice 3b | | Source snippet + `^` caret | ✅ done — slice 3c (line embedded in `Frame`) | | `--emit-obj` artifact plumbing | ✅ done — slice 3d | | Stepping verification: macOS lldb | ✅ done — 3e rung 1 (`tests/debug_stepping_smoke.sh`) |