`zig build test` now runs the full examples/ + issues/ regression corpus
alongside the Zig unit tests, driven by a pure-Zig test
(src/corpus_run.test.zig) — no shell script in the build path. It spawns
the installed `sx` per example (subprocess-isolated, per-run timeout),
diffs stdout/stderr/exit and optional `sx ir` snapshots, and fails the
build on any mismatch. The file list is enumerated at runtime, so new
examples are covered with no test edit.
- `sx ir` / `ir-dump` now write to stdout (fd 1) instead of stderr, so
the dumps can be piped/redirected.
- `zig build test -Dupdate-goldens` regenerates snapshots in-build,
byte-identical to the legacy `run_examples.sh --update`; on mismatch
the runner prints how to regenerate.
- run_examples.sh kept (still used by tools/verify-step.sh) and made
portable to a bare macOS: timeout/gtimeout fallback, bash 3.2-safe
empty-array handling.
- CLAUDE.md: document the new workflow.
Adds `src/lsp/corpus_sweep.test.zig`: a permanent test that drives the
editor analyzer (`DocumentStore.analyzeDocument` — the exact path the
server's `textDocument/didOpen` uses) over EVERY `.sx` file in the
example + issue corpora, in process. The contract: analysis must
complete without panic/abort for any file. A panic aborts the test
binary — the loud CI signal that some new AST node shape crashes the
analyzer (the bug class issue 0099 fixed at sema.zig:397).
- Corpus dirs are injected as absolute paths at configure time
(build.zig `corpus_paths` options module) so the sweep is
CWD-independent; the FILE LIST is still read from disk at test time,
so new examples are covered automatically with no test edit.
- Imports resolve against the shipped `library/` (root_path + stdlib
path set), so the analyzer runs over real, fully-resolved code —
maximum crash surface, mirroring an editor session opened on the repo.
- Wired into `zig build test` via the `src/root.zig` lsp barrel, same
mechanism document.test.zig uses (refAllDecls reaches one struct deep,
so the file is referenced directly).
- `SX_LSP_SWEEP_VERBOSE` prints each file before analysis; on a crash the
last printed line names the offending file.
Coverage: 470 examples + 1 issue repro analyze with zero crashes.
Regression-guard proven: temporarily reverting A's sema.zig:397 fix
(`@intCast(ate.length.data.int_literal.value)`) makes the sweep abort
with `access of union field 'int_literal' while field 'identifier' is
active`; restoring it turns the sweep green.
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).
`#jni_call` collapses to a single surface — env is *always* implicit:
either picked up from the lexically-enclosing `#jni_env(env) { ... }`
block's Ref (cheap, register-resident, no TL touch) or from the
runtime's thread-local slot via `sx_jni_env_tl_get()` (one fn call
per dispatch). The explicit-env shape is gone — chess and the
existing tests migrate cleanly by wrapping their helper-fn bodies
in `#jni_env(env) { ... }`.
The TL slot lives outside the user's IR module so the LLVM ORC JIT
can load object files cleanly without `orc_rt` for TLS support:
library/vendors/sx_jni_runtime/sx_jni_env_tl.c:
static _Thread_local void *sx_jni_env_tl_slot;
void *sx_jni_env_tl_get(void) { return sx_jni_env_tl_slot; }
void sx_jni_env_tl_set(void *env) { sx_jni_env_tl_slot = env; }
Linkage:
- sx-the-compiler links the .c file via build.zig so the JIT
process-symbol generator resolves `sx_jni_env_tl_get`/`_set`.
- AOT targets get the same .c file auto-linked via the lowering
pass: when lower touches the TL externs, it sets
`needs_jni_env_tl_runtime`, and `Compilation.lowerToIR` appends a
synthetic `CImportInfo` to `lowering_extra_c_sources` that
`collectCImportSources` merges with user-written ones.
Lowering-side changes:
- `getJniEnvTlFids` lazily declares the two externs (parallel
to `getSelRegisterNameFid`) and flips `needs_jni_env_tl_runtime`.
- `#jni_env(env) { body }` emits save→set→body→restore via three
`call` ops to the externs; the inner body sees env via the
lexical-direct stack.
- `lowerJniCall` resolves env from `jni_env_stack` (top) or the TL
fallback. The explicit-env branch is gone.
- `jni_env_stack_base` tracks per-fn lexical scope so lazy-lowering
a callee doesn't accidentally see the caller's Ref (Refs are only
valid inside one fn's instruction stream).
Test migration (mechanical):
- ffi-jni-call-{01..09}: each helper fn wraps `#jni_call(...)`
bodies in `#jni_env(env) { ... }`. Returning values pass through
the block as an expression — `#jni_env` now also lowers in
expression position.
Verified:
- zig build test + tests/run_examples.sh: 130/130 green.
- tests/cross_compile.sh: 3/3 green.
- Chess APK rebuilt + reinstalled on Pixel. Board renders with
status-bar clearance + info panel intact; no crashes in logcat.
Safe-insets dispatch through `#jni_env` + lexical-direct now
fully exercised end-to-end on real hardware.
- examples/modules/ -> library/modules/ (top-level, no more
symlink hacks in consumer projects)
- compiler discovers stdlib via _NSGetExecutablePath / readlink
/proc/self/exe; searches dev layout (../../library), install
layout (../library), and alongside-binary fallback
- SX_STDLIB_PATH env var overrides for tests / dev convenience
- SX_DEBUG_STDLIB env var dumps the discovery results
- build.zig installs library/ alongside the binary
- Compilation gains stdlib_paths field threaded through resolveImports
- 50 tests pass; consumer projects can now build from any cwd