docs(debugger): align trace-push mechanism to one ground-truth model (A9.2)
The .trace_frame op is niladic: it carries no operand and no GlobalId. The compiled backend yields the interned Frame global's address as the op's value (reflection.emitTraceFrame); the interpreter yields a packed (func_id, ir_offset) as the op's value and never calls sx_trace_push (recovered later by .trace_resolve). The sx_trace_push call is a separate call op emitted by lower.zig at each push site, consuming the op's value. Reword every passage that stated the old/wrong model: the niladic invariant is about the op (not the push site emitting only one instruction); reflection yields the op's value rather than lowering a push; the interp returns the packed value rather than calling the foreign sx_trace_push via host_ffi dlsym.
This commit is contained in:
@@ -100,10 +100,11 @@ never collide.
|
||||
|
||||
### The niladic trace-push op
|
||||
|
||||
Because the same IR runs in both machines, the push is a **dedicated,
|
||||
niladic, span-stamped IR op** — the same pattern as `is_comptime` /
|
||||
`interp_print_frames`. It carries **no operands and no global reference**;
|
||||
each backend derives the frame from its own context:
|
||||
Because the same IR runs in both machines, the frame value comes from a
|
||||
**dedicated, niladic, span-stamped IR op** (`.trace_frame`) — the same
|
||||
pattern as `is_comptime` / `interp_print_frames`. It carries **no operands
|
||||
and no global reference**; each backend derives the frame from its own
|
||||
context:
|
||||
|
||||
- **`emit_llvm` (the `.trace_frame` arm):** resolves the op's `span` +
|
||||
current function → `{file, line, col, func}` (reusing the source map
|
||||
@@ -112,14 +113,16 @@ each backend derives the frame from its own 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`:** pushes the packed `(func_id, ir_offset)` from its own
|
||||
execution context.
|
||||
- **`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.
|
||||
|
||||
This keeps the lowerer thin: at each push site it emits the op and nothing
|
||||
else — no operand wiring, no global construction. The rejected
|
||||
alternative — an op carrying a `GlobalId` to an IR-level `Frame` global —
|
||||
would make the global visible to the interpreter (forcing comptime onto
|
||||
the pointer-deref path) and fatten the lowerer; **do not do this.**
|
||||
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
|
||||
rejected alternative — an op carrying a `GlobalId` to an IR-level `Frame`
|
||||
global — would make the global visible to the interpreter (forcing
|
||||
comptime onto the pointer-deref path) and fatten the lowerer; **do not do
|
||||
this.**
|
||||
|
||||
`Frame` is defined **once** in sx (`trace.sx`/std); the reflection builder
|
||||
(`src/backend/llvm/reflection.zig`) builds the interned global off that
|
||||
@@ -242,12 +245,12 @@ both the trace path and the DWARF path. Items marked ✅ exist today;
|
||||
|---|---|
|
||||
| [`src/core.zig`](../src/core.zig) | `Compilation`: owns `import_sources` (file→source map), constructs the emitter, calls `setDebugContext` + `emit`; re-enters the interpreter for `#run`/post-link |
|
||||
| [`src/ir/lower.zig`](../src/ir/lower.zig) | AST→IR. Stamps `Inst.span`; emits push/clear at failure/absorb sites; `tracesEnabled` gate; declares the `sx_trace_*` externs |
|
||||
| [`src/ir/emit_llvm.zig`](../src/ir/emit_llvm.zig) | IR→LLVM orchestrator. Owns `LLVMEmitter` + the source map (`setDebugContext`); dispatches the push 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; lowers the push op to a pointer push |
|
||||
| [`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 push op to a packed `(func_id, offset)`; resolves comptime frames |
|
||||
| [`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/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-push op) |
|
||||
| [`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` |
|
||||
| [`library/modules/trace.sx`](../library/modules/trace.sx) | the formatter (`to_string` / `print_current`) |
|
||||
| [`src/llvm_api.zig`](../src/llvm_api.zig) | binds `llvm-c/Core.h` + `llvm-c/DebugInfo.h` |
|
||||
@@ -288,8 +291,10 @@ 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 call the foreign `sx_trace_push` (resolved
|
||||
via `host_ffi` `dlsym` against the linked `sx_trace.c`).
|
||||
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
|
||||
`file:line:col` via the IR/source tables.
|
||||
|
||||
**Buffer (run time) ✅** — `sx_trace.c` stores the `u64`s. Linked into the
|
||||
compiler so the JIT resolves `sx_trace_*` via `dlsym`; auto-injected as a
|
||||
@@ -297,7 +302,7 @@ compiler so the JIT resolves `sx_trace_*` via `dlsym`; auto-injected as a
|
||||
|
||||
**Formatter (run time) ✅ (compiled 3a, comptime 3b)** — `trace.sx` `to_string()` loops
|
||||
`sx_trace_len()` / `sx_trace_frame_at(i)` and resolves each `u64` through
|
||||
a **read-side context-split primitive** (the mirror of the push op):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user