ERR/E3.0 (slice 3b): comptime trace resolution

#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.
This commit is contained in:
agra
2026-06-01 15:33:50 +03:00
parent 11f6377d9c
commit b5241243e6
11 changed files with 120 additions and 5 deletions

View File

@@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
const types = @import("types.zig");
const inst_mod = @import("inst.zig");
const mod_mod = @import("module.zig");
const errors = @import("../errors.zig");
const TypeId = types.TypeId;
const TypeTable = types.TypeTable;
@@ -148,6 +149,12 @@ pub const Interpreter = struct {
/// tracked — foreign calls return before the frame is pushed.
call_chain: std.ArrayList(FuncId) = .empty,
/// File → source text (the diagnostics' import_sources). Set by the host
/// where available so `.trace_resolve` can turn a `(func_id, span.start)`
/// frame into `file:line:col` at comptime (ERR E3.0 slice 3b). Null → the
/// resolver degrades to line/col 1:1.
source_map: ?*const std.StringHashMap([:0]const u8) = null,
// Heap: dynamically allocated memory blocks
heap: std.ArrayList([]u8),
@@ -203,6 +210,12 @@ pub const Interpreter = struct {
};
}
/// Provide the file→source map so `.trace_resolve` can compute file:line:col
/// for comptime trace frames. Optional — absent in unit tests.
pub fn setSourceMap(self: *Interpreter, sm: *const std.StringHashMap([:0]const u8)) void {
self.source_map = sm;
}
pub fn deinit(self: *Interpreter) void {
// Free all heap allocations
for (self.heap.items) |block| {
@@ -652,6 +665,32 @@ pub const Interpreter = struct {
const packed_frame: u64 = (fid << 32) | @as(u64, instruction.span.start);
return .{ .value = .{ .int = @bitCast(packed_frame) } };
},
.trace_resolve => |u| {
// Unpack the comptime frame `(func_id << 32 | span.start)` and
// resolve it to a `Frame { file, line, col, func }` aggregate.
const raw: u64 = @bitCast(frame.getRef(u.operand).asInt() orelse 0);
const fid: u32 = @intCast(raw >> 32);
const offset: u32 = @truncate(raw);
const func = self.module.getFunction(FuncId.fromIndex(fid));
const func_name = self.module.types.getString(func.name);
const file_full = func.source_file orelse "";
const file = std.fs.path.basename(file_full);
var line: i64 = 1;
var col: i64 = 1;
if (self.source_map) |sm| {
if (sm.get(file_full)) |src| {
const loc = errors.SourceLoc.compute(src, offset);
line = @intCast(loc.line);
col = @intCast(loc.col);
}
}
const fields = self.alloc.alloc(Value, 4) catch return .{ .value = .undef };
fields[0] = .{ .string = file };
fields[1] = .{ .int = line };
fields[2] = .{ .int = col };
fields[3] = .{ .string = func_name };
return .{ .value = .{ .aggregate = fields } };
},
.const_type => |tid| return .{ .value = .{ .type_tag = tid } },
// ── Arithmetic ──────────────────────────────────────