From 7a52dbebd02876b27dd265597c444e69147a186f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Wed, 25 Jun 2025 21:10:54 +0200 Subject: [PATCH] fix some rough edges around stack traces hide irrelevant CallFrames fix span info for the implicit stack frame --- crates/haku2/src/haku2.zig | 15 ++++++++++----- crates/haku2/src/vm.zig | 20 +++++++++++++++----- static/brush-editor.js | 4 +++- static/haku.js | 6 ++++++ 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/crates/haku2/src/haku2.zig b/crates/haku2/src/haku2.zig index 15dc3b1..ec428fc 100644 --- a/crates/haku2/src/haku2.zig +++ b/crates/haku2/src/haku2.zig @@ -196,27 +196,32 @@ export fn haku2_vm_stackframe_count(vm: *const Vm) usize { 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 { 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 { const stack_frame = vm.stackFrame(index); - if (stack_frame.closure.impl == .bytecode) + if (stack_frame.closure.?.impl == .bytecode) if (stack_frame.ip) |ip| - return stack_frame.closure.programCounter(ip); + return stack_frame.closure.?.programCounter(ip); return -1; // no return address } export fn haku2_vm_stackframe_function_name_len(vm: *const Vm, index: usize) usize { 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 { const stack_frame = vm.stackFrame(index); - return stack_frame.closure.name.ptr; + return stack_frame.closure.?.name.ptr; } // Renderer diff --git a/crates/haku2/src/vm.zig b/crates/haku2/src/vm.zig index c08c8e3..d5b8d86 100644 --- a/crates/haku2/src/vm.zig +++ b/crates/haku2/src/vm.zig @@ -30,7 +30,7 @@ pub const Limits = struct { }; pub const CallFrame = struct { - closure: *const value.Closure, + closure: ?*const value.Closure, return_addr: ?[*]const u8, 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. pub const StackFrame = struct { - closure: *const value.Closure, + closure: ?*const value.Closure, ip: ?[*]const u8, }; @@ -117,7 +117,17 @@ pub fn stackFrame(vm: *const Vm, index: usize) StackFrame { if (index == 0) { return .{ .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 { // NOTE: Minus two, because remember index == 0 is occupied by the current stack frame. @@ -269,7 +279,7 @@ pub fn run( } try vm.pushCall(.{ - .closure = closure, + .closure = null, .return_addr = null, // nowhere to return to .bottom = bottom, }); @@ -591,7 +601,7 @@ pub fn run( try vm.push(result); if (call_frame.return_addr) |addr| { - closure = call_frame.closure; + closure = call_frame.closure.?; ip = addr; bottom = call_frame.bottom; diff --git a/static/brush-editor.js b/static/brush-editor.js index 7d77433..8e4f611 100644 --- a/static/brush-editor.js +++ b/static/brush-editor.js @@ -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]; if (frame.span != null) { diagnostics.push({ diff --git a/static/haku.js b/static/haku.js index 7d4a66a..69d1b0f 100644 --- a/static/haku.js +++ b/static/haku.js @@ -336,6 +336,12 @@ export class Haku { let count = w2.haku2_vm_stackframe_count(this.#pVm2); let trace = []; 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 pc = w2.haku2_vm_stackframe_pc(this.#pVm2, i);