aboutsummaryrefslogtreecommitdiff
path: root/TODO.md
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 10:53:36 +0000
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 10:53:36 +0000
commit51f0d6205b2ceb78c64ecbfc09ad2056de671a4a (patch)
tree0f9de3e0c4c0b3fcf1bc316f55366d53c4ef2afd /TODO.md
parenttodo: test blocks, destructors and record/replay in flight (diff)
todo: record each agent's state at the end of the session and the merge order
Diffstat (limited to 'TODO.md')
-rw-r--r--TODO.md193
1 files changed, 170 insertions, 23 deletions
diff --git a/TODO.md b/TODO.md
index 28ecdd4..210b4a3 100644
--- a/TODO.md
+++ b/TODO.md
@@ -247,6 +247,14 @@ the reasoning behind the open items survives the session.
latency (microseconds per block against milliseconds for llvm), which
matters for short programs and the language server.
+- where to pick up (end of session): merge the unmerged agent work in this
+ order: line tables first (bounds checking and the conformance suite both
+ want positions in their failures), then bounds checking on top of it, then
+ the spec conformance suite, then the xref baseline and its ci check. test
+ blocks and destructors come after, since they are features rather than
+ safety nets. each agent's state and next step is in "agent work at the end
+ of the session" below.
+
### feature decisions (owner's requirements, settled)
- bounds checking is on by default; `unbounded` (used like `unchecked`:
function prefix, block, top-level statement) turns it off; `o3` implies
@@ -282,21 +290,160 @@ the reasoning behind the open items survives the session.
phase files, `docs/SPEC.md`, standard-library api docs and
`examples/std_tour.zc`, temporary notes removed, `PROPOSALS.md`.
-### in flight when this was written (not on the branch; re-brief if missing)
-these were running as agents in worktrees. if their branches were not merged
-before the session ended, the work is lost and each item below must be started
-again from its description in the open list. a zip of the repository with every
-worktree is uploaded to the owner after each update.
-- spec verification, restarted after an interruption. the first agent's
- partial work (runner plus sections 1 to 3, about 375 programs) is committed
- on the local branch `worktree-agent-a020917eebd018418` (3edfbaa) and the new
- agent continues from it; see "verify both compilers".
-- runtime line tables and backtraces, see "runtime source positions".
-- bounds checking with `unbounded`, see "bounds checking".
-- the cross-reference report, see "a cross-reference report".
-- test blocks as a library (`src/lib/test.zc`, `zy --test`), see "test blocks".
-- struct destructors (`fn drop()`) together with the `defer` single-statement follow-up, see "struct destructors".
-- record and replay at the syscall gate (`ZYRECORD`/`ZYREPLAY`), see "record and replay".
+### agent work at the end of the session (unmerged; read this first)
+the owner stopped the session with seven agents running. each was asked to
+commit what it had and report. their worktrees were copied next to the main
+code in the final zip (`agents/<name>/`, with `AGENT-COMMITS.txt`,
+`AGENT-CHANGES.diff` against the base they branched from, and
+`AGENT-UNCOMMITTED.txt`). none of it is on this branch. to resume one, apply its
+diff to a fresh branch (or re-create its worktree from the commits listed) and
+continue from "next" below; the original architecture is in the matching item
+of the open list.
+
+- line tables and backtraces (`agents/line-tables`, 3 commits, last 917bc34).
+ done and checked on zy, zym and the javascript vm: `lines on;`/`lines off;`,
+ `rt.trace()`, traces after the unchanged first line of `oob`, bad heap
+ pointer, use after free/double free, out of memory and frame overflow. per
+ function line blocks [next, entry, end, name, file, count, offset<<32|line...]
+ emitted into the code stream and chained from `rt.line_blocks`; compiler side
+ `lt_*` in base.zc plus hooks in stmt, function definition, closures and eval;
+ image.zc patches the image's own `rt.line_blocks`; check mode skips it. gate
+ op 56 returns the return-stack pointer (core.c `case 56: return cb_r;`,
+ micro.c from `r`, tinyvm.ts `this.rr`; wasm and baremetal go through micro.c;
+ jit and llvm paths untested). zc0 emits line tables for run-only images, via a
+ `//:lines NAME` marker that automap.sh puts before the program; the resident
+ compiler honours the same marker, so `build_image` images get tables too
+ (`runtime_image_test.py` has a case). stdin source is named `<stdin>`. tests:
+ trace.zc, trace_more.zc, trace_run.zc with `.expected` files; test.sh fault
+ checks changed to `head -1`. genome grows 383672 to about 391049 words (+1.9%)
+ from code alone, with lines off. not done: README and SPEC section 8 docs,
+ the focused suites on the final build, the measurements (image sizes on/off,
+ mandel compile time, genome with lines on), the x86-64 jit path for op 56.
+ limits: o3 frames are not on the return stack; a function straddling code
+ segments is not recorded; core-level stack overflow messages have no trace;
+ the frame window (top 24 plus last 4) prints "... N more frames" with a raw
+ word count. merge note: main has the x86-64 jit since this branched; op 56
+ must also work there (it reads `cb_r`, check the x64 hook sets it).
+- bounds checking with `unbounded` (`agents/bounds-checking`, 2 commits, last
+ 46aa911). done, builds to the fixpoint: zc0 accepts and ignores `unbounded`
+ in all three positions; the compiler has the prefix (any order with
+ `unchecked`, `immediate`, `o3`; methods and closures), the block and the unit
+ form. fixed arrays with a variable index get an inline `outside` check and
+ `oob`; `for (i < K; i++)` with constant K proves a fixed array of at least K
+ (unverified that the check is really skipped). pointers get a one-instruction
+ `rt.bnd` enhancement test, then `rt.bchk` to report; interior pointers, stack
+ arrays and globals are not checked; constant index 0 and analyzer-known sizes
+ skip it. heap lengths are the buddy block capacity (`rt.new(4)` reports
+ length 11); exact lengths need a header change. `unbounded` and `o3` also drop
+ growable-array checks (changes `o3_bounds` and `bounds_hi`, which expected an
+ abort). `unbounded;` heads rt.zc, core.zc, base.zc, server.zc, run.zc,
+ build.zc; only actor.zc's header peeks in `encode` are marked. every program
+ is unchanged except `bounds_hi`, `o3_bounds`, `o3call`; errors, typeerrors,
+ langerrors, learn, icon and actor pass. cost: pointer kernel user time 0.58 s
+ to 0.76 s, fixed-array variable-index kernel about 35% slower. next: mark
+ `main` in tests/programs/o3call.zc `unbounded` (it reads `p[-1]` on purpose);
+ update the `o3_bounds` and `bounds_hi` expectations; add boundsrules.zc,
+ bounds_fixed.zc, bounds_heap.zc, zc0unbounded.zc with their test.sh lines;
+ docs (README, SPEC 2.6.1, 3.7.4, 3.13, keyword table, `kwtext`, vscode
+ grammar); time the self-compile (a baseline build is in the session
+ scratchpad `base/`, not in the zip); `make parity`. the 35% fixed-array cost
+ is worth a look before merging (hoist checks out of loops whose bound is the
+ array length, extend `proven`).
+- spec conformance (`agents/spec-conformance`, last 808886b; the first,
+ interrupted attempt is `agents/spec-conformance-first-attempt`, already
+ folded in). done: sections 1 source (114 programs, zy 103 ok), 2 types
+ (142), 3 expressions (167, zy 165 ok). real self-hosted deviations found
+ (class a): a bad float or character literal in the first statement of a
+ function body is not reported (the lexer error is swallowed by lookahead);
+ `u*d` with `u`, `d` visible variables is taken as a recast (spec 1.6.3); the
+ bstr flag leaks into `int*` locals so `p[0]` reads a byte; the parameter
+ limit is 23 not 24 (decl.zc:326; closures 19, expr.zc:671); `void f() {
+ return 3; }` yields 3 not 0. spec issues (class c): 5.2.2 against 2.6.6 on
+ array parameters; `_` is an undocumented discard with a warning; an untyped
+ word stored into a float converts by value. zc0 (class b): mostly dropped
+ types (appendix a); b.1.11 literal saturation confirmed; new: INT_MIN / -1
+ gives 0 and % gives INT_MIN in zc0. partly done: section 4 statements (129
+ programs, zy 119 ok); six expectations are the agent's own mistakes to fix
+ (switch_break_in_loop_leaves_switch_only should be 5,
+ switch_expression_evaluated_once "hit1/", switch_label_forms calls t(-1),
+ t('a'), t(6), t(1), memdebug_report uses pn(rt.memreport()*100+7) substring
+ 107, memdebug_report_line line 9, asm_float_literal uses `word w`); real:
+ `memdebug scrub;` rejected (b.1.1/b.2.1 confirmed), top-level `o3 { }`
+ rejected (c), `case rect():` with fields possibly rejected (unresolved).
+ left: sections 5 to 8, the appendix b confirm/refute list, `known.txt`, the
+ `--zc0` runs, final counts, then add the runner to tests/test.sh.
+- record and replay (no code; agent stopped during research). findings: every
+ syscall goes through `hostcall(op, a, b, c)` (core.c ~264) with three callers
+ (interpreter ~6324, jit `hook()` ~3617 which the x86-64 helpers also reach,
+ llvm ~5060): rename it `hostcall_real` and wrap it. stdin does not go through
+ the gate: the vm reads it with the Q opcode (io_linux.c; `J` is the end flag),
+ stdout with G, so log Q as (byte | J<<8) chunks flushed before each gate
+ record. the program source and its input share stdin (the compiler reads up
+ to the backtick with `srcend` in lex.zc), so replaying a modified program
+ needs a c mirror of `srcend`: serve the new source live, then answer the rest
+ from the log after skipping the old source. ops writing vm memory: 3 and 11
+ (read), 5 and 13 (getenv), 22 and 24 (argv, env via `vecstr`), 9 sub-ops, 41
+ (float format), 20 (scan), 43 to 45 (fork, channels); 18 and 19 are pure;
+ 14 to 17 (c abi) and 51 to 54 (contexts) hand out native handles: stop in
+ replay, and detect thread start (51, 43) in record. micro.c has its own gate.
+- struct destructors and the `defer` single-statement form (no code; research
+ only). findings: `freevar(i)` (analyzer.zc ~611) is the only place an owned
+ local is freed, called from `exit_vars` (block end, return, break, continue,
+ deferred frees) and `fixups` (join paths); `borrow_cleanup_since` (~136) frees
+ fresh temporaries passed to borrow parameters (needs the argument type kept in
+ `emit_borrow_hold` to drop them); explicit `rt.free`/`rt.recycle` go through
+ `call()` in expr.zc, where `xty` is the argument type after `args()`;
+ reassignment never frees implicitly (`assigncheck` rejects overwriting a live
+ owner). a struct is `TY_STRUCT` with its name in `t[2]`, a pointer `TY_PTR`
+ with the struct in `t[4]`; find `drop` with `lookup(methname(name, "drop"))`
+ and `skind == K_FN`; reject generic, static, argument-taking or
+ result-returning `drop` in `kw_struct`. planned emission before the free,
+ when `fchk && !o3on && !dry`: `lit(0); op(O_PICK); skip = hole(); op(O_JZ);
+ lit(0); op(O_PICK); fnref(dropsym); op(O_CALL); op(O_DROP); setlit(skip,
+ here);`. the defer form: `defer_tokens`/`kw_defer` in stmt.zc.
+- test blocks (no code; research only). findings: the native core never reads
+ a file argument (`zy foo.zc < src` keeps reading stdin; only the javascript
+ runner reads argv[2]), and any `--option` as argv[1] is rejected as unknown
+ in core.c main (~6743), micro/port_linux.c main (~375) and tinyvm.ts (~960),
+ so `--test` needs a small change in all three (accept it and keep it in
+ argv). `main` is called in `start()` in setup.zc (~187) through
+ `lookup(s_main)`, and `s_main` is a global of namespace `run`, so the library
+ can point it at `test.run_all` with no compiler change. plan: `src/lib/test.zc`
+ like actor.zc (`run.syntax("test", parse_test)`); without `--test` skip the
+ block by brace matching; with it compile the block through `run.tokens` and
+ `run.compile_list_context` as `test.case_N` and record [fn, name]; arm the
+ runner at include time; rewrite `expect(c)` to `if (!(c)) { test.fail(line,
+ text); return; }`. open: whether the keyword handler can decline when the
+ next token is not a string (so `test` stays usable as a name). diagnostics
+ print `zc: line N:` with no file name.
+- compiler cross-reference report (`agents/xref-report`, one wip commit
+ e5d5136). done: `src/tools/xref.js` and `src/tools/xref.sh` (`--help`,
+ unknown option exits 2). the language server has no documentSymbol, so the
+ globals come from the tool's own parse of the joined compiler source (652
+ globals, 628 functions, nothing left unparsed); writers and readers come from
+ one zclsp references request per global over 4 parallel servers. one full run
+ finished in 11m53s (about 2.5 s per request, about 30 s per server to check
+ the document); its output is in the zip at `agents/xref-report/xref-out/`
+ (report.txt, report.json), not yet reviewed by a person. top 15 globals by
+ number of writer functions, read off that json by the coordinator: xty 29,
+ xbor 23, xfloat 22, lv 21, xaf 21, lvk 20, xvk 19, ltag 17, xzero 17,
+ lstate 16, sflag 16, effect_value 15, fslots 13, panic 13, chkabort 12 (these
+ are the first candidates for the "group the parallel arrays" item). 60
+ globals show no writer and 198 no reader (many are `_cap` twins); some of the
+ "no writer" ones are the known limits below, so check before trusting them.
+ written but never run: the `make xref` target, the README and ONBOARD.md
+ sections, the xref.sh entry in `tests/drivers/cli_help_test.py`. not done:
+ `tests/xref.baseline` (derive it from report.json, one line per global with
+ its sorted writers, or run `xref.sh --update` in the background), `--check`
+ and `--update` never run, the self-test (copy src/compiler to a scratch dir,
+ add one write to a global in another function, `xref.sh --compiler-dir DIR
+ --out SCRATCH --check` must exit 1), README says "minutes" where it should
+ say about 12 minutes. known limits: a store through a pointer (`*p = ...`),
+ an array written by a callee (`copy_w`, `memset`) and a global reached only
+ through its `_initial` array are not counted as writes. next: baseline,
+ `--check` (expect 0), self-test, `python3 tests/drivers/cli_help_test.py
+ --group scripts`, commit. a 12 minute run is too slow for every ci job; run
+ it nightly or only when `src/compiler/` changes.
### decisions
- no structs in zc0 (owner's call): zc0 stays the small trusted root of the
@@ -312,28 +459,28 @@ items with an architecture note carry the design worked out so far, enough to
brief someone without re-reading the code.
- [ ] remaining zc0 gaps: string literals over 4095 bytes, `embed` of an array type inside a function, `x as T` with more than a single name (`x as int*`), zc0 silently accepting `.u` on a parameter, and integer literals (`010` is 8 in zc0, 10 in the compiler; zc0 saturates on overflow). `docs/history/43-stdlib/notes.md` still says zc0 reserves u/m/d: add a pointer forward rather than rewriting history.
-- [ ] (in flight) verify both compilers against `docs/SPEC.md` with a fresh agent that did not write it: expectations derived from the spec text before running anything, one program per rule under `tests/conformance/<section>/`, a runner `tests/conformance/run.sh`, each failure classified as self-hosted bug, zc0 bug, or spec error, and appendix b confirmed item by item. then add the runner to `tests/test.sh`.
+- [ ] (partly done, see "agent work at the end of the session") verify both compilers against `docs/SPEC.md` with a fresh agent that did not write it: expectations derived from the spec text before running anything, one program per rule under `tests/conformance/<section>/`, a runner `tests/conformance/run.sh`, each failure classified as self-hosted bug, zc0 bug, or spec error, and appendix b confirmed item by item. then add the runner to `tests/test.sh`.
- [ ] fix the implementation bugs listed in `docs/SPEC.md` appendix b, once confirmed: `memdebug scrub;` rejected; enum members can't reference earlier members; a plain function redefinition silently shadows the first; a prototype after the definition hides it; `?:` with one int and one float branch gives garbage; `bool` not normalised; float global initializers rejected; a global initializer naming another global uses its address; `include std;` inside a function body; syntax-error recovery hiding later errors; unterminated `/*` and string literals; `x as int * y`; prototypes needing parameter names; `len(char*)` with typecheck off; duplicate `case` labels; local `const` in constant expressions; constant expressions without precedence (`1+2*3` is 9). fix the prose items the appendix lists too.
-- [ ] (in flight) a cross-reference report: for every global of the compiler, the functions that write it and the ones that only read it. build it from the language server's references (`textDocument/references` on `build/bin/zclsp`, so scoping is exact) and classify each reference as a write when an assignment operator, `++` or `--` follows it. output under `build/xref/`; a committed baseline and a `--check` mode that fails when a new writer of a global appears, so it can run in ci.
+- [ ] (partly done, see "agent work at the end of the session") a cross-reference report: for every global of the compiler, the functions that write it and the ones that only read it. build it from the language server's references (`textDocument/references` on `build/bin/zclsp`, so scoping is exact) and classify each reference as a write when an assignment operator, `++` or `--` follows it. output under `build/xref/`; a committed baseline and a `--check` mode that fails when a new writer of a global appears, so it can run in ci.
- [ ] optional, low priority: plain enums and `?:` in zc0 (both are small and produce ordinary code), so parity covers more programs. no structs in zc0 (decided).
- [ ] group the compiler's parallel per-local arrays (`lname`, `ltype`, `lstate`, `lhome`, `lown`, `lslot`, `ltag`, `lpos`, `lnsz`, `lleaf`, ...) without structs: declare each family together in one place, with one comment block stating each array's meaning and invariant (for example when `lhome` is meaningful), route snapshot and restore of the family through one pair of functions, and use the cross-reference report to confirm nothing else writes them. then symbols and types the same way. fixpoint and analyzer behavior unchanged.
- [ ] continue removing `unchecked` from the runtime. a reverted trial showed: `copy_w`/`fill_w` pass the analyzer when their gate stores go through `syscall(SYS_COPY, ...)`, `dgrow` with `own(w, it)` instead of `w[-2] = it`, `dpush` with `own` before the store, `dnew` as is; but the checked versions changed behavior (7 program checks failed, `tests/programs/share.zc` hung with a checked `cons`), so checked code inserts cleanup these functions can't afford. each needs its contract taught to the analyzer. `free`, `recycle`, `recycle_leaf`, `gc` are the allocator and collector the analyzer's own cleanup calls: document them as the irreducible escapes. `src/lib/actor.zc` has about 20 more.
- [ ] revise diagnostics, comments and learning/reference prose using professional compiler documentation guidance, preserving lowercase and adding message regressions. touches messages everywhere, so do it when few other compiler changes are in flight.
-- [ ] (in flight) test blocks: `test "name" { ... }` with `expect(cond)`, compiled only by `zy --test file.zc`, which runs each block and prints one line per test with the position of the first failed `expect`; skipped at parse time otherwise. build it as a library through `run.syntax` and `run.statement_hook`, like `src/lib/actor.zc`.
-- [ ] (in flight) bounds checking on by default, `unbounded` to turn it off (function prefix, block, top-level `unbounded;`, mirroring `unchecked`), implied by `o3`. architecture: indexing is compiled in the postfix `[` handling of the expressions part; growable arrays already check (`dat`, `range_jz`, the `outside` enhancement, `proven()` removing checks a loop test already did). fixed arrays `T[n]` with a variable index get the same inline check against the constant `n`. pointers get a runtime call (for example `rt.bchk(p, i)`) that checks only when `p` is the start of a live heap block (header check word `p ^ MAGIC`, `paylen`), skipping constant index 0 and sizes the analyzer knows (`lnsz`). reports go through `oob`. the runtime, core, compiler parts, server, `run.zc` and `build.zc` start with `unbounded;`, which zc0 accepts and ignores (genome bytes unchanged). keep std, icon and actor checked unless a function touches raw memory on purpose. measure interpreter time before and after.
+- [ ] (partly done, see "agent work at the end of the session") test blocks: `test "name" { ... }` with `expect(cond)`, compiled only by `zy --test file.zc`, which runs each block and prints one line per test with the position of the first failed `expect`; skipped at parse time otherwise. build it as a library through `run.syntax` and `run.statement_hook`, like `src/lib/actor.zc`.
+- [ ] (partly done, see "agent work at the end of the session") bounds checking on by default, `unbounded` to turn it off (function prefix, block, top-level `unbounded;`, mirroring `unchecked`), implied by `o3`. architecture: indexing is compiled in the postfix `[` handling of the expressions part; growable arrays already check (`dat`, `range_jz`, the `outside` enhancement, `proven()` removing checks a loop test already did). fixed arrays `T[n]` with a variable index get the same inline check against the constant `n`. pointers get a runtime call (for example `rt.bchk(p, i)`) that checks only when `p` is the start of a live heap block (header check word `p ^ MAGIC`, `paylen`), skipping constant index 0 and sizes the analyzer knows (`lnsz`). reports go through `oob`. the runtime, core, compiler parts, server, `run.zc` and `build.zc` start with `unbounded;`, which zc0 accepts and ignores (genome bytes unchanged). keep std, icon and actor checked unless a function touches raw memory on purpose. measure interpreter time before and after.
- [ ] after the fuzzing work, add more general functionality to the standard library.
-- [ ] (in flight) struct destructors: a struct may declare `fn drop()`, called wherever the compiler frees an owned value of that type, before the memory is released. architecture: the compiler frees owned locals in `freevar` (analyzer part), called from `exit_vars`, reassignment and explicit `rt.free`/`rt.recycle` calls (`s_free`, `s_recycle` handling in the call code); when the local's static type is a pointer to a struct with a `Name.drop` method, emit a call to it first. objects freed by the collector or inside unchecked code do not run `drop`: document that. first users: file descriptors in `std.Host`, processes in `actor.zc`.
-- [ ] (in flight) runtime source positions. architecture: at each statement start (`stmt()`) record (code address `here`, line); per function a permanent data block [entry, end, name, file, count, pairs...] linked into a runtime list (`rt.line_blocks`), or appended to run-only images by `build_image` like the symbol blob. `lines on;`/`lines off;` like `docs on;`; on for user programs, off for the genome unless the growth is small. a new syscall op returns the return-stack pointer (the return stack is in vm memory, 0x1A4100..0x1B40FF, growing up); implement it in `src/core/core.c`, `src/micro/micro.c`, `src/js/tinyvm.ts` and the wasm port. `rt.trace()` walks the return stack, printing ` at file:line in function` for words inside a known function range. the runtime's fatal reports (`oob`, bad heap pointer, double free, out of memory, frame overflow) print the trace after their unchanged first line. check mode skips all of it.
+- [ ] (partly done, see "agent work at the end of the session") struct destructors: a struct may declare `fn drop()`, called wherever the compiler frees an owned value of that type, before the memory is released. architecture: the compiler frees owned locals in `freevar` (analyzer part), called from `exit_vars`, reassignment and explicit `rt.free`/`rt.recycle` calls (`s_free`, `s_recycle` handling in the call code); when the local's static type is a pointer to a struct with a `Name.drop` method, emit a call to it first. objects freed by the collector or inside unchecked code do not run `drop`: document that. first users: file descriptors in `std.Host`, processes in `actor.zc`.
+- [ ] (partly done, see "agent work at the end of the session") runtime source positions. architecture: at each statement start (`stmt()`) record (code address `here`, line); per function a permanent data block [entry, end, name, file, count, pairs...] linked into a runtime list (`rt.line_blocks`), or appended to run-only images by `build_image` like the symbol blob. `lines on;`/`lines off;` like `docs on;`; on for user programs, off for the genome unless the growth is small. a new syscall op returns the return-stack pointer (the return stack is in vm memory, 0x1A4100..0x1B40FF, growing up); implement it in `src/core/core.c`, `src/micro/micro.c`, `src/js/tinyvm.ts` and the wasm port. `rt.trace()` walks the return stack, printing ` at file:line in function` for words inside a known function range. the runtime's fatal reports (`oob`, bad heap pointer, double free, out of memory, frame overflow) print the trace after their unchanged first line. check mode skips all of it.
- [ ] borrow returns tied to an argument. inference already works for a callee defined before the call: `char* skip(char* s) { ...; return s + i; }` makes `let r = skip(t); rt.free(t); r[0]` an error. the gap: when the call comes before the callee's definition, nothing is reported and the program reads freed memory. extend the deferred effect requirements (`effect_require`, `effect_finish` at unit end) so a forward-called function whose return turns out to alias a parameter produces the same error. for bodyless declarations, externs and function types, add an unobtrusive spelling, for example a `borrows s` clause after the parameter list (`char* skip(char* s) borrows s;`), checked against the body when one exists.
- [ ] variadic arguments and `std.print`. design: a pack is passed as one hidden argument, a caller-built read-only growable-array handle (`fixed T[?]` view: [len, cap, items, element size] in the caller's frame, so `len(a)` and `a[i]` work unchanged and nothing is allocated). `...int a` is a typed pack; several packs per function when their element types differ (`void f(...int a, ...char b)`): each extra argument goes to the pack whose element type matches its static type, an ambiguous match is an error, packs come after the fixed parameters. `...any args` packs each value with a kind code (int, float, char, string, bstr, pointer, ...) and its type record: `any` is a small built-in record { kind, value, type }. `...*any args` makes the call comma-free: after the fixed arguments, the parser reads unary expressions until `)`, commas optional, so `std.print("x = " name ", age " age "\n");` works and binary expressions must be parenthesized. `std.print(...*any args)` prints each argument by its kind (`put_n`, the float printer, `put_s`, `put_bs`, a character for `char`, hex for pointers).
- [ ] x86-64 jit phase 2: compile o3 regions to x86-64 (port `rcompile` and the region register allocation; regions now run on the region interpreter and are 2 to 4 times slower than their plain versions on this jit) and inline enhancements (lower the optimized micro-ops to x86 registers as the arm64 path does, instead of calling `xrun`). validate with `tests/drivers/jit_x64_test.sh` and `tests/bench/bench.sh`, which now has an x86-64 jit column. add the driver to `tests/test.sh` on x86-64 linux.
- [ ] `memdebug`'s `obj_base` searches only 64 words back for a block header, so under `memdebug on` an interior access more than 64 words into a block (`buf[255]` on a 256-word block) reports "bad heap pointer". find the header from the buddy allocator's size classes instead of a bounded scan, with a regression program.
-- [ ] (in flight) `defer` follow-ups (small): accept `defer if (...) ...;` and other single statements besides an expression; decide whether deferred code may name an owner declared after the `defer` (now rejected by the lexical rule; one line in `defer_one`).
+- [ ] (partly done, see "agent work at the end of the session") `defer` follow-ups (small): accept `defer if (...) ...;` and other single statements besides an expression; decide whether deferred code may name an owner declared after the `defer` (now rejected by the lexical rule; one line in `defer_one`).
- [ ] build the llvm core by default when `llvm-config` is available (the makefile falls back to the interpreter otherwise), so x86 hosts get a native tier and the language server stops being interpreted; keep `ZYNOLLVM=1` and `-DZYINTERP` as the opt-outs.
- [ ] stack-effect checking: every function and `asm` block declares or is inferred to have a fixed data-stack effect, and the compiler reports a block whose paths leave the stack unbalanced. the first step of a proving scaffold.
- [ ] declared side effects at module boundaries (globals touched, syscalls or not), checked against the effect masks the compiler already computes.
- [ ] `requires`/`ensures` contracts on functions, compiled to run-time checks under a switch, with the cases the value facts settle (constants, simple ranges) checked statically.
-- [ ] (in flight) record and replay: a core option that logs every syscall gate result to a file and replays a run from the log exactly, for reproducing failures and adding tracing after the fact.
+- [ ] (partly done, see "agent work at the end of the session") record and replay: a core option that logs every syscall gate result to a file and replays a run from the log exactly, for reproducing failures and adding tracing after the fact.
- [ ] a debug adapter for vscode (breakpoints, stepping, locals) on the interpreter, built on the line tables.
- [ ] reduce the cost of word-per-character strings: measure, then decide between making `bstr` the default string, unifying the two apis behind one name, or both.
- [ ] continue fuzzing the compiler, analyzer, runtime, standard library, lsp, build tools and generated artifacts, including analyzer properties, memory-limit edges, compiler image mutation and long-running lsp memory use; feed generated programs to `make parity` for differential fuzzing between zc0 and the self-hosted compiler; fix discovered bugs.