hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit ed9142ac20ea3f6bed3bc6d6f78acca6d82f7ec3
parent 87a97835e87f0a5dd3dfa330155a042a3cadef36
Author: Drew DeVault <sir@cmpwn.com>
Date:   Wed,  1 Jan 2025 20:00:57 +0100

debug: add stack trace storage solution

This provides a means of storing back traces and associating them with a
unique 64-bit ID, which can be used to annotate data structures with
backtraces -- say, for example, malloc metadata.

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mdebug/+aarch64/walk.ha | 2+-
Mdebug/+riscv64/walk.ha | 2+-
Mdebug/+x86_64/walk.ha | 2+-
Mdebug/backtrace.ha | 3++-
Adebug/traces.ha | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/debug/+aarch64/walk.ha b/debug/+aarch64/walk.ha @@ -32,7 +32,7 @@ export fn next(frame: stackframe) (stackframe | done) = { export fn frame_pc(frame: stackframe) uintptr = frame.lr; // Implementation detail, constructs a synthetic stack frame. -fn mkframe(next: *stackframe, ip: uintptr) stackframe = { +fn mkframe(next: nullable *stackframe, ip: uintptr) stackframe = { return stackframe { fp = next, lr = ip, diff --git a/debug/+riscv64/walk.ha b/debug/+riscv64/walk.ha @@ -32,7 +32,7 @@ export fn next(frame: stackframe) (stackframe | done) = { export fn frame_pc(frame: stackframe) uintptr = frame.ra; // Implementation detail, constructs a synthetic stack frame. -fn mkframe(next: *stackframe, ip: uintptr) stackframe = { +fn mkframe(next: nullable *stackframe, ip: uintptr) stackframe = { return stackframe { fp = next, ra = ip, diff --git a/debug/+x86_64/walk.ha b/debug/+x86_64/walk.ha @@ -32,7 +32,7 @@ export fn next(frame: stackframe) (stackframe | done) = { export fn frame_pc(frame: stackframe) uintptr = frame.ip; // Implementation detail, constructs a synthetic stack frame. -fn mkframe(next: *stackframe, ip: uintptr) stackframe = { +fn mkframe(next: nullable *stackframe, ip: uintptr) stackframe = { return stackframe { fp = next, ip = ip, diff --git a/debug/backtrace.ha b/debug/backtrace.ha @@ -15,7 +15,8 @@ def MAX_FRAMES_TOP: size = 16z; def MAX_FRAMES_BOTTOM: size = 16z; def MAX_FRAMES: size = MAX_FRAMES_TOP + MAX_FRAMES_BOTTOM; -fn backtrace(self: *image::image, frame: stackframe) void = { +// Prints a backtrace to the standard error. +export fn backtrace(self: *image::image, frame: stackframe) void = { let orig = frame; let nframe = 1z; for (true; nframe += 1) { diff --git a/debug/traces.ha b/debug/traces.ha @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MPL-2.0 +// (c) Hare authors <https://harelang.org> + +use hash; +use hash::fnv; +use rt; + +def TRACE_BUCKETS = 65535; + +let traces: [TRACE_BUCKETS][]trace = [[]...]; + +export type trace = struct { + id: u64, + frames: *stackframe, +}; + +// Retrives a stack trace by its ID. +export fn trace_by_id(id: u64) (stackframe | void) = { + let bucket = &traces[id % TRACE_BUCKETS]; + for (let trace &.. bucket) { + if (trace.id == id) { + return *trace.frames; + }; + }; +}; + +// Stores a stack trace and returns its ID. Stored stack traces are hashed and +// de-duplicated in a global stack list. +export fn trace_store(frame: stackframe) u64 = { + static let pc: []uintptr = []; + pc = pc[..0]; + + const hash = fnv::fnv64a(); + for (true) { + hash::write(&hash, &frame_pc(frame): *[size(uintptr)]u8); + append(pc, frame_pc(frame)); + + match (next(frame)) { + case let next: stackframe => + frame = next; + case done => break; + }; + }; + + const id = fnv::sum64(&hash); + let bucket = &traces[id % TRACE_BUCKETS]; + for (let trace &.. bucket) { + if (trace.id == id) { + return id; + }; + }; + + let frames: []stackframe = alloc([stackframe { ... }...], len(pc) + 1); + for (let i = 0z; i < len(pc); i += 1) { + frames[i] = mkframe(&frames[i + 1], pc[i]); + }; + + append(bucket, trace { + id = id, + frames = &frames[0], + }); + + return id; +};