Each trace frame now shows the offending source line with a `^` caret
under the column — in the catch-handler formatter, the failable-main C
reporter, and the comptime path.
The source line is embedded at compile time as a 5th Frame field
(line_text), not read from disk at runtime: the file field is a
basename and a runtime read would add a filesystem dependency that
fails under the test harness and on locked-down targets.
- errors.lineAt(src, offset): shared helper for the whole source line.
- Frame gains line_text (mirrored in emit_llvm getFrameStructType,
trace.sx Frame, sx_trace.c SxFrame). emitTraceFrame embeds it; the
interp .trace_resolve extracts it from the source map.
- trace.sx (new spaces helper) and the C reporter render the line +
a col-aligned caret, guarded on a non-empty line_text.
Snapshots 243/244/247/253 regenerated. Gates: zig build, zig build
test, run_examples.sh -> 291 passed.
#run failures now print the same `func at file:line:col` trace as
runtime, resolved in-process via the interpreter's IR/source tables.
- Read-side context-split op `.trace_resolve` (mirror of .trace_frame),
lowered from a name-recognized `__trace_resolve_frame(u64) -> Frame`.
- emit_llvm: inttoptr the operand to *Frame + load (the value
.trace_frame stamped in).
- interp: unpack (func_id << 32 | span.start); resolve func/file from
module.functions and line/col via SourceLoc.compute over a new
source_map (setSourceMap wired at every production interp site).
- trace.sx: frame_at -> u64; to_string routes each frame through
__trace_resolve_frame, so one source works in both machines.
Compiled path behavior unchanged (243/244/247 identical; it now loads
via the op). New examples/253-comptime-trace.sx exercises the comptime
path. Gates: zig build, zig build test, run_examples.sh -> 291 passed.
Return-trace frames now resolve to real `func at file:line:col`
in-process — no DWARF, no symbolizer.
- New niladic, span-stamped `.trace_frame` IR op (mirrors is_comptime):
carries no operands; each backend derives the frame from context.
lower.zig's placeholderTraceFrame emits it; the existing
sx_trace_push call consumes it.
- emit_llvm: resolve the op's span + current function to
{file(basename), line, col, func}, build an interned Frame global
({string,i32,i32,string}, strings cached by content), push its
address (ptrtoint).
- interp: pack (func_id << 32 | span.start) for the comptime resolver
(slice 3b); never a pointer.
- sx_trace.c report_unhandled derefs SxFrame; trace.sx gains the Frame
struct, frame_at -> *Frame, and field-reading to_string. Layout
mirrored in 3 places with cross-ref comments.
Verified JIT + AOT. Snapshots 243/244/247 regenerated (placeholder ->
func at file:line:col). Gates: zig build, zig build test,
run_examples.sh -> 290 passed.
The last E4 item: a comptime call-frame dump.
- New nullary `interp_print_frames` IR op (inst/print). The interpreter
maintains a `call_chain` side-stack (push/pop a FuncId around each sx-bodied
`call`, freed in deinit) and `printInterpFrames` appends the chain to its
output — most-recent-last, with the dump frame itself skipped. emit_llvm
makes the op a no-op: compiled code has no interpreter stack, and the only
caller is `process.exit`'s dead `is_comptime()` branch.
- Lowered from a name-recognized `__interp_print_frames()` builtin
(tryLowerReflectionCall + inferExprType → void).
- `trace.print_interpreter_frames()` wraps the builtin; wired into
`process.exit`'s comptime branch (process.sx now imports trace.sx).
- Frame source locations await IR-offset resolution (the comptime analog of
DWARF), so only function names print today.
examples/252-interp-frames.sx (top-level `#run` drives the dump; exit 0).
Phase E4 (entry-point + stdlib error story) is now 100% complete.
The trace formatter, unblocked now that 0057 is fixed.
- library/modules/trace.sx: to_string() walks the trace buffer (sx_trace_len /
frame_at / truncated) and renders "error return trace ..." with one line per
frame; print_current() writes it to stderr (libc write(2, ...)). Frame
locations are "<location pending DWARF>" until E3.0 resolves PCs; count +
ordering + the overflow note are already meaningful.
- Catch-clear timing fix (lowerCatch): move the absorption clear from
runCatchBody ENTRY to the handler's non-diverging EXIT (both the pure and
value-carrying paths). This reconciles the two PLAN-ERR statements that
conflicted — §clear-points "buffer cleared before the catch body" vs
§catch-over-or "frames still in the buffer when the body runs". Exit-clear
satisfies both: the handler can inspect the trace (trace.print_current()
shows the chain), and the buffer is empty once the handler completes. A
diverging body (raise/return) keeps/discards on its own path.
- examples/243-trace-format.sx: catch handler prints the tag + the 2-frame
trace, then shows the buffer is empty after. examples/241 updated: the
handler now observes len=2 (was 0 under the buggy entry-clear).
Gates: zig build, zig build test, bash tests/run_examples.sh (280 passed; lone
failure is the user's uncommitted 213-canonical-map pack WIP).