ERR/E3.1: thread-local error return-trace ring buffer (runtime)

Add the trace buffer that raise/try push to and catch/or/destructure clear,
following the JNI-TLS precedent exactly (a thread_local IR global doesn't work
under the ORC JIT, which doesn't init TLS for AddObjectFile'd objects).

- library/vendors/sx_trace_runtime/sx_trace.c: a `_Thread_local` fixed-cap ring
  (32 frames) of opaque u64s + C API (push / clear / len / truncated /
  frame_at). Overflow keeps the newest CAP frames and latches `truncated`
  (Zig-style); frame_at returns oldest-to-newest. The frame is opaque — the
  E3.3 formatter dispatches on context (PC at runtime, packed (func_id, offset)
  at comptime).
- build.zig: link the .c into the compiler so the JIT resolves sx_trace_* via
  dlsym (and so the unit test links against it).
- src/runtime_trace.test.zig: exercises push / overflow-survives-newest / clear
  / len / truncated / ordering against the linked C — grounds the buffer logic
  without shipping throwaway sx builtins.
- lower.zig getTraceFids(): lazily declares the sx_trace_push/clear externs +
  sets needs_trace_runtime. Declared now; the raise/try push sites and the
  absorbing clear sites get wired at E3.2.
- core.zig: auto-injects the .c as a #source for AOT when needs_trace_runtime,
  mirroring the JNI env runtime.

Gates: zig build, zig build test (incl. the new buffer tests), bash
tests/run_examples.sh (277 passed; no codegen change this step — lone failure
is the user's uncommitted 213-canonical-map pack WIP).
This commit is contained in:
agra
2026-06-01 08:13:12 +03:00
parent a3ff503f47
commit 51f5277380
6 changed files with 197 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
// Unit tests for the ERR E3.1 error return-trace ring buffer
// (library/vendors/sx_trace_runtime/sx_trace.c). The .c is linked into the
// module via build.zig, so these extern symbols resolve at test-link time.
// This grounds the buffer logic (push / overflow-survives-newest / clear /
// len / truncated / oldest-to-newest ordering) before E3.2 wires the
// push/clear calls into codegen.
const std = @import("std");
extern fn sx_trace_push(frame: u64) void;
extern fn sx_trace_clear() void;
extern fn sx_trace_len() u32;
extern fn sx_trace_truncated() u32;
extern fn sx_trace_frame_at(i: u32) u64;
const CAP: u32 = 32;
test "trace buffer: push / len / frame_at oldest-to-newest" {
sx_trace_clear();
try std.testing.expectEqual(@as(u32, 0), sx_trace_len());
try std.testing.expectEqual(@as(u32, 0), sx_trace_truncated());
sx_trace_push(10);
sx_trace_push(20);
sx_trace_push(30);
try std.testing.expectEqual(@as(u32, 3), sx_trace_len());
try std.testing.expectEqual(@as(u64, 10), sx_trace_frame_at(0));
try std.testing.expectEqual(@as(u64, 20), sx_trace_frame_at(1));
try std.testing.expectEqual(@as(u64, 30), sx_trace_frame_at(2));
// Out-of-range frame → 0.
try std.testing.expectEqual(@as(u64, 0), sx_trace_frame_at(3));
try std.testing.expectEqual(@as(u32, 0), sx_trace_truncated());
}
test "trace buffer: clear resets length and truncation" {
sx_trace_clear();
sx_trace_push(1);
sx_trace_push(2);
sx_trace_clear();
try std.testing.expectEqual(@as(u32, 0), sx_trace_len());
try std.testing.expectEqual(@as(u32, 0), sx_trace_truncated());
try std.testing.expectEqual(@as(u64, 0), sx_trace_frame_at(0));
}
test "trace buffer: overflow keeps the newest CAP frames, latches truncated" {
sx_trace_clear();
// Push CAP + 5 frames with distinguishable values (i = 1..CAP+5).
var i: u64 = 1;
while (i <= CAP + 5) : (i += 1) sx_trace_push(i);
// Length saturates at CAP; truncation latched.
try std.testing.expectEqual(CAP, sx_trace_len());
try std.testing.expectEqual(@as(u32, 1), sx_trace_truncated());
// The surviving frames are the newest CAP, oldest-to-newest. The first 5
// (values 1..5) were overwritten, so frame_at(0) is value 6.
try std.testing.expectEqual(@as(u64, 6), sx_trace_frame_at(0));
try std.testing.expectEqual(@as(u64, CAP + 5), sx_trace_frame_at(CAP - 1));
// Strictly increasing by 1 across the surviving window.
var k: u32 = 0;
while (k < CAP) : (k += 1) {
try std.testing.expectEqual(@as(u64, 6 + k), sx_trace_frame_at(k));
}
}
test "trace buffer: exactly CAP frames, no truncation" {
sx_trace_clear();
var i: u64 = 0;
while (i < CAP) : (i += 1) sx_trace_push(i * 100);
try std.testing.expectEqual(CAP, sx_trace_len());
try std.testing.expectEqual(@as(u32, 0), sx_trace_truncated());
try std.testing.expectEqual(@as(u64, 0), sx_trace_frame_at(0));
try std.testing.expectEqual(@as(u64, (CAP - 1) * 100), sx_trace_frame_at(CAP - 1));
sx_trace_clear();
}