ERR/E4.1: trace.print_interpreter_frames() — Phase E4 complete
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.
This commit is contained in:
@@ -1663,6 +1663,11 @@ pub const LLVMEmitter = struct {
|
||||
// `false`. A `if is_comptime() { … }` branch becomes dead.
|
||||
self.mapRef(c.LLVMConstInt(self.cached_i1, 0, 0));
|
||||
},
|
||||
.interp_print_frames => {
|
||||
// No interpreter stack in compiled code; this only ever sits in
|
||||
// a dead `is_comptime()` branch. Emit nothing.
|
||||
self.advanceRefCounter();
|
||||
},
|
||||
.const_string => |str_id| {
|
||||
const str = self.ir_mod.types.getString(str_id);
|
||||
const llvm_val = self.emitStringConstant(str);
|
||||
|
||||
@@ -91,6 +91,11 @@ pub const Op = union(enum) {
|
||||
/// `false`. Lets stdlib (`process.exit`, `assert`) take a comptime-only
|
||||
/// diagnostic branch that dead-codes out of compiled binaries.
|
||||
is_comptime,
|
||||
/// ERR E4.1 — `trace.print_interpreter_frames()`. At comptime the interp
|
||||
/// walks its sx call-frame chain and appends it to the output; in compiled
|
||||
/// code it's a no-op (only ever reached from a dead `is_comptime()` branch,
|
||||
/// where there is no interpreter stack to walk).
|
||||
interp_print_frames,
|
||||
/// Comptime-only Type value. Carried as a `Value.type_tag(TypeId)`
|
||||
/// in the interpreter. NEVER emitted to LLVM — types are erased
|
||||
/// after lowering. `emit_llvm` bails loudly if it sees one,
|
||||
|
||||
@@ -143,6 +143,10 @@ pub const Interpreter = struct {
|
||||
output: std.ArrayList(u8),
|
||||
call_depth: u32 = 0,
|
||||
max_call_depth: u32 = 256,
|
||||
/// Active sx call-frame chain (oldest→newest), maintained across `call` for
|
||||
/// `trace.print_interpreter_frames()` (ERR E4.1). Only sx-bodied frames are
|
||||
/// tracked — foreign calls return before the frame is pushed.
|
||||
call_chain: std.ArrayList(FuncId) = .empty,
|
||||
|
||||
// Heap: dynamically allocated memory blocks
|
||||
heap: std.ArrayList([]u8),
|
||||
@@ -206,6 +210,7 @@ pub const Interpreter = struct {
|
||||
}
|
||||
self.heap.deinit(self.alloc);
|
||||
self.output.deinit(self.alloc);
|
||||
self.call_chain.deinit(self.alloc);
|
||||
self.global_values.deinit();
|
||||
self.hooks.deinit();
|
||||
}
|
||||
@@ -424,6 +429,24 @@ pub const Interpreter = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Append the current sx call-frame chain to the interp output, most-recent
|
||||
/// last (ERR E4.1). The topmost frame is `print_interpreter_frames` itself
|
||||
/// (the dump site), so it's skipped. Frame source locations await IR-offset
|
||||
/// resolution (the comptime analog of DWARF), so only function names print.
|
||||
fn printInterpFrames(self: *Interpreter) void {
|
||||
const n = self.call_chain.items.len;
|
||||
if (n <= 1) return;
|
||||
self.output.appendSlice(self.alloc, "comptime call frames (most recent call last):\n") catch {};
|
||||
var i: usize = 0;
|
||||
while (i < n - 1) : (i += 1) {
|
||||
const fid = self.call_chain.items[i];
|
||||
const fname = self.module.types.getString(self.module.getFunction(fid).name);
|
||||
const line = std.fmt.allocPrint(self.alloc, " at {s}\n", .{fname}) catch continue;
|
||||
defer self.alloc.free(line);
|
||||
self.output.appendSlice(self.alloc, line) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
fn callForeign(self: *Interpreter, func: *const inst_mod.Function, args: []const Value) InterpError!Value {
|
||||
const name = self.module.types.getString(func.name);
|
||||
|
||||
@@ -508,6 +531,10 @@ pub const Interpreter = struct {
|
||||
return self.callForeign(func, args);
|
||||
}
|
||||
|
||||
// Track the sx call chain for `trace.print_interpreter_frames()`.
|
||||
self.call_chain.append(self.alloc, func_id) catch {};
|
||||
defer _ = self.call_chain.pop();
|
||||
|
||||
// Compute total refs: params + all instructions across all blocks
|
||||
var total_refs: u32 = @intCast(func.params.len);
|
||||
for (func.blocks.items) |blk| {
|
||||
@@ -609,6 +636,10 @@ pub const Interpreter = struct {
|
||||
.const_null => return .{ .value = .null_val },
|
||||
.const_undef => return .{ .value = .undef },
|
||||
.is_comptime => return .{ .value = .{ .boolean = true } },
|
||||
.interp_print_frames => {
|
||||
self.printInterpFrames();
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.const_type => |tid| return .{ .value = .{ .type_tag = tid } },
|
||||
|
||||
// ── Arithmetic ──────────────────────────────────────
|
||||
|
||||
@@ -10327,6 +10327,11 @@ pub const Lowering = struct {
|
||||
// serves both). Lets stdlib gate a comptime-only diagnostic branch.
|
||||
return self.builder.emit(.{ .is_comptime = {} }, .bool);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "__interp_print_frames")) {
|
||||
// Backs `trace.print_interpreter_frames()`: dumps the interp call
|
||||
// chain at comptime, no-op in compiled code (ERR E4.1).
|
||||
return self.builder.emit(.{ .interp_print_frames = {} }, .void);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "error_tag_name")) {
|
||||
// error_tag_name(e) → look the error-set value's runtime tag id up
|
||||
// in the always-linked tag-name table. The value IS its u32 tag id.
|
||||
@@ -13962,6 +13967,7 @@ pub const Lowering = struct {
|
||||
if (std.mem.eql(u8, bare_name, "field_name")) return .string;
|
||||
if (std.mem.eql(u8, bare_name, "error_tag_name")) return .string;
|
||||
if (std.mem.eql(u8, bare_name, "is_comptime")) return .bool;
|
||||
if (std.mem.eql(u8, bare_name, "__interp_print_frames")) return .void;
|
||||
if (std.mem.eql(u8, bare_name, "is_flags")) return .bool;
|
||||
if (std.mem.eql(u8, bare_name, "type_of")) return .any;
|
||||
if (std.mem.eql(u8, bare_name, "field_value")) return .any;
|
||||
|
||||
@@ -144,6 +144,7 @@ fn printInst(instruction: *const Inst, ref_idx: u32, tt: *const TypeTable, write
|
||||
.const_null => try writer.writeAll("const null : "),
|
||||
.const_undef => try writer.writeAll("const undef : "),
|
||||
.is_comptime => try writer.writeAll("is_comptime : "),
|
||||
.interp_print_frames => try writer.writeAll("interp_print_frames : "),
|
||||
.const_type => |tid| try writer.print("const type({s}) : ", .{tt.typeName(tid)}),
|
||||
|
||||
// ── Arithmetic ──────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user