fix some rough edges around stack traces

hide irrelevant CallFrames
fix span info for the implicit stack frame
This commit is contained in:
りき萌 2025-06-25 21:10:54 +02:00
parent e49885c83a
commit 7a52dbebd0
4 changed files with 34 additions and 11 deletions

View file

@ -196,27 +196,32 @@ export fn haku2_vm_stackframe_count(vm: *const Vm) usize {
return vm.stackFrameCount(); return vm.stackFrameCount();
} }
export fn haku2_vm_stackframe_is_return_marker(vm: *const Vm, index: usize) bool {
const stack_frame = vm.stackFrame(index);
return stack_frame.closure == null;
}
export fn haku2_vm_stackframe_is_system(vm: *const Vm, index: usize) bool { export fn haku2_vm_stackframe_is_system(vm: *const Vm, index: usize) bool {
const stack_frame = vm.stackFrame(index); const stack_frame = vm.stackFrame(index);
return stack_frame.closure.impl == .system; return stack_frame.closure.?.impl == .system;
} }
export fn haku2_vm_stackframe_pc(vm: *const Vm, index: usize) i32 { export fn haku2_vm_stackframe_pc(vm: *const Vm, index: usize) i32 {
const stack_frame = vm.stackFrame(index); const stack_frame = vm.stackFrame(index);
if (stack_frame.closure.impl == .bytecode) if (stack_frame.closure.?.impl == .bytecode)
if (stack_frame.ip) |ip| if (stack_frame.ip) |ip|
return stack_frame.closure.programCounter(ip); return stack_frame.closure.?.programCounter(ip);
return -1; // no return address return -1; // no return address
} }
export fn haku2_vm_stackframe_function_name_len(vm: *const Vm, index: usize) usize { export fn haku2_vm_stackframe_function_name_len(vm: *const Vm, index: usize) usize {
const stack_frame = vm.stackFrame(index); const stack_frame = vm.stackFrame(index);
return stack_frame.closure.name.len; return stack_frame.closure.?.name.len;
} }
export fn haku2_vm_stackframe_function_name(vm: *const Vm, index: usize) [*]const u8 { export fn haku2_vm_stackframe_function_name(vm: *const Vm, index: usize) [*]const u8 {
const stack_frame = vm.stackFrame(index); const stack_frame = vm.stackFrame(index);
return stack_frame.closure.name.ptr; return stack_frame.closure.?.name.ptr;
} }
// Renderer // Renderer

View file

@ -30,7 +30,7 @@ pub const Limits = struct {
}; };
pub const CallFrame = struct { pub const CallFrame = struct {
closure: *const value.Closure, closure: ?*const value.Closure,
return_addr: ?[*]const u8, return_addr: ?[*]const u8,
bottom: u32, bottom: u32,
}; };
@ -100,7 +100,7 @@ pub fn outOfMemory(vm: *Vm) Error {
// NOTE: Different from CallFrame, this is a helper type for obtaining stack traces. // NOTE: Different from CallFrame, this is a helper type for obtaining stack traces.
pub const StackFrame = struct { pub const StackFrame = struct {
closure: *const value.Closure, closure: ?*const value.Closure,
ip: ?[*]const u8, ip: ?[*]const u8,
}; };
@ -117,7 +117,17 @@ pub fn stackFrame(vm: *const Vm, index: usize) StackFrame {
if (index == 0) { if (index == 0) {
return .{ return .{
.closure = closure, .closure = closure,
.ip = vm.ip, // Span info hairiness:
// Because call frames place the instruction pointer past the call instruction,
// the frontend subtracts 1 from it to get a position within the chunk that
// has the correct line info.
// This subtracting by one doesn't work for the implicit stack frame though, because
// its ip gets updated at the _start_ of the instruction---meaning that if you
// subtract one from it, it'll point to the _previous_ instruction, while the actual
// error occurred in the current one.
// To alleviate this, add one here to point to either the middle or the end of the
// current instruction.
.ip = if (vm.ip) |ip| ip + 1 else null,
}; };
} else { } else {
// NOTE: Minus two, because remember index == 0 is occupied by the current stack frame. // NOTE: Minus two, because remember index == 0 is occupied by the current stack frame.
@ -269,7 +279,7 @@ pub fn run(
} }
try vm.pushCall(.{ try vm.pushCall(.{
.closure = closure, .closure = null,
.return_addr = null, // nowhere to return to .return_addr = null, // nowhere to return to
.bottom = bottom, .bottom = bottom,
}); });
@ -591,7 +601,7 @@ pub fn run(
try vm.push(result); try vm.push(result);
if (call_frame.return_addr) |addr| { if (call_frame.return_addr) |addr| {
closure = call_frame.closure; closure = call_frame.closure.?;
ip = addr; ip = addr;
bottom = call_frame.bottom; bottom = call_frame.bottom;

View file

@ -280,7 +280,9 @@ class ErrorException extends HTMLElement {
} }
}; };
for (let i = 0; i < this.result.stackTrace.length; ++i) { // Iterate in reverse to let uppermost stack frames "win" and overwrite bottommost stack
// frames' colouring.
for (let i = this.result.stackTrace.length - 1; i >= 0; i--) {
let frame = this.result.stackTrace[i]; let frame = this.result.stackTrace[i];
if (frame.span != null) { if (frame.span != null) {
diagnostics.push({ diagnostics.push({

View file

@ -336,6 +336,12 @@ export class Haku {
let count = w2.haku2_vm_stackframe_count(this.#pVm2); let count = w2.haku2_vm_stackframe_count(this.#pVm2);
let trace = []; let trace = [];
for (let i = 0; i < count; ++i) { for (let i = 0; i < count; ++i) {
// Don't collect information about return marker stack frames.
// They don't actually represent function calls, they're only used to signal the .ret
// instruction at which the VM ought to halt.
let isReturnMarker = w2.haku2_vm_stackframe_is_return_marker(this.#pVm2, i) != 0;
if (isReturnMarker) continue;
let isSystem = w2.haku2_vm_stackframe_is_system(this.#pVm2, i) != 0; let isSystem = w2.haku2_vm_stackframe_is_system(this.#pVm2, i) != 0;
let pc = w2.haku2_vm_stackframe_pc(this.#pVm2, i); let pc = w2.haku2_vm_stackframe_pc(this.#pVm2, i);