commit 1307fac52dc44f5fb029f5d56bfa657b9c7e1bcc
parent 1ecf0ceacd8cfc6c1cdf0407fa3d4e75408a66bf
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 30 Apr 2026 06:53:44 -0700
tcc todo update
Diffstat:
| M | docs/TCC-TODO.md | | | 524 | +++++++++++++++++++++++++++---------------------------------------------------- |
1 file changed, 181 insertions(+), 343 deletions(-)
diff --git a/docs/TCC-TODO.md b/docs/TCC-TODO.md
@@ -1,389 +1,227 @@
-# tcc.flat.c via scheme cc — known blockers
+# tcc-boot2 Current TODO
-Working punch list for what stands between today's scheme1-hosted cc
-and a successful compile of `tcc.flat.c`. Companion to
-[TCC.md](TCC.md) (the surrounding three-stage pipeline) and
-[CC.md](CC.md) (the C subset the cc accepts).
+Current tracker for the scheme1-hosted `cc.scm` path that builds
+`tcc.flat.c` into `tcc-boot2`. Historical parser, scratch, linker, and
+runtime bring-up notes have been removed from this file; those fixes
+are now covered by tests and by the build rules themselves.
-## Repro
+Companion docs:
-`tcc.flat.c` is the stage-1 artifact. Generate it with the host
-preprocessor:
+- [TCC.md](TCC.md) describes the surrounding tcc pipeline.
+- [CC.md](CC.md) describes the C subset and validation milestones.
+- [LIBC.md](LIBC.md) describes the libc side used to link `tcc-boot2`.
-```
-sh scripts/stage1-flatten.sh --arch X86_64
-# -> build/tcc/X86_64/tcc.flat.c (608 KB, 18 896 lines, 0 directives)
-```
+## Current State
+
+`cc.scm` can compile the flattened tcc translation unit, the P1pp
+output assembles and links, and `tcc-boot2` starts. The old blockers
+around whole-file parse coverage, scratch exhaustion, tentative
+definitions, anonymous members, `offsetof` const-expr, large AArch64
+stack frames, argv preservation, and `for`-`continue` lowering are
+done and have focused regression tests.
-Run the catm'd scheme cc against it inside the per-arch container.
-The cc-debug flag prints heap usage between phases on stderr:
+Useful smoke checks:
+```sh
+make tcc-boot2 ARCH=aarch64
+
+build/aarch64/tcc-boot2/tcc-boot2 -v
+build/aarch64/tcc-boot2/tcc-boot2 -E smoke.c
+build/aarch64/tcc-boot2/tcc-boot2 -c smoke.c -o smoke.o
```
-podman run --rm --pull=never --platform linux/arm64 \
- --tmpfs /tmp:size=512M -e ARCH=aarch64 \
- -v "$(pwd)":/work -w /work boot2-busybox:aarch64 \
- build/aarch64/scheme1/scheme1 build/aarch64/cc/cc.scm --cc-debug \
- build/tcc/X86_64/tcc.flat.c /tmp/tcc.flat.P1pp
+
+For native generated-program testing, use the ARM64-targeted build via
+the `tcc-cc` suite:
+
+```sh
+make test SUITE=tcc-cc
```
-Prerequisites: `make scheme1 cc ARCH=aarch64` (or any other arch) so
-`build/$ARCH/scheme1` and `build/$ARCH/cc/cc.scm` exist.
+## `tcc-cc` Suite
-For triage, the small-prefix probe is useful:
+`tcc-cc` is the next acceptance suite. It runs the plain `tests/cc`
+fixtures through `tcc-boot2` instead of through `cc.scm` directly.
+The Makefile builds an ARM64-targeted `tcc-boot2`, builds the tiny
+aarch64 `_start` object with the host assembler, then the runner does:
-```
-head -c 50000 build/tcc/X86_64/tcc.flat.c \
- > build/tcc/X86_64/tcc.head.c
-# then re-run the podman invocation against tcc.head.c
+```sh
+build/aarch64/tcc-boot2/tcc-boot2 \
+ -nostdlib build/aarch64/tcc-cc/start.o tests/cc/NAME.c \
+ -o build/aarch64/tests/tcc-cc/NAME
+
+./build/aarch64/tests/tcc-cc/NAME
```
-## Status — tcc-boot2 builds and runs
+The result is compared against the same `.expected` and
+`.expected-exit` files used by the regular `cc` suite. The suite is
+aarch64-only today because it needs generated binaries to run natively
+inside the aarch64 container.
-The full 608 KB TU now parses to EOF (line 18800) and cg-finish emits
-~6.5 MB of P1pp. No semantic-coverage gap remains in this TU. Last
-aarch64 cc-debug run:
+Run a subset with `NAMES`:
-```
-[cc] phase=start: heap 1 225 052
-[cc] phase=slurp: heap 3 101 100 src-bytes 608 547
-[cc] decl: line 14861 heap 61 008 644
-[cc] decl: line 18024 heap 66 002 084
-[cc] decl: line 18800 heap 64 824 540 ; final decl
-[cc] phase=parse: heap 64 864 516
-[cc] phase=cg-finish: heap 90 674 020 out-bytes 6 489 215
+```sh
+NAMES='002-arith 007-call-with-args' make test SUITE=tcc-cc
```
-The emitted P1pp now assembles through m1pp → M0 → hex2 and links with
-the mes-libc subset via the `tcc-boot2` make target. Runtime smoke
-tests now pass under the aarch64 container:
+## Latest `tcc-cc` Result
+Fresh run:
+
+```sh
+make test SUITE=tcc-cc
```
-build/aarch64/tcc-boot2/tcc-boot2 -v
-# tcc version 0.9.26 (x86_64 Linux)
-build/aarch64/tcc-boot2/tcc-boot2 -E smoke.c
-# preprocesses successfully
+Result:
-build/aarch64/tcc-boot2/tcc-boot2 -c smoke.c -o smoke.o
-# writes an x86-64 relocatable object
+```text
+13 passed, 140 failed
```
-The old traced aarch64 crash tail with `CC_TRACE_EMIT=1` was:
+Raw run log:
-```
-[trace @663108 cc__next_nomacro]
-[trace @662d68 cc__next_nomacro_spc]
-[trace @658d20 cc__next_nomacro1]
-[trace @630580 cc__tok_alloc_new]
-[trace @62d228 cc__tal_realloc_impl]
-[trace @607bb4 memcpy]
-[trace @6078e8 _memcpy]
-Segmentation fault (core dumped)
+```text
+build/aarch64/.work/tests/tcc-cc/full-run.log
```
-Address lookup for the tail:
+Passing fixtures:
-```
-0x630580 cc__tok_alloc_new+0x30
-0x62d228 cc__tal_realloc_impl+0x30
-0x607bb4 memcpy+0x30
-0x6078e8 _memcpy+0x30
+```text
+000-empty-main
+000-return-argc
+001-return-argc
+002-add-const
+003-local-assign
+004-if-else
+005-while-break
+012-comparison
+014-mul-paren
+018-sext-narrow
+026-sizeof-expr
+049-init-scalar-global
+072-enum-const
```
-That trace was misleading: temporary probes showed `tok_alloc_new`
-completed and returned. Disassembly showed the real fault was a
-truncated AArch64 stack-frame immediate. `cc__next_nomacro1` requested
-a frame larger than 4095 bytes, but `aa64_sub_imm` masked the value to
-12 bits, so later stack slots addressed memory outside the allocated
-frame. `P1/P1-aarch64.M1pp` now emits one or two ADD/SUB-immediate
-instructions for large immediates, including `%enter(size)` frames.
-Regression: `tests/p1/large-addi.P1pp`.
-
-One follow-on runtime issue was also fixed: `P1/entry-libc.P1pp` now
-saves `argc`/`argv` across `__libc_init`, so TCC actually receives its
-command-line arguments. That exposed a compiler bug where `continue`
-inside `for (...; ...; step)` jumped to the condition and skipped the
-step expression. `cc/cc.scm` now lowers `for` loops so `continue`
-lands on the step block. Regression: `tests/cc/133-for-continue.c`.
-
-Historical source review put the final `memcpy` after
-`tal_realloc_impl` returns in `tok_alloc_new`:
+Failure groups from per-fixture `tcc.log` files:
-```
-ts = tal_realloc_impl(&toksym_alloc, 0, sizeof(TokenSym) + len);
-...
-memcpy(ts->str, str, len);
-```
+| group | count | examples |
+|------:|------:|----------|
+| plain segfault during compile/link | 58 | `006-call-no-args`, `008-pointer-deref`, `011-struct`, `125-anon-union` |
+| `store(...); assert fail: 0`, then segfault | 44 | `002-arith`, `004-inc-dec`, `020-switch`, `133-for-continue` |
+| `assert fail: vtop[-1].r < VT_CONST && vtop[0].r < VT_CONST`, then segfault | 28 | `007-call-with-args`, `013-call`, `024-globals`, `132-tentative-bss-sizing` |
+| compile succeeds, generated program exits wrong | 3 | `019-zext-narrow`, `068-main-noret`, `101-char-escapes` |
+| `too many field init` diagnostic | 3 | `001-kitchen-sink`, `012-struct-ptr`, `053-init-struct-pos` |
+| `__builtin_va_start` warning, then segfault | 3 | `015-variadic`, `076-vararg-recv`, `079-vararg-deep` |
+| `field expected` diagnostic | 1 | `054-init-struct-desig` |
-Harness target: `make tcc-boot2 ARCH=aarch64` (see Makefile +
-`scripts/boot-build-cc.sh`) drives stage1-flatten on the host, runs
-cc.scm on the flattened TU inside the container, and feeds the P1pp
-into the standard `boot-build-p1pp.sh` pipeline. `TCC_TARGET` selects
-which tcc codegen target gets baked into the binary
-(default `X86_64`; use `ARM64` for aarch64); pick `ARCH` to match if
-you want generated programs to run natively in the per-arch container.
-
-## Resolved — offsetof-style const expr in `options_W[]` (line 18026)
-
-Done. `parse-const-cast` now accepts pointer-typed casts as a type
-re-tag (the integer offset rides through unchanged), and
-`parse-const-unary` has an `&` arm that runs a small postfix-style
-designator parser: a `(T *)0` head (with optional grouping parens or
-a `*` deref) followed by a chain of `->` / `.` field selectors. Field
-lookup reuses `%cg-find-field`, so anonymous union/struct members
-(needed by `struct Sym`-style layouts) work without extra plumbing.
-Scope is intentionally narrow — only the offsetof shape is admitted;
-no general pointer arithmetic in const-expr.
-Test: `tests/cc/126-offsetof-const.c` — covers `&((T*)0)->FIELD`,
-`&(*(T*)0).FIELD`, and the same form through anonymous union members.
-
-## Resolved — scratch pressure on `asm_instrs[]` (line 14527)
-
-Done. `parse-init-global` still returns a pieces list to
-`cg-emit-global`, but `%parse-init-array-list` now parses each array
-element as a unit, promotes that unit's pieces plus parser/pp/lex
-lookahead into main storage, rewinds scratch to a pre-unit mark, and
-continues with the outer accumulator in main. This is selected by
-initializer shape (file-scope/static positional aggregate), not by the
-symbol name.
-
-Before the fix, parse reached the `static const ASMInstr asm_instrs[] =
-{ ... };` table at line 14527 (~333 entries spanning lines
-14528-14860) and aborted with `scheme1: scratch exhausted` partway
-through. Per-element scratch growth measured then:
+The important shape is that 137 of 140 failures happen before the
+generated fixture binary runs. The dominant problem is still the
+compiled `tcc-boot2` while it is compiling/linking C input, not the
+runtime behavior of most generated test binaries.
-```
-elem 16 -> scratch 287 503 672
-elem 176 -> scratch 400 656 488
-delta 113 152 816 over 160 elements ~= 707 KB / elem
-```
+Working hypothesis: our compiler is miscompiling tcc itself. In this
+suite, `tcc-boot2` is a tcc binary produced by `cc.scm`; the failures
+look like that produced tcc is executing bad compiler/codegen logic and
+therefore emitting bad code, asserting, or crashing while compiling the
+fixtures. The host baseline below rules out the fixtures and expected
+files as the main source of the failures.
-Each row writes ~12 bytes of static data but consumed ~700 KB of
-scratch to do it. tcc.c entries are dense:
-
-```c
-{ TOK_ASM_cmpsb,
- ((uint64_t) ((((0xa6) & 0xff00) == 0x0f00)
- ? ((((0xa6) >> 8) & ~0xff) | ((0xa6) & 0xff))
- : (0xa6))),
- (((0x01 | 0x1000)) | ((0) << 13)
- | ((((0xa6) & 0xff00) == 0x0f00) ? 0x100 : 0)),
- 0, { 0 } },
+A stronger control is to compile the same ARM64 `tcc.flat.c` with
+Alpine gcc and use that gcc-built tcc to run the same `tests/cc`
+fixtures. That control is not perfectly green, but it is far healthier:
+
+```text
+gcc-built ARM64 tcc.flat.c: 126 passed, 27 failed
+cc.scm-built ARM64 tcc-boot2: 13 passed, 140 failed
```
-Current aarch64 run:
+So there are two layers of signal:
-```
-[cc] decl: line 14527 heap 50929348
-[cc] decl: line 14861 heap 61008644
-delta 10079296 over 333 rows ~= 30 KB / row
+- 27 failures reproduce even when tcc is built by gcc from the
+ flattened ARM64 source. Treat these as tcc/ARM64/flattened-source
+ baseline failures until proven otherwise.
+- 114 additional fixtures fail only with the `cc.scm`-built tcc. That
+ is the main evidence that our compiler is miscompiling tcc.
+
+## Host Baseline
+
+The `tests/cc` fixtures themselves are coherent under a host compiler.
+A temporary host harness was used to compile, run, and compare every
+fixture with plain host `cc`:
+
+```sh
+build/aarch64/.work/tests/tcc-cc/run-host-cc.sh
```
-The table now completes and parse advances to line 18026. The remaining
-row-scale heap growth is persistent `.data` output plus promoted
-pieces/metadata; it is no longer a scratch cap blocker.
-
-## Resolved — `static` tentative-def merge
-
-Done. handle-decl now records file-scope `int x;` / `static int x;`
-(no init, non-extern) as tentative defs (`defined?=#f`) and adds the
-name to a new `world-tentatives` list rather than emitting BSS at decl
-time. `cg-finish` walks the list and emits `.bss` for any tentative
-that didn't get a real definition, so two `static int gnu_ext;`
-followed by `static int gnu_ext = 1;` merges cleanly via the existing
-`sym-merge` (defined? wins). Test: `tests/cc/124-tentative-static.c`.
-
-## Resolved — anonymous union/struct members (`s->d`, `s->c`)
-
-Done. tcc.c's `struct Sym` uses three back-to-back anonymous unions
-(`union { long c; int *d; }` etc.) and accesses them as if they were
-direct members. `%cg-find-field` and `%find-field` now recurse into
-nameless struct/union members, returning a synthetic
-`(name ctype composed-offset)` triple. `%parse-init-local-struct-list`'s
-zero-pass also got an anon-aware `%anon-touched?` helper so a
-designator like `.a = 10` on a struct with anon-union members no
-longer gets clobbered by the trailing zero-fill.
-Test: `tests/cc/125-anon-union.c`.
-
-## Resolved — `sizeof EXPR` in const-expr context
-
-Done. `char buf1[sizeof file->filename];` (line 3867) and similar.
-The existing `parse-unary` sizeof handler already used cg
-snapshot/rewind to recover the operand's ctype without evaluating it;
-const-expr now does the same via `%const-sizeof-expr`. Both
-parens-form and bare-`sizeof EXPR` work.
-
-## Resolved — FP softening (cast-to dbl at line 4205)
-
-Done. `parse_number` declares `double d; d = 0;` which triggered the
-`cg-cast` FP rejection. `%cg-fp-reject!` is now a named no-op so
-fp ctypes flow through size-dispatched load/store and same-size casts
-as raw bit patterns. Real FP arithmetic is still wrong (binops emit
-integer ALU ops on the underlying bits), but tcc-boot2's runtime
-never executes its own float code paths when compiling float-free
-programs, so producing valid-but-semantically-wrong P1pp here is
-sufficient. Comment in cc.scm flags the call sites for any future
-target that needs real FP.
-
-## Resolved — `__attribute__` decl-spec at line 1628
-
-The cc now consumes GNU `__attribute__ ((...))` specs and discards
-them. Skip lives next to `eat-cv-quals!` in cc.scm; called from
-`parse-decl-spec` (prefix attributes) and `parse-decl-suf-cont`
-(trailing attributes after a declarator, e.g.
-`void foo(void) __attribute__((noreturn));`). Same softening pattern
-as floats / wide types — `noreturn`, `format`, `aligned`, etc. are
-not honoured semantically, just parsed away.
-
-## Resolved — parse-phase heap pressure
-
-The two earlier memory blockers (whole-TU heap explosion, single-decl
-scratch peak inside the `enum tcc_token` block) are both gone after
-the per-decl scratch arena (Phase 3) plus the static aggregate
-initializer unit rewind path ([CC-INIT-SCRATCH.md](CC-INIT-SCRATCH.md))
-plus the recent scope-bind alist / scratch reclamation work.
-
-Current full-file aarch64 run against
-`build/tcc/X86_64/tcc.flat.c` — parse + cg-finish complete:
+Current host baseline:
+```text
+HOST_CC=cc
+HOST_CFLAGS=-std=gnu11 -w
+153 passed, 0 failed
```
-[cc] phase=start: heap 1 225 052
-[cc] phase=slurp: heap 3 101 100 src-bytes 608 547
-[cc] decl: line 14527 heap 50 929 348
-[cc] decl: line 14861 heap 61 008 644
-[cc] decl: line 18024 heap 66 002 084
-[cc] decl: line 18800 heap 64 824 540 ; final decl
-[cc] phase=parse: heap 64 864 516
-[cc] phase=cg-finish: heap 90 674 020 out-bytes 6 489 215
+
+The gcc-built flattened-tcc control runs in the Alpine gcc image:
+
+```sh
+podman run --rm --pull=never --platform linux/arm64 \
+ -v "$PWD":/work -w /work boot2-alpine-gcc:aarch64 \
+ sh build/aarch64/.work/tests/tcc-cc/run-gcc-flat-tcc.sh
```
-Milestones from that run:
-
-| point | line | heap | Δ from start | note |
-|------:|-----:|-----:|-------------:|------|
-| start | - | 1 225 052 | - | runtime before slurp |
-| slurp | - | 3 101 100 | 1 876 048 | 608 547-byte source loaded |
-| before `asm_instrs[]` | 14 527 | 50 929 348 | 49 704 296 | enters large static table |
-| after `asm_instrs[]` | 14 861 | 61 008 644 | 59 783 592 | table completed |
-| through options tables | 18 024 | 66 002 084 | 64 777 032 | offsetof const-expr region |
-| end of TU | 18 800 | 64 824 540 | 63 599 488 | final decl reached |
-| post cg-finish | - | 90 674 020 | 89 448 968 | text/data buffers + 6.5 MB out |
-
-Observed rates:
-
-- Parse to EOF holds steady around ~64.8 MB above start for 608 547
- source bytes — i.e. ~110 bytes of resident state per source byte.
-- The `asm_instrs[]` table adds ~10.1 MB over 333 rows, about
- 30 KB / row after the streaming initializer fix.
-- cg-finish adds ~26 MB on top of parse and produces 6.5 MB of P1pp.
-
-Older prefix probes that end at clean top-level `};` boundaries
-(HEAP_CAP_BYTES = 256 MiB, SCRATCH_CAP_BYTES = 128 MiB):
-
-| line | bytes | heap after parse | Δ from start | KB / source byte |
-|-----:|-------:|-----------------:|-------------:|-----------------:|
-| 220 | 7 953 | 18 012 476 | 16 802 432 | 2.11 |
-| 280 | 9 795 | 18 885 492 | 17 675 448 | 1.81 |
-| 683 | 18 260 | 21 464 308 | 20 254 264 | 1.11 |
-| 880 | 22 111 | 22 650 284 | 21 440 240 | 0.97 |
-| 981 | 24 557 | 23 281 212 | 22 071 168 | 0.90 |
-| 986 | 24 630 | 23 306 676 | 22 096 632 | 0.90 |
-| 1612 | 40 943 | 31 186 500 | 29 976 456 | 0.73 |
-| 1627 | 41 626 | 31 481 060 | 30 271 016 | 0.73 |
-
-Marginal residency converges to roughly **0.9 KB / input byte** at
-the small-prefix scale and drops further across the enum block. The
-current full-file run confirms the 608 KB TU fits comfortably under
-the existing heap and scratch caps until the line 18026 const-expr
-coverage failure.
-
-The full 608 KB TU itself slurps to a heap of 3 101 100 bytes (~3 MB)
-— bytevector storage for the source plus runtime baseline.
-
-Heap delta minus the start-of-process baseline (~1.21 MB scheme1
-runtime + cc-init bufs at ~12 MB):
+Current result:
+```text
+tcc version 0.9.26 (AArch64 Linux)
+126 passed, 27 failed
```
-parse_heap - start_heap
-= persistent main-heap state introduced by parse
-= surviving roots (scope, tags, str-pool) + cg-text/data/bss bytes
+
+Those 27 failures break down as:
+
+- Compile/link segfaults: `001-kitchen-sink`, `015-variadic`,
+ `032-local-struct-desig`, `067-vararg-call`, `076-vararg-recv`,
+ `079-vararg-deep`, `084-struct-assign`, `096-fwd-struct`,
+ `097-vararg-many-named`, `099-init-zero-tail`,
+ `108-typedef-fnptr`, `109-typedef-anon`, `111-struct-ret-1word`,
+ `112-struct-ret-2word`, `113-struct-ret-3word`,
+ `114-struct-ret-many-args`, `115-struct-ret-3word-many-args`,
+ `116-struct-ret-vararg`, `117-compound-literal`,
+ `125-anon-union`, `129-extern-libp1pp`, `131-vararg-mixed`.
+- Runtime exit mismatches: `018-sext-narrow`, `068-main-noret`,
+ `102-cmpd-narrow`, `119-float-parse`, `128-cast-signedness`.
+
+Two fixture cleanups are part of that baseline:
+
+- `tests/cc/125-anon-union.c` explicitly initializes its local struct
+ before probing anonymous-union aliasing. Tests should not depend on
+ implicit zeroing of automatic locals.
+- `tests/cc/132-tentative-bss-sizing.c` returns distinct numeric exit
+ codes instead of calling `sys_write`/`strlen`. Plain `tests/cc`
+ fixtures should not need stdio/libc helpers.
+
+The cleaned fixtures also pass the regular aarch64 `cc` path:
+
+```sh
+NAMES='125-anon-union 132-tentative-bss-sizing' \
+ make test SUITE=cc ARCH=aarch64
+# 2 passed, 0 failed
```
-The cg-init bufs themselves account for ~12 MB of the start baseline
-(see %BUF-CAP-* in cc.scm). After amortizing them out, persistent
-parse state at the 1k-line scale is closer to **0.4 KB / input
-byte**.
-
-### History — what the earlier numbers looked like
-
-For posterity. Pre-Phase-3, parse-phase residency was roughly
-6.5 KB heap per source byte (1612-line cut: ~267 MB, just under cap;
-50 000 B head and full TU: heap exhausted). Post-Phase-3 dropped that
-to roughly 0.9 KB / byte at the steady state but left a single-decl
-*scratch* peak: the `enum tcc_token` block (lines 987–1612, 800+
-enum constants) overflowed even 128 MiB of scratch because
-`scope-bind!`'s `alist-ref` walk made cumulative per-decl scratch
-O(N²) in member count. The recent scratch / alist work makes that
-decl complete with parse heap at ~31 MB on the 1612-line cut.
-
-## Tracepoint instrumentation (`%trace` / `--cc-trace-emit`)
-
-See [DEBUG.md](DEBUG.md) — `CC_TRACE_EMIT=1` injects per-function-entry
-stderr probes; `m1-symbols.py lookup` resolves the printed addresses
-back to functions. `%trace` now saves/restores all exposed P1 registers
-(`a0..a3`, `t0..t2`, `s0..s3`) by borrowing stack space inside the
-current `%fn` frame, so manual probes can be inserted in live code
-without clobbering caller state.
-
-## Expected next-tier blockers (downstream of cc.scm)
-
-The semantic parser has covered every construct in this TU, and the
-large P1pp output now makes it through m1pp / M0 / hex2. The next likely
-walls are runtime/codegen mismatches:
-
-- **`tcc-boot2 -version` correctness**. Even when the toolchain
- produces an ELF, the runtime still has to walk through tcc's setup
- (string-table init, command-line parsing, output for `-version`)
- without tripping on cg semantics that pass the small tests but
- diverge from C in subtle ways.
-- **Struct layout / flexible-tail object correctness**. The current
- crash path is `tok_alloc_new` copying into `TokenSym::str`, so offsets
- around `TokenSym`, `TinyAlloc`, and related tcc structs are high-value
- targets for small focused tests.
-- **libc behavior under full tcc load**. The mes-libc subset is now in
- the link, but runtime helpers still need validation under tcc's actual
- allocation/string/token workloads.
-
-The end goal is milestone 4 in [CC.md §Validation milestones](CC.md)
-— "Compile tcc.c (under the tcc-mes defines) → tcc-boot2; verify
-`tcc-boot2 -version` runs."
-
-## libc — see [LIBC.md](LIBC.md)
-
-The unresolved externals in [LIBC.txt](LIBC.txt) are met by porting a
-curated subset of mes libc and adding a thin syscall-wrapper file that
-calls P1pp's `sys_*` labels directly. **musl is rejected** for this
-layer (built around gcc-only idioms — inline asm everywhere, TLS
-`errno`, weak/visibility attrs, `_Atomic`, IEEE math, dynamic linker —
-none of which survive cc.scm's subset).
-
-The work splits cleanly into two phases. **Phase A** is what makes
-tcc-boot2 itself link: cc.scm compiles the vendored mes libc subset
-into `libc.P1pp`, catm'd with `tcc.P1pp` to produce the tcc-boot2
-ELF. **Phase B** is what makes tcc-boot2 *useful* — tcc-boot2
-auto-appends `-lc` and resolves runtime helpers
-(`__divdi3`, `__floatundidf`, …) against `$LIBDIR/tcc/libtcc1.a` when
-linking the code it compiles. Both archives have to exist on disk or
-even hello-world won't link. Phase B uses tcc-boot2 itself as the
-compiler (mirrors live-bootstrap's `pass1.kaem` substituting tcc-boot2
-for tcc-mes); upstream tcc's `lib/libtcc1.c` is the source for the
-libtcc1.a side and is already pulled in by `stage1-flatten.sh`'s
-tarball unpack.
-
-The full implementation handoff (manifest of files to vendor, the four
-surgical patches, the new P1pp entry points, the Phase A / B build
-scripts, smoke tests, acceptance criteria) lives in
-[LIBC.md](LIBC.md).
-
-CC.md needs a follow-up edit: its "we link against the same `libc+tcc`
-archive MesCC uses" line is now stale.
+## Next Debug Targets
+
+Start with the earliest minimal failures in each dominant group:
+
+- `002-arith`: first `store(...); assert fail: 0` case. This is a
+ small arithmetic fixture and is likely the highest-leverage entry
+ point into the ARM64 tcc codegen path compiled by `cc.scm`.
+- `007-call-with-args`: first clear `vtop[-1].r < VT_CONST` assertion
+ on an ordinary call with arguments.
+- `006-call-no-args`: first plain segfault with a very small source.
+- `001-kitchen-sink` or `012-struct-ptr`: first incorrect tcc parser
+ diagnostics around aggregate initialization.
+- `019-zext-narrow`, `068-main-noret`, `101-char-escapes`: the only
+ current failures where `tcc-boot2` successfully emits and links a
+ binary but the binary returns the wrong status.
+
+Keep using `make test SUITE=cc ARCH=aarch64 NAMES=...` as the control
+path for fixture semantics, and `make test SUITE=tcc-cc NAMES=...` as
+the `tcc-boot2` acceptance path.