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

@@ -124,6 +124,9 @@ pub const Lowering = struct {
jni_env_tl_get_fid: ?FuncId = null, // extern `sx_jni_env_tl_get` (from library/vendors/sx_jni_runtime/sx_jni_env_tl.c)
jni_env_tl_set_fid: ?FuncId = null, // extern `sx_jni_env_tl_set`
needs_jni_env_tl_runtime: bool = false, // set when lowering touches the JNI env TL; signals Compilation to auto-link the runtime .c
trace_push_fid: ?FuncId = null, // extern `sx_trace_push` (ERR E3.1, from library/vendors/sx_trace_runtime/sx_trace.c)
trace_clear_fid: ?FuncId = null, // extern `sx_trace_clear`
needs_trace_runtime: bool = false, // set when lowering emits a trace push/clear; signals Compilation to auto-link sx_trace.c
foreign_class_map: std.StringHashMap(*const ast.ForeignClassDecl) = std.StringHashMap(*const ast.ForeignClassDecl).init(std.heap.page_allocator), // sx alias → ForeignClassDecl (jni_class / objc_class / swift_class / ... — registered in scan pass)
current_foreign_class: ?*const ast.ForeignClassDecl = null, // set while lowering a `#jni_main` (or any sx-defined `#jni_class`) bodied method — `super.method(args)` dispatch resolves the parent class against this fcd's `#extends`
current_foreign_method: ?ast.ForeignMethodDecl = null, // the specific method whose body is being lowered; `super.<same_name>(...)` reuses its signature
@@ -13029,6 +13032,33 @@ pub const Lowering = struct {
return .{ .get = self.jni_env_tl_get_fid.?, .set = self.jni_env_tl_set_fid.? };
}
/// Lazily declare the `sx_trace_push(u64)` / `sx_trace_clear()` runtime
/// externs (ERR E3.1). Storage is a `_Thread_local` ring buffer in
/// `library/vendors/sx_trace_runtime/sx_trace.c` — kept OUT of the user's IR
/// module (same JIT-TLS reason as the JNI env slot). Setting
/// `needs_trace_runtime` signals Compilation to auto-link the .c for AOT.
/// Wired into the `raise` / `try` push sites and the absorbing clear sites
/// at ERR E3.2.
fn getTraceFids(self: *Lowering) struct { push: FuncId, clear: FuncId } {
self.needs_trace_runtime = true;
if (self.trace_push_fid == null) {
const name = self.module.types.internString("sx_trace_push");
const frame_param = self.module.types.internString("frame");
var params = std.ArrayList(inst_mod.Function.Param).empty;
params.append(self.alloc, .{ .name = frame_param, .ty = .u64 }) catch unreachable;
const fid = self.builder.declareExtern(name, params.toOwnedSlice(self.alloc) catch unreachable, .void);
self.module.getFunctionMut(fid).call_conv = .c;
self.trace_push_fid = fid;
}
if (self.trace_clear_fid == null) {
const name = self.module.types.internString("sx_trace_clear");
const fid = self.builder.declareExtern(name, &.{}, .void);
self.module.getFunctionMut(fid).call_conv = .c;
self.trace_clear_fid = fid;
}
return .{ .push = self.trace_push_fid.?, .clear = self.trace_clear_fid.? };
}
/// When a namespaced import (`Ns :: #import "..."`) contains foreign-class
/// declarations, ALSO register them under their qualified name `Ns.Class`
/// so receiver types like `*Ns.Class` can find the fcd. The recursive