# Third-Party Test Results & Triage TODO

Batch run: 2026-08-14 (full 221/221 targets; supersedes the 2026-08-06
partial 199/221 run below)
Binary: rcc HEAD (third_party branch)

## Summary

| rc  | count | meaning                                                               |
| --- | ----- | --------------------------------------------------------------------- |
| 0   | 78    | pass                                                                  |
| 2   | 89    | build/compile failure                                                 |
| 1   | 12    | runtime/test failure (many are build-system: CC not respected)        |
| 124 | 29    | timeout (420 s) — most are large projects that build/test cleanly     |
|     |       | and just need more wall-clock time under this batch's per-test budget |
| 127 | 10    | missing tool (muon, lzip, scons, llvm-config, etc.)                   |
| 139 | 1     | SIGSEGV (box2d — confirmed a crash in rcc-compiled C code, see below) |
| 8   | 2     | project's own test-suite comparison failed (utf8proc, yyjson)         |

221 targets triaged this session (5 parallel scouts read every failing
log and classified it ENV / TIMEOUT-ARTIFACT / RCC-BUG / UNCLEAR — see
"Needs fixing" below for the RCC-BUG clusters). One methodological note
for future batch reruns: this sandbox's `/tmp` has a per-user tmpfs
quota: a stale 7.5G `/tmp/.Trash-1000` silently ate most of the headroom
partway through this session and caused several large projects
(test_rsync, test_muon, test_nginx, test_perl, test_qbe_simplecc) to
fail with "Disk quota exceeded" mid-build/mid-test — a sandbox artifact,
not an rcc regression. Cleaned up (`rm -rf /tmp/.Trash-1000`); a full
clean rerun of those five was interrupted by user request and still
needs to be redone to reconfirm their previously-documented "Fixed"
status holds (they were NOT re-broken by anything in this session —
each was mid-build/mid-test with no rcc error when the quota hit).

## File Layout

**⚠ False positives**: Many projects (lua, mruby, many cmake projects)
hardcode `CC=gcc` in their Makefiles and ignore the environment. The test
harness sets `CC=rcc` but the build system overrides it. Verify by checking
`strings <binary> | grep GCC` — if it says GCC, rcc wasn't used.

**Genuine rcc bugs found so far**:

### Fixed (2026-08-14, array-range designator / overflow builtins / ADX+mxcsr session)

- **GNU C designated array-range initializer on a struct member array**
  (`.field[LOW ... HIGH] = val,`) rejected with "expected specific
  operator" — parser.c. The top-level "array with braces" initializer
  path already supported `[LOW ... HIGH] = val` on a bare array target,
  but the `.member[N]` designator-_chain_ loops used by both
  `global_init_one()` (file-scope/`static`/`constexpr`) and
  `local_init_one()` (auto locals) only ever parsed the `[N]` step as a
  single constant index, with no `...` handling — so a range directly on
  a struct member array (as opposed to a whole-array target) fell
  through to "expected specific operator" on the `...`. Fixed by adding
  range detection to both chain loops: `global_init_one()`'s emits one
  relocation per covered index (each a fresh parse of the same value
  tokens, matching the pattern the top-level array path already uses);
  `local_init_one()`'s evaluates a non-brace value once into a temp
  local (avoiding re-running side effects) and assigns it to every
  covered element, or recurses per-element for a brace-enclosed value.
  Found via test_binutils/test_binutils_gccverify
  (`opcodes/i386-dis.c:9739`) and test_coreutils (`lib/utimecmp.c:344`).
  Regression test: `test/test_designator_array_range_member.c` (new,
  covers file-scope, `static` local, auto local, and single-evaluation
  of a side-effecting value), PASS at -O0..-O3 on x86-64, ARM64
  (qemu-aarch64) and mingw (wine).

- **`__builtin_{s,u}{add,sub,mul}{,l,ll}_overflow` family (18 names)
  entirely unrecognized** — `cg_builtins.c`, `rcc.h`. The type-generic
  3-arg `__builtin_{add,sub,mul}_overflow` / `__builtin_mul_overflow_p`
  were already implemented (codegen driven off the actual
  argument/result-pointer types), but the fixed-width/signedness
  family GCC also exposes (`__builtin_sadd_overflow`,
  `__builtin_uaddl_overflow`, `__builtin_umull_overflow`, etc. —
  confirmed genuine real GCC builtins via a direct `gcc -c` check) had
  no name registered at all, so the parser fell back to an ordinary
  implicit-declaration external call that produced a valid `.o`
  failing at link time with "undefined reference". Fixed by interning
  the 18 names and routing them through the exact same
  add/sub/mul-overflow codegen already used by the generic form — that
  codegen reads width/signedness from the actual argument and
  result-pointer types rather than the call's spelling, so no new
  codegen was needed, only the name-to-dispatch wiring. One subtlety:
  these 18 names are ordinary identifiers, not lexer keywords (unlike
  the generic `__builtin_*_overflow` names, which are registered in
  `keywords.gperf`) — interning them via the keyword-table-only
  `_BI()` / `keyword_interned()` helper silently returned NULL and
  never matched, so they use a new `_SI()` helper (plain
  `str_intern()`, the same hash-consing pool the lexer's non-keyword
  identifier path already feeds into). Confirmed blocking
  `test_libgit2` (`src/util/alloc.c`, `filebuf.c`, `fs_path.c`).
  Regression test: `test/test_builtin_overflow_family.c` (new, all 18
  variants plus the pre-existing generic form), PASS at -O0..-O3 on
  x86-64, ARM64 and mingw.

- **Missing SSE/assembler instruction coverage: ADCX/ADOX (ADX
  extension) and STMXCSR/LDMXCSR** — `x86_enc.c`/`.h`, `asm.c`. Real
  GNU inline-asm/`.S` mnemonics rejected with "unknown x86
  instruction". ADCX/ADOX had no encoder at all; added
  `x86_adcx_rr`/`x86_adcx_rm`/`x86_adox_rr`/`x86_adox_rm`
  (66/F3 `[REX.W]` `0F 38 F6 /r`) and wired them into `asm.c`'s
  dispatch before the existing `ALU_OP("adc", ...)` prefix match,
  which otherwise silently swallowed "adcx" as a bare "adc" (`strncmp`
  prefix match, not exact) and mis-encoded it as a 2-byte ADC — also
  added "adcx"/"adox" to the operand-size-suffix exemption list so the
  32-bit register forms (`%eax`) weren't forced to 64-bit width.
  STMXCSR/LDMXCSR's encoders already existed (used by the
  `__builtin_ia32_stmxcsr`/`__builtin_ia32_ldmxcsr` compiler-intrinsic
  path) but were never wired into the inline-asm mnemonic dispatch;
  also fixed a spurious REX prefix byte both encoders emitted whenever
  the memory operand's base/index register was RSP/RBP/RSI/RDI
  (`maybe_rex()`'s shared helper takes the raw register number rather
  than a needs-REX bool in its B/X slots, over-triggering on any of
  those four registers — harmless at runtime, a REX byte with
  all-zero bits is architecturally a no-op, but non-minimal versus
  real assemblers; fixed locally in these two encoders only, the
  shared `maybe_rex()` helper itself has the same latent issue at
  roughly 60 other call sites, out of scope here). All forms verified
  byte-for-byte identical to `as`/objdump reference output. Found via
  `test_libgc`/`test_rvvm` (STMXCSR/LDMXCSR) and `test_libressl`
  (`bn/arch/amd64/*.S`, ADCX/ADOX). Regression test:
  `test/test_asm_adx_mxcsr.c` (new; ADX flag-readback itself is not
  checked — this session's test host has a documented AMD Zen/Zen+
  erratum where ADCX/ADOX's flag output is unreliable even for
  gcc-compiled code, confirmed by reproducing the same anomaly with a
  hand-assembled `as` binary and a plain `gcc`-compiled one; only the
  deterministic arithmetic sum is checked), PASS at -O0..-O3 on x86-64
  (ADX/mxcsr are x86-only, guarded out on ARM64; verified the guard
  compiles clean and the rest of the suite is unaffected on ARM64 and
  mingw).

- **Remaining SSE/assembler instruction coverage: PSHUFLW/PSHUFHW and
  PSLLD/PSRLD immediate-shift form** — `x86_enc.c`/`.h`, `asm.c`.
  PSHUFLW/PSHUFHW encoders already existed (used by the
  compiler-intrinsic path) but were never wired into `asm.c`'s
  inline-asm mnemonic dispatch. PSLLD/PSRLD had only a
  register-count-shift encoder (`x86_pslld_r`/`x86_psrld_r`), not the
  immediate-shift "Group 13" form (`66 0F 72 /ext ib`) real
  hand-written SIMD assembly actually uses; added `x86_pslld`/
  `x86_psrld`, mirroring the existing Group 14 (`0F 73`) helper
  backing `pslldq`/`psrldq`/`psllq`/`psrlq` — encoded locally (not via
  the shared `maybe_rex()`) to avoid the same spurious-REX-byte issue
  noted above for XMM4-XMM7. All forms verified byte-for-byte
  identical to `as`/objdump reference output, including the REX.B case
  for an XMM8-15 destination. Found via `test_nettle`. Regression
  test: `test/test_asm_sse_shift_shuffle.c` (new, byte-verification
  style matching `test_asm_aesni_sse2.c`'s convention — a
  functional/runtime test using vector-typed C locals bound to `"x"`
  inline-asm constraints hit a separate, pre-existing, unrelated bug:
  the `"x"`/`"=x"` xmm-register constraint doesn't correctly bind a
  `vector_size` C local to the asm's register operand at all, even for
  a bare `movaps` copy — not attempted this session, out of scope,
  see "Needs fixing" below), PASS at -O0..-O3 on x86-64, ARM64 and
  mingw.

### Fixed (2026-08-14, F16C intrinsics / unknown-flag acceptance session)

- **F16C half-precision convert intrinsics not implemented**
  (`__builtin_ia32_vcvtph2ps`/`vcvtph2ps256`/`vcvtps2ph`/`vcvtps2ph256`)
  — type.c, x86_enc.c/.h, cg_vectors.c. None of these four names start
  with `"cvt"` (the leading `v` of `vcvtph2ps` defeats the
  `memcmp(n,"cvt",3)` prefix check every other `cvt*` builtin's return
  type is classified through) and none end in a b/w/d/q lane-size
  letter, so `type.c`'s `ia32_builtin_ret()` fell through to its
  `ty_int` catch-all instead of the real vector return type — which
  then tripped a second, unrelated, still-open codegen bug: a
  vector-typed local declaration initialized **without** a cast from an
  int-returning call whose own argument is itself a vector
  (`__v8hi H = __builtin_ia32_vcvtps2ph(A, imm);`, no leading
  `(v8hi)`/`(__m128i)`) mis-parses as "expected an expression" instead
  of surfacing the real type mismatch — reproduces even for an
  already-working intrinsic like `addps256` and is unrelated to F16C
  specifically; not fixed this session (real GCC/clang headers always
  wrap builtin calls in a cast, so it never blocks real-world header
  usage — see "Needs fixing" below). Two new VEX encoders
  (`x86_vcvtph2ps`/`x86_vcvtph2ps256`/`x86_vcvtps2ph`/`x86_vcvtps2ph256`)
  were added and independently verified byte-for-byte against real
  `gcc -mf16c` objdump output rather than transcribed from the SDM by
  hand — good thing, since doing so caught a second, unrelated
  pre-existing bug along the way: `vex3()`'s `W` parameter is the
  **inverse** of the real VEX.W bit (by established convention across
  every existing caller in this file, e.g. `x86_vpermq` passes `W=0` to
  produce genuine VEX.W=1 — verified against gcc's own `vpermq` bytes),
  which is invisible for every other VEX instruction wired up so far
  because they all happen to be W-ignored (WIG); VCVTPH2PS/VCVTPS2PH
  are genuinely W0-only, so getting the (inverted) parameter backwards
  the first time around SIGILL'd instead of silently working. Found
  via test_brotli, whose `backward_references.c` transitively pulls in
  `<immintrin.h>` -> `<f16cintrin.h>` (parsed unconditionally by GCC's
  headers regardless of `-mf16c`, matching every other ISA-extension
  header). Regression test: `test/test_f16c_intrinsics.c` (new),
  verified against real `gcc -mf16c` output; PASS at -O0..-O3.
  test_brotli's own ctest suite: 12/12 passed. The same
  `f16cintrin.h:62` parse failure was independently hit by test_sdl3
  and test_libflac in this session's batch triage — expected to be
  fixed too by the same change (not individually reverified).

- **Several common, legitimate GCC/clang flags hard-error instead of
  being accepted** (`-Os`, `-ggdb`, `-fno-builtin[-NAME]`,
  `-fno-common`/`-fcommon`) — main.c. rcc already tolerates flags it
  doesn't implement by warning-and-ignoring them, **except** when the
  same command line also passes `-Werror`, which (correctly, by
  design) promotes any **truly** unrecognized flag to a hard error; the
  bug was that these four are not exotic or unknown to any real
  compiler — `-Os`/`-ggdb` are ordinary optimization/debug-format
  levels, `-fno-builtin`/`-fno-common` are extremely common portability
  flags — so they belong in the same "recognized, silently accepted"
  bucket as `-Wall`/`-fwrapv`/`-Wno-*`, not the "genuinely unknown"
  bucket that `-Werror` is allowed to escalate. `-Os`/`-Ofast`/`-Og`
  now alias to `-O1` (rcc has no separate size- or fast-math-aware
  optimization pass); `-ggdb[123]` aliases to `-g` (rcc's debug info is
  already gdb-oriented, no separate format to select); `-fno-builtin`
  and `-fno-common`/`-fcommon` are accepted as true no-ops (rcc's
  `__builtin_*` recognition is already strictly name-prefix-gated, and
  rcc always emits plain BSS for uninitialized globals, never COMMON
  symbols, matching `-fno-common`'s own semantics regardless of which
  of the pair is requested). Found via this session's third-party
  batch triage: test_micropython, test_mpack (`-Os`), test_jemalloc
  (`-fno-builtin`), test_mongoose (`-fno-common`), test_cello,
  test_valkey, test_rvvm (`-ggdb`) all hard-errored specifically on
  build steps that also happened to pass `-Werror`. Not individually
  reverified against each real project this session (time-boxed); the
  flag-acceptance behavior itself is covered by a direct
  compile-and-check in this session's verification (all five flags
  compile cleanly with `-Werror` now).

### Fixed (2026-08-14, C `defer` session)

- **C `defer` statement not implemented at all** (WG14 N3199 / TS 25755,
  clang's experimental `-fdefer-ts` flag name) — parser.c, codegen.c,
  opt.c, main.c. `defer { ... }` registers a statement to run, LIFO
  with every other pending cleanup, on every exit from the enclosing
  scope (fall-through, `return`, `break`, `continue`, `goto`).
  Recognized unconditionally (`defer` followed directly by `{` can
  never be valid C otherwise); the brace-less single-statement form
  (`defer if (f) fclose(f);`, nob.h's own preferred style) is genuinely
  ambiguous with a call to a function literally named `defer`, so it
  stays gated behind `-fdefer-ts`. Implemented as a zero-storage marker
  on the same `locals` chain `__attribute__((cleanup(...)))` already
  uses, inheriting its existing LIFO/return/fall-through threading.
  Found via test_nob (https://github.com/tsoding/nob.h), which uses
  `defer` throughout its file-I/O helpers. Three stacked bugs found and
  fixed along the way, each with its own regression case in the new
  test/test_defer.c: (1) a defer body that itself calls a function
  clobbered an already-computed scalar return value — codegen placed
  the return value in the ABI return register(s) **before** running
  cleanup; fixed by spilling to a scratch stack slot across cleanup and
  reloading after. (2) a **later** top-level declaration after the
  `defer` (e.g. a loop-local variable) silently excluded the defer
  marker from every subsequent `return`'s own cleanup range while the
  shared function epilogue also skipped it, so the defer body never
  ran at all — found via test_nob's own `nob_write_entire_file()`
  (`defer if (f) fclose(f);` followed by a `while` loop then
  `return true;`, so the file was never actually flushed/closed);
  fixed by advancing the same bookkeeping pointer
  (`current_fn_scope_locals`) past a defer marker that ordinary
  top-level declarations already advance past. (3) a function called
  **only** from inside a `defer` body was invisible to
  `opt.c`'s dead-code-elimination reachability scan (the defer body's
  Node lives solely in `LVar.defer_stmt`, reached only via codegen's
  own dedicated call at each scope-exit site, never as a child of the
  function's ordinary body Node tree the DCE walk scans) and was wrongly
  omitted at `-O1` and above, leaving a real call site with no
  definition to link against; fixed by also scanning every live
  function's locals chain for a `defer_stmt` during the DCE
  reachability walk. Verified: `test/test_defer.c` (new) passes at
  -O0/-O1/-O2/-O3 on x86-64, and under qemu-aarch64/wine (mingw)
  emulation; full `make check-all` clean (0 failed) on all three
  targets; test_nob's own test suite (12/14 `tests/*.c`, excluding a
  Win32-only test and one with unstable directory-listing order) builds
  and runs to completion under `rcc -fdefer-ts`.

### Fixed (2026-08-13, wide `_BitInt(N>64)` session)

- **`_BitInt(N)` with N > 64 silently truncated to 64 bits** (codegen.c,

### Fixed (2026-08-13, decimal `_Decimal32/64/128` session)

- **`_Decimal32/64/128` were aliased to float/double/long double** (parser.c,
  codegen.c, type.c, main.c, lib/libdfp/) — literals were converted to
  binary doubles at lex time, so `0.1dd` lost its exact decimal
  representation and decimal arithmetic silently used binary FP. Now they
  are real IEEE 754-2008 decimal floating-point types with the BID
  (binary-integer-decimal) bit encoding: `TY_DECIMAL32/64/128` kinds,
  `df/dd/dl` and C23 `d32/d64/d128` literal suffixes folded exactly at
  compile time via the bundled libbid runtime, and every operation
  (add/sub/mul/div, comparisons, int/float/decimal casts, negation,
  function args/returns) lowered to `__bid_*3`/`__bid_*2` runtime calls —
  the same symbols GCC and kefir use. The runtime is **libdfp 1.0.17's
  libbid core** (LGPL-2.1, same license as rcc) vendored into
  `lib/libdfp/` with a plain bit-pattern wrapper layer
  (`rcc_dec_rt.c`, no `_Decimal` C types so it compiles where the host
  compiler has no decimal support, e.g. aarch64 gcc), built per target
  into `lib/libdfp.a` by the Makefile, and auto-linked by rcc's native
  linker for decimal TUs (the driver injects the archive path; a system
  libdfp.so would use GCC's XMM-based ABI, incompatible with rcc's
  GP-register ABI for the same symbol names). Compile-time literal folding
  calls `__bid*_from_string` linked into rcc itself. Function args/returns
  follow the `__int128`-like two-GP-register convention for decimal128 and
  single GP registers for decimal32/64 (matching rcc's internal ABI).
  Regression test: `test/test_decimal.c` (7 sub-tests), PASS at
  -O0..-O3 on x86-64 and via qemu on ARM64 (where the native linker's
  missing `R_AARCH64_LDST128_ABS_LO12_NC` reloc now correctly fails the
  link and falls back to the external aarch64 gcc + bundled libdfp.a,
  which links and runs correctly). `__STDC_IEC_60559_TYPES__` /
  `__STDC_DEC_FP__` feature macros and glibc `%Hf/%Df/%DDf` printf hooks
  are not yet wired (decimal printf not supported).

- **`_BitInt(N)` with N > 64 silently truncated to 64 bits** (codegen.c,
  type.c, parser.c, main.c, src/bitint_rt.c) — before this fix every
  `_BitInt(N>64)` operation went through the scalar 64-bit path: operands
  were truncated (e.g. `x << 100` produced 0, 128-bit+ values compared
  wrong), because `gen()` dispatched only `TY_INT128` to the wide-int
  slot path and left `TY_BITINT` on the scalar path. Now wide `_BitInt`
  values live in `size`-byte stack slots and every op (add/sub/mul/div/
  mod, shifts, bitwise, compare, casts, truthiness, function args/returns)
  is a call to a per-TU `static` runtime helper ported from slimcc's
  `bitint.c` (`src/bitint_rt.c`, MIT: Copyright (c) 2019 Rui Ueyama,
  Copyright (c) 2023-2026 Hsiang-Ying Fu, embedded via `tools/embed-c.sh`
  as `src/bitint_rt.h` and self-hosted: rcc preprocesses+parses its own
  embedded runtime into any TU that uses wide `_BitInt`, so no link-time
  runtime object is needed and it is target-correct on x86-64, ARM64 and
  mingw). ABI: `_BitInt(65..128)` shares the `__int128` two-GP-reg /
  RAX:RDX (X0:X1) convention; `_BitInt(N>128)` uses the large-struct
  hidden-retbuf/pointer convention. Regression test:
  `test/test_wide_bitint.c` (9 sub-tests), PASS at -O0/-O1/-O2/-O3 on
  x86-64 and via qemu on ARM64, matching gcc reference output. ARM64
  specifics fixed along the way: physical-reg base misuse in
  `emit_bitint_call` save/restore (`arm64_str_uoff` expects VRegs),
  VReg vs physical reg confusion in `emit_bitint_copy_bytes`, and a
  spill-slot depth leak (save pushed a slot, restore never popped →
  stale `spill_depth` made the next function spill to offset 0, i.e. the
  saved frame-pointer slot; `spill_depth` is now also zeroed at function
  entry). `test_c23doku` stays skipped (arbitrary-precision 11163-bit
  bignum workload; the runtime is width-agnostic but that project is not
  a target).

### Fixed (2026-08-07, blosc2 session)

- **Inline-asm multi-output register clobber** (codegen.c) — a
  multi-output `asm()` using x86 fixed-register constraints (e.g.
  `"=a"`/`"=b"`/`"=c"`/`"=d"` for `cpuid`) could silently lose one
  output's value. codegen.c saves/restores an output's _address_
  register around the asm to protect it from being clobbered by the
  asm itself, but address-register allocation is independent per
  operand — operand j's address can land in the exact physical
  register operand i's own `"=b"` constraint targets. Popping
  operand j's saved address back into `%rbx` after the asm executed
  silently overwrote operand i's still-unread real result before the
  store-back loop could read it, with no diagnostic. Fixed by
  capturing every fixed-register output into a scratch vreg
  immediately after the asm executes, before any address-register
  restore runs.
  → found via blosc2's `blosc_get_cpu_features()` (four-output
  `cpuid` queries via `__builtin_cpu_supports()`)
- **Function-declarator parameter names leaked into file scope**
  (parser.c) — a function declarator's parameter names, from either
  a bare prototype or a full definition, were pushed onto the
  persistent file-scope `locals` list via `parse_params()`/`new_var()`
  but never cleared once the declarator finished (only the transient
  `current_fn_scope_locals` snapshot was reset). `find_var()` checks
  `locals` before falling back to globals/enum constants (required
  so a real local shadows a same-named global), so any later
  top-level identifier sharing a name with an earlier parameter —
  even a single letter like `f` — silently resolved to the stale,
  wrongly typed phantom parameter instead of its own declaration.
  Reproduced standalone with `int proto(const char *f); enum K { e,
f, g = f };` on the clean tree (pre-existing, not a regression from
  this session's other changes). Caught by torture test
  `c23-tag-enum-7` once a synthetic-prelude `__builtin_cpu_supports
(const char *f)` shadowed `enum K`'s own `f`.
- **`__builtin_cpu_supports("feature")`** (parser.c synthetic
  prelude, preprocess.c macro alias) — runtime CPU-dispatch compiler
  builtin used by several perf-sensitive libraries' SIMD-path
  selection. Implemented via a real `cpuid`-querying function
  injected into the x86-64 synthetic prelude (`static
__always_inline__` to avoid multi-TU link collisions).
- **SSE2 gaps**: `_mm_shufflelo_epi16`/`_mm_shufflehi_epi16`, the
  full `_mm_unpacklo`/`_mm_unpackhi_epi{8,16,32,64}` interleave
  family, `_mm_packs_epi16`/`_mm_packus_epi16`/`_mm_packs_epi32`
  saturating packs (emmintrin.h); `__m64` support via
  `_mm_storeh_pi`/`_mm_storel_pi` (xmmintrin.h).
- **SSSE3**: new `include/tmmintrin.h` (rcc had none) — everything
  expressible as plain lane-wise C arithmetic (abs/sign/hadd/hsub/
  alignr/maddubs/mulhrs) implemented via
  `__attribute__((vector_size))`; `_mm_shuffle_epi8` via a new
  `__builtin_ia32_pshufb128` encoder + dispatch (x86-only,
  x86_enc.c/h + codegen.c's `gen_vector_binary_builtin`).
  → unblocks: bearssl, blosc2, libflac (`_mm_shuffle_epi8`)
- **`stdint.h` `ptrdiff_t` typedef mismatch** — a pre-existing bug:
  rcc's own `stdint.h` typedef'd `ptrdiff_t` as `long`, conflicting
  with `<stddef.h>`'s correct `long long` once both got included in
  the same TU ("conflicting types for `ptrdiff_t`").

New regression tests: `test_asm_multi_output_clobber.c`,
`test_proto_param_scope_leak.c`, `test_sse2_unpack_pack.c`,
`test_m64_storehl_pi.c`, `test_ssse3_shuffle.c`,
`test_stdint_ptrdiff_conflict.c` (the last two guard their x86-only
portions with `#if !defined(__aarch64__) && !defined(_M_ARM64)`,
matching `test_ia32_pause.c`'s existing convention, so ARM64/macOS CI
keeps the portable coverage without failing on the x86-only bits).

**Verified**: c-blosc2 configures, builds (blosc + all
plugins/codecs), links, and its CTest suite runs cleanly (2100+
tests, no crashes, no hangs). One residual gap: a handful of
SSSE3-dispatch bitshuffle variants stay compiled out because CMake's
`check_c_compiler_flag(-mssse3)` reports "unsupported" — rcc
warns-and-ignores unrecognized `-m*` flags rather than erroring like
GCC/Clang would, which is what `check_c_compiler_flag` relies on to
detect support. This is a separate, deliberate rcc driver behavior
(silently accepting instruction-set hints it doesn't need), not
addressed this session. TCC 118/118, Unit 170/170, Torture 3605/3609
(100%), Dg-error 34/34, Link 4/4.

### Fixed (2026-08-07, continued)

- **Prototype/definition redeclaration not diagnosed** (parser.c) — rcc
  silently accepted incompatible re-declarations of the same function
  within one TU (`extern int f(int); int f(char x) {}`; `int rand(void);`
  redeclared `int rand(int n);`). Now:
  - Errors on a real type mismatch, at _any_ redeclaration point (not
    just the definition) — needed because glibc's own `<stdlib.h>` +
    `int rand(int n);` conflict is two plain declarations, not a
    declaration-then-definition.
  - Distinguishes explicit `(void)` (zero params) from K&R-style empty
    `()` (unspecified params, compatible with anything) via a new
    `Type::is_void_params` flag — both otherwise parse to the same
    empty `param_types` list, so redeclaring `int f(void)` as `int
f(int)` must error while `int f(); int f(int a) {...}` must not.
  - Warns (only with `-W`) on qualifier-only differences (`int
x[const 5]` vs `int (*const x)`) instead of erroring — these are
    the _same_ type per C, not a conflict.
  - Skips `extern __inline__` glibc fortify wrappers, old-style/K&R
    definitions, and struct/union-typed parameters (compared via
    `type_equal()`'s existing pointer-identity rule, see below).
    → unblocks: curl (configure's
    `CURL_CHECK_COMPILER_PROTOTYPE_MISMATCH` probe)
- **`type_equal()` struct/union pointer-identity check was too strict**
  (parser.c) — `declarator_params()` intentionally shallow-copies each
  parameter's `Type` node (`*pt = *pty`), so a struct/union parameter
  re-parsed from a second declaration was never pointer-identical to
  the first, even more so when nested inside a function-pointer
  parameter's own parameter list (copied _again_). Compare
  `members` (set once, when the tag is defined, and preserved
  unmodified by the shallow copy) instead of raw identity.
  → unblocks: mruby (`mrb_get_values_at`'s `mrb_value (*func)(...)`
  parameter false-flagged as a prototype conflict by the fix above)
- **glibc `_FORTIFY_SOURCE` `_chk`/`_chk_warn` builtins were bare
  object-macros** (preprocess.c) — `#define __read_chk read` etc.
  (added in `de077b2c` so real call sites from glibc's
  `__glibc_fortify` expansion resolve correctly) also fired on the
  _declaration_ of `__read_chk` itself in
  `<bits/unistd-decl.h>` (`extern ssize_t __read_chk(int, void*,
size_t, size_t);`, 4 params), textually producing `extern ssize_t
read(int, void*, size_t, size_t);` — 4 params, conflicting with
  `<unistd.h>`'s real 3-param `read` once the prototype check above
  started catching it. Converted all 32 of these (bare and
  `__builtin_`-prefixed, plus `_warn` siblings) to function-like
  macros that drop the trailing bufsize/buflen parameter: real calls
  still forward correctly, and the declaration's parameter list
  shrinks in step, keeping arity in sync automatically.
  → unblocks: curl, and any `_FORTIFY_SOURCE=2 -O1+` build
- **`include/assert.h` unconditionally included `<stdbool.h>`** — real
  glibc `<assert.h>` does not; this leaked `bool`/`true`/`false` as
  _macros_ into any TU that merely asserted, breaking `##`-pasted
  identifiers built from those names (e.g. flatcc's lexer generator:
  `#define lex_kw_match(kw) ... tok_kw_##kw`, called as
  `lex_kw_match(bool)` — `bool` got macro-expanded to `_Bool` _before_
  the paste per the `#`/`##` non-expansion rule already being honored
  correctly elsewhere, producing `tok_kw__Bool` instead of
  `tok_kw_bool`). Removed the include.
  → unblocks: flatcc (`keywords.h:19: error: undeclared variable
tok_kw__Bool`)
- **Compile-time constant folding of float subexpressions cast to an
  integer truncated too early** (parser.c, `eval_const_expr`) —
  `ND_FNUM` truncated each float literal to `long long` _at the leaf_,
  so `(size_t)(0.7f * 256.0f)` folded as `(long long)0.7f *
(long long)256.0f = 0 * 256 = 0` instead of `(long long)(0.7f *
256.0f) = 179`. Added a float-aware sibling evaluator
  (`eval_const_fexpr`, `long double` throughout) that
  `eval_const_expr` delegates to for any floating-typed subexpression,
  converting to an integer only once, at the actual cast boundary.
  → unblocks: flatcc — its hash-table load-factor threshold
  (`_flatcc_refmap_above_load_factor`) was permanently 0, so the
  resize loop's exit condition could never be met; `buckets *= 2`
  looped forever (eventually wrapping to 0 and spinning), hanging
  `monster_test` at 100% CPU the first time the table needed to grow.
- **`__builtin_ia32_pause`/`mfence`/`lfence`/`sfence` unimplemented**
  (preprocess.c) — real GCC/clang implement these as genuine compiler
  builtins requiring no header and no linkable symbol; code (curl's
  bundled curlx headers) calls them directly without going through
  `<emmintrin.h>`'s `_mm_pause()`. Added as inline-asm
  function-like macros (x86-only, gated on `!ARCH_ARM64`).
  → unblocks: curl (`undefined reference to '__builtin_ia32_pause'`
  at link time)

Regression tests: `test/test_err_proto_conflict.c`,
`test/test_proto_fnptr_struct_param.c`,
`test/test_proto_void_params.c` + `test/test_err_proto_void_params.c`,
`test/test_fortify_chk_arity.c`, `test/test_const_float_fold.c`,
`test/test_ia32_pause.c`. Full suite verified after each fix: TCC
118/118, Unit tests 163/163, Torture 3605/3609 (100% of non-skipped),
Dg-error 34/34, Link 4/4.

### Fixed (2026-08-08, httpparser session)

- **`-funroll` (opt.c) aliased duplicated labels across unrolled loop
  copies** — a `for` loop with a constant trip count and a `goto`/label
  pair in its body (e.g. `for (type_both = 0; type_both < 2;
type_both++) { ...; if (parser.upgrade) goto test; ...; test: ...; }`)
  got unrolled into N independent code copies via `clone_expr()`, a
  shallow per-field AST copy that left every copy's `ND_LABEL` with the
  _same_ `label_name`. codegen.c resolves both `goto` and `&&label` by
  formatting one symbolic name, `.L.label.<enclosing-fn>.<label_name>`
  — since every unrolled copy lives in the same enclosing function, all
  copies collided on that one symbol, so a later copy's `goto` bound to
  an _earlier_ copy's already-emitted address instead of its own,
  not-yet-emitted one. Taking that branch resumed execution mid an
  earlier copy with the later copy's live registers/stack state, which
  fed back into the earlier copy's own loop-continue path — corrupting
  the _enclosing_ scan's own induction variables and turning a bounded
  double loop into one that ran roughly 25x too many iterations before
  the test harness's 60s timeout caught it. Reproducible at any `-O2`+
  `-funroll` build regardless of target OS (identical AST-level bug,
  not codegen-backend-specific). Fixed by collecting the label names a
  loop body defines before unrolling and suffixing each unrolled copy's
  matching `ND_LABEL`/`ND_GOTO`/`ND_LABEL_VAL` nodes with a per-copy
  tag, so each copy's `goto`/`&&label` only ever binds to that same
  copy's own `label:` — labels the body merely jumps to _outside_ the
  loop (e.g. a shared `error:`) are left untouched since only names the
  loop itself defines are collected.
  → unblocks: test_httpparser (`test_scan()`'s `for (type_both =
0; type_both < 2; ...)`, found via the request-scan phase's upgrade
  test messages)

New regression test: `test/test_unroll_label_alias.c` — a bounded,
non-hanging reproduction (an escape-hatch counter breaks out after 20
bounces instead of spinning like the real bug) that fails fast
(`assert`) on the unfixed compiler at `-O2`/`-O3` and passes at every
optimization level once fixed. Full suite verified: TCC 118/118, Unit
tests 171/171 (also verified separately at `-O2`), Torture 3605/3609
(100% of non-skipped), Dg-error 34/34 — identical to baseline, plus
confirmed clean on the mingw and arm64 cross targets.

### Fixed (2026-08-08, test_bash session)

- **`eval_const_expr()`'s `ND_MEMBER` fold treated any _global_ with a
  literal initializer as compile-time-constant forever** (parser.c) — the
  fold's guard was `root_var->is_constexpr || !root_var->is_local`: for
  a plain (non-`const`) global struct/union, `!is_local` is true and a
  static initializer sets `has_init`, so a `global.member` read folded
  to the value baked into the _initializer_ — permanently, for every
  later read in the translation unit — even though an ordinary mutable
  global is written to at runtime throughout the program. Once a branch
  like this folds away, no later write can ever bring it back.
  Found via bash's `struct dstack dstack = { NULL, 0, 0 };` (parse.y):
  `current_delimiter(dstack)` (`dstack.delimiter_depth ? ... : 0`) folded
  to a permanent `0`, so every `if (current_delimiter(dstack) ==
'\'') ...`-style quote-tracking check throughout the hand-written
  parser always took the "not quoted" branch. That broke the alias-
  recursion guard in `shell_getc()` badly enough that alias expansion
  never terminated: `shopt -s expand_aliases; alias command=command;
eval 'command true'` spun forever (`test/third_party/test_bash`'s own
  `tests/alias4.sub`, run by its `make test`). Reproducible at any
  `-O1`+ build (CTFE only runs there); `-O0` passed, which is what made
  this a genuine miscompile rather than a bash logic bug — confirmed by
  diffing rcc-built bash's `alias4.sub` output against real bash's
  (identical after the fix). Root-caused by tracing which `ND_IF`s
  opt.c's dead-branch elimination actually folded during bash's build
  (a temporary `getenv("RCC_TRACE_FOLD")`-gated trace in the fold site)
  and finding `current_delimiter(dstack) == '\''` folding to a constant
  `0` despite `dstack` never being declared `const`.
  Fixed by requiring genuine immutability: `is_constexpr` (real
  `constexpr` declarations and constant-valued compound literals), or a
  non-local variable that is _also_ `const`-qualified — a plain mutable
  global with a literal initializer no longer qualifies.
  → unblocks: test_bash's `run-alias` (was hanging)

New regression test: `test/test_const_fold_mutable_global.c` — a
mutable global struct read through an `if` before and after a runtime
write (must observe the write), plus a genuinely-`const` global struct
read the same way (must still constant-fold, guarding against
regressing the legitimate case). Fails on the unfixed compiler at
`-O1`/`-O2`/`-O3` (passes at `-O0`, where CTFE doesn't run at all);
passes at every level once fixed. Full suite verified: 0 failed
(Linux native `--all`, Torture 3605/3609 = 100% of non-skipped); Unit
tests 172/172, also verified separately at `-O2`.

### Fixed (2026-08-08, continued — small-struct return ABI)

- **SysV x86-64 small-aggregate return: rcc always used a hidden return
  pointer for every struct/union return, regardless of size** (codegen.c)
  — `has_hidden_retbuf` / the prologue's `param_index` / `has_retbuf`
  (three near-duplicated classification sites) all treated _any_
  `TY_STRUCT`/`TY_UNION` return type as needing a hidden pointer
  argument. The real ABI only requires that for aggregates >16 bytes (or
  ones that don't classify as all-INTEGER/all-SSE eightbytes); a struct
  ≤16 bytes with no floating member returns in RAX (first eightbyte) and
  RDX (second eightbyte, if >8 bytes) with **no** hidden pointer at all.
  Every rcc-compiled caller and callee agreed with each other (so pure
  rcc-to-rcc code, single- or multi-TU, never showed a symptom), but
  calling any _external_ (non-rcc-compiled) function with this return
  shape silently passed a phantom hidden-pointer argument the real
  callee never expects — shifting every real argument into the wrong
  register — and then read the "return value" out of a buffer the
  callee never wrote to.
  → found via bash's own arithmetic evaluator (`expr.c`), which computes
  every `%`/`/` inside `$(( ))` via glibc's `imaxdiv()` (`intmax_t
quot, rem;`, 16 bytes, all-integer): confirmed in isolation first —
  `div()`/`ldiv()`/`lldiv()`/`imaxdiv()` all returned `{0, 0}`
  regardless of arguments — then traced back to bash: `run-arith`
  printed wrong numbers and `run-arith-for` hung
  (`tests/arith-for.tests`'s post-bash-4.2 case, a `for ((...))` whose
  termination depends on `i % 9`, never terminated once `%` always
  returned 0). Root-caused with a minimal `imaxdiv()`-calling
  reproduction once the earlier `-O0` bisection (which ruled out
  `y.tab.c`/`expr.c`/`execute_cmd.c`/`subst.c` optimization artifacts,
  since this bug fires at _every_ optimization level including `-O0`)
  pointed away from a CTFE-style miscompile and toward a calling-
  convention issue instead. Confirmed cross-architecture: rcc-arm64 has
  the identical bug (AAPCS64 has the same ≤16-byte no-hidden-pointer
  rule via X0:X1) — **only the x86-64 SysV path is fixed this session**;
  ARM64 is untouched (still always hidden-pointer for structs) to avoid
  shipping a half-implemented AAPCS64 register-return path, and is
  tracked as a follow-up below.
  Fixed by classifying struct/union return types ≤16 bytes with no
  float/double/long-double/`_Complex` member anywhere (recursively
  through nested structs/unions/arrays) as GP-register returns, and
  wiring that classification into all three x86-64 SysV decision sites
  plus the actual RAX/RDX value transfer at both the call site
  (`gen_funcall`) and the function definition (`ND_RETURN`). A second,
  subtler bug surfaced while fixing this: the function epilogue already
  saved/restored RAX around `__attribute__((cleanup(...)))` handler
  calls (so a pending scalar return value survives a cleanup handler's
  own function calls, which clobber caller-saved registers) but never
  did the same for RDX — so a >8-byte register-returned struct whose
  local had a cleanup attribute lost its upper eightbyte the moment the
  cleanup handler made any call of its own (regressed the TCC suite's
  `101_cleanup` mid-fix: `test_cleanup3`'s 16-byte `tsti` came back `42
43 0 0` instead of `42 43 44 45`). Fixed by saving/restoring RDX
  around cleanup calls exactly like RAX, gated on the function actually
  returning a >8-byte GP-register struct.
  → unblocks: test*bash's `run-arith`/`run-arith-for` (both now pass),
  plus nine \_other* bash test suites that looked unrelated at first
  glance but turned out to depend on correct internal arithmetic too
  (`run-assoc`, `run-comsub2`, `run-coproc`, `run-extglob`,
  `run-heredoc`, `run-histexpand`, `run-jobs`, `run-lastpipe`,
  `run-nameref` — all diffed against real bash before this fix, all
  match after)

New regression test: `test/test_struct_return_gpregs.c` — calls the
libc div-family functions directly (the real-world trigger) plus a set
of user-defined struct shapes (16-byte all-integer, struct with a
pointer member, nested small struct, through a function pointer, direct
assignment) that must return correctly, alongside a >16-byte struct and
a struct with a float member that must still take the (unchanged)
hidden-pointer path. Fails (`assert` abort) on the unfixed compiler at
every optimization level (including `-O0` — this bug is a calling-
convention defect, not an optimizer artifact); passes at every level
once fixed. Full suite verified: 0 failed (Linux native `--all`,
Torture 3605/3609 = 100% of non-skipped); Unit tests 173/173, also
verified separately at `-O2`. Real-world repro: bash's own `make test`
now runs to completion (previously hung); comparing which of its 86
test scripts diff against a real-bash baseline run in the same sandbox
dropped from 19 to 1 (the one remaining, `run-glob-bracket`, was an
unrelated `-rdynamic`/dynamic-loadable-builtin symbol-visibility
issue — since fixed, see "Fixed: test_bash `run-glob-bracket`" below).

### Fixed rcc bug: ARM64 `stur`/`ldur` frame-offset immediate overflow

The previous TODO entry here ("ARM64 small-struct return ABI... not
fixed this session") was **stale** — that classification (a struct/
union ≤16 bytes with no floating member returns raw bits in X0:X1, no
hidden pointer, mirroring the x86-64 fix above) is already fully wired
for ARM64 throughout codegen.c (`struct_returns_in_gp_regs()`'s
`ARCH_ARM64` branch in `has_hidden_retbuf`, the call-site X0/X1
load, `ND_RETURN`'s X0/X1 store, and the prologue's `has_retbuf`/
`save_rdx_for_cleanup`-equivalent X1 preservation) — confirmed working
by re-running the exact `div()`/`ldiv()`/`lldiv()`/`imaxdiv()` repro
that flagged the original x86-64 bug, cross-built for arm64 and run
under `qemu-aarch64`: all four now return correct `{quot, rem}` pairs
into external (non-rcc-compiled) glibc-called code, exactly like the
x86-64 fix. The **actual** remaining rcc-arm64 bug that TODO entry's
own regression evidence (c-testsuite's `00204.c`, an ARM64-specific
HFA/small-struct calling-convention smoke test) was really pointing
at turned out to be unrelated to struct-return classification at all:

- **`asm_stur_fp()`/`asm_ldur_fp()` (codegen_asm.h) emitted `stur`/
  `ldur` with no range check on the frame-pointer-relative offset** —
  AArch64's unscaled-offset STUR/LDUR encode a signed 9-bit immediate
  (`imm9`, range -256..255), and `arm64_stur()`/`arm64_ldur()`
  (arm64*enc.c) mask the raw value with `& 0x1ff` unconditionally, with
  no bounds validation. An offset whose magnitude exceeds that range
  (e.g. requesting `[x29, #-264]`) silently wraps to a small
  **positive** offset instead (`-264 & 0x1ff == +248`), addressing
  memory \_above* the frame pointer — where the caller's own stack
  content lives — instead of erroring or falling back to indirect
  (scratch-register) addressing the way every other `x29`-relative
  helper in this file already does (`arm64_load_from_fp_minus()`/
  `arm64_store_to_fp_minus()`, `asm_sub_fp_imm()`, etc. all check the
  offset and fall back to `sub x16/x17, x29, #offset; ldr/str`).
  `asm_stur_fp`/`asm_ldur_fp` are used by `gen_funcall()`'s
  register-pressure argument-staging path (parking an about-to-be-
  called function's evaluated argument values in per-call temp slots
  before the final register-loading pass), `spill_offset()` register
  spills, and a couple of libgcc-helper call sites (128-bit shift/
  divide, atomic RMW) — any of which can legitimately need an offset
  beyond 255 bytes from `x29` once a function's own parameter/local
  storage is deep enough. This stayed latent for small/shallow
  functions and only manifested once a function had enough of its own
  storage (several small struct parameters plus several multi-member
  HFA struct parameters, each needing its own frame slot) that adding
  a handful of staged call-argument temps for a _nested_ call (this
  session's repro: a `printf()`/`snprintf()` call needing several
  register-pressure temps of its own) pushed past the 255-byte
  boundary — silently overwriting the region just above the frame,
  up to and including the **caller's saved x29/x30** a few slots
  further up, corrupting the return address itself with leftover
  floating-point argument bit patterns (a SIGSEGV at a garbage PC that
  happens to be a valid IEEE-754 double bit pattern, not a wrong-value
  bug — confirmed via a `qemu-aarch64` gdbstub trace showing the fault
  PC and X29/X30 holding the exact bit patterns of two of the crashing
  function's own `long double` struct-member arguments).
  Fixed by giving `asm_stur_fp()`/`asm_ldur_fp()` the same range check
  and indirect-addressing fallback (via scratch `x16`, reusing the
  already-range-safe `asm_sub_fp_imm()`/`asm_ldr_reg()`/`asm_str_reg()`
  helpers) that the file's other `x29`-relative accessors already have.
  → found via GCC c-testsuite's `00204.c` (ARM64-specific calling-
  convention smoke test): `fa4()`'s exact parameter shape (three tiny
  char-array structs interleaved with three float/double/long-double
  HFA structs) still SIGSEGV'd after the struct-return-ABI
  investigation above ruled that out, isolated via bisection down to a
  minimal repro and a `qemu-aarch64` gdbstub trace of the exact
  corrupting instruction.
  A second, smaller side effect of the same fix: GCC torture's
  `vect/pr88497-7` (a separate deep-enough-frame test that happened to
  hit the same `stur`/`ldur` overflow) now also passes on arm64,
  previously miscounted among the "genuine complex/imaginary-constant
  gaps" torture failures.

New regression test: `test/test_arm64_frame_call_stage.c` — the
`00204.c`-shaped repro (three GP-passed structs interleaved with three
HFA structs, calling `snprintf()` from inside a function with that much
of its own parameter storage), asserting the exact expected formatted
output; confirmed to SIGSEGV on the unfixed compiler (verified via a
`git stash`-isolated pre-fix rebuild) and pass with the fix, on both
arm64-cross and native Linux x86-64 (the bug is ARM64-specific by
construction — the STUR/LDUR instruction only exists on that
architecture — but the test's C source and expected output are
portable).
**Full suite re-verified after the fix (arm64 cross)**: c-testsuite
220/220 (was 219/220 — `00204.c` now passes), Torture 3599/3609 (was
3598/3609 — `vect/pr88497-7` also now passes) with 6 pre-existing,
unrelated runtime failures remaining (c11-complex-1, c23-float-6,
c23-imaginary-constants-1/5/9, pr92904 — all genuine complex-number/
imaginary-constant gaps, unaffected by this fix), Dg-error 34/34, TCC
Compatibility 119/120 (1 pre-existing, unrelated `__DECIMAL_BID_FORMAT__`
gap) — 0 new failures, 2 additional tests fixed as a side effect.
Native Linux x86-64 and mingw cross (both unaffected by an ARM64-only
encoder bug) re-verified clean.

### Fixed: test_bash `run-glob-bracket` (`-rdynamic` not implemented)

`tests/run-glob-bracket` failed: `./bash: symbol lookup error:
./strmatch.so: undefined symbol: strmatch`. bash's test loads a
small shared-library "loadable builtin" (`enable -f ./strmatch.so
strmatch`) that calls back into a `strmatch` symbol the main `bash`
binary is expected to export. Root cause: `-rdynamic` (bash's own
`LOCAL_LDFLAGS`) wasn't recognized by rcc's driver at all — it fell
through the generic "ignored unknown option" catch-all (main.c) and
was silently dropped before ever reaching the linker command line, so
neither rcc's native ELF linker nor its GCC fallback ever saw it.
Even where rcc's native ELF linker successfully handles the whole
link (which this bash invocation doesn't reach — an all-`.o`
"link-only" invocation with no freshly-compiled source always falls
through to the GCC fallback today, a separate pre-existing gap, not
addressed here), its own dynamic-symbol-table builder (link_elf.c)
only ever collected an executable's imported (undefined) dynamic
symbols, never its own definitions — real `-rdynamic`/
`--export-dynamic` additionally exports every globally visible
defined symbol so a later `dlopen()`'d object can resolve back into
the main program.
Fixed by: (1) recognizing bare `-rdynamic` and `-Wl,-E`/
`-Wl,--export-dynamic` in main.c, threading a new `opt_export_dynamic`
flag through `rcc_link`/`LinkState` (and still appending the flag
verbatim to the `libs` string so the GCC fallback path also receives
it); (2) extending link_elf.c's existing `-shared` "collect every
global symbol" export path (previously `if (s->opt_shared)`) to also
fire for `opt_export_dynamic` on a plain executable, appending those
symbols to `.dynsym`/`.gnu.version`/`.hash` alongside the imports at
their final patched addresses.
→ unblocks: test_bash's `run-glob-bracket` (rebuilt bash now exports
`strmatch`, matching gcc's `-rdynamic` `.dynsym`; the plugin's dlopen
resolves and the test's output matches `glob-bracket.right`).

New regression test: `test/test-link.sh` case 6 ("-rdynamic dlopen
callback") — a main program built with `-rdynamic` that `dlopen()`s a
separately-built plugin `.so` calling back into a symbol defined in
the main program, mirroring bash's `strmatch.so` shape; fails to even
load (`dlopen`/`dlsym` return NULL) without the fix, passes with it.
Full suite verified: Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link tests 5/5 (incl. the new case), 0 failed overall; also
confirmed clean on the mingw and arm64 cross-compile targets (compile
only — `-rdynamic`'s native-ELF-linker effect is Linux-specific).

### Fixed (2026-08-08, continued — assignment-expression-as-lvalue)

- **`gen_addr()`'s `ND_ASSIGN` case computed the address of an
  assignment's target without ever emitting the assignment's store**
  (codegen.c) — `(a = b).member`, `&(a = b)`, and chain assignments
  through a struct RHS (`d = e = a[0]`) all route through `gen_addr()`
  to get an lvalue address; its `ND_ASSIGN` case was simply `return
gen_addr(node->lhs);` — the address of `a`, computed _without_ first
  generating `a = b`'s actual store. Every read through that address
  then saw `a`'s stale/uninitialized prior contents instead of `b`.
  Reproducible with three lines of plain C, no mruby involved:
  `struct { uintptr_t w; } a, b = {42}; uintptr_t r = (a = b).w;`
  prints garbage instead of `42`; `gcc`/`clang` both print `42`
  correctly. One call site already carried a matching comment ("For
  chain assignments (d = e = a[0] = c), use gen() to trigger inner
  assignment evaluation, not gen*addr() which skips it") for the
  struct-assignment RHS case (parser.c:7386-7387) — this was the same
  bug, just unfixed for the `gen_addr()` entry point itself (`.member`
  access and `&`).
  → found via mruby 4.0.0's VM: word-boxed `mrb_value` is `{ uintptr_t
w; }`, and GCC-style macros like `WORDBOX_OBJ_TYPE_P(o,n)
(!mrb_immediate_p(o) && mrb_val_union(o).bp->tt == n)` expand their
  parameter `o` multiple times, so a real call site like
  `mrb_hash_p(kdict = regs[kidx])` (`OP_KARG`'s keyword-argument
  dispatch, `src/vm.c`) re-expands `kdict = regs[kidx]` textually
  several times — this bug meant only the \_first* repetition's address
  computation happened and the store into `kdict` never did, so every
  `mrb_hash_p`/`mrb_hash_key_p` check downstream read `kdict` as
  garbage. Confirmed real (not pre-existing/environment): the previous
  session's investigation of this same test*mruby failure (see below,
  formerly a full `mrbtest` SIGSEGV from infinite recursion in error
  formatting) had already fully fixed the crash's proximate trigger via
  the earlier small-struct-return-ABI session; what remained was this
  bug, surfacing as 5 spurious `ArgumentError`s ("missing keyword: …",
  wrong `Hash#key?`/`#==` results) that `mrbtest` itself catches and
  counts as "Crash" (not an OS-level SIGSEGV, mruby's own VM-level
  exception guard).
  Fixed by actually generating the assignment (`gen(node)`, which
  performs the store) before using its target's address: for
  struct/union/array/complex targets `gen()` already returns the
  destination address directly (reused as-is, matching the existing
  chain-assignment fix's pattern); for scalar targets `gen()` returns
  the assigned \_value*, discarded before re-taking the target's
  address.
  → unblocks: test_mruby's `mrbtest` — `Total: 1686, OK: 1677, KO: 0,
Crash: 0` (was: full SIGSEGV, no summary at all, before the earlier
  struct-return-ABI fix; `Crash: 5` after that fix; `KO: 2` after this
  fix but before the `Math.erf`/`Math.erfc` fix below — `mrbtest` now
  matches gcc-built mruby's own summary exactly, including its 9
  environment-only `Skip`s). See "Fixed (2026-08-08, continued —
  include/math.h erf/erfc)" below for the last 2 failures.

New regression test: `test/test_assign_expr_lvalue.c` — struct member
access on an assignment expression's result, the same assignment used
twice within one `&&`-sequenced expression (the real macro-re-expansion
shape), `&` on a scalar assignment result, and a struct chain
assignment; all cross-checked against gcc. Full suite verified: Torture
3605/3609 (100% of non-skipped), Dg-error 34/34, Unit tests 176/176,
Link tests 5/5, 0 failed overall; confirmed clean (test PASSes) on the
mingw and arm64 cross-compile targets.

### Fixed (2026-08-08, continued — include/math.h erf/erfc)

- **`include/math.h` (rcc's own bundled header, used in preference to
  the system `<math.h>` the same way `emmintrin.h`/`tmmintrin.h` are)
  had no declaration for `erf()`/`erfc()` at all** — most of ISO C99's
  `<math.h>` surface was there (`sin`, `cos`, `tgamma`... no, not even
  that far — just the commonly-used subset), but `erf`/`erfc` were
  simply missing. Calling an undeclared external function silently
  falls back to an implicit `int` return type (K&R legacy behavior,
  accepted with no diagnostic even under `-W`): `double y = erf(x);`
  treated the call's return value as arriving in `RAX` (the
  integer-return register) and converted it to `double` via
  `cvtsi2sd`, instead of reading the real `double` result glibc placed
  in `XMM0` per the SysV ABI. The call itself succeeded and glibc
  computed the mathematically correct value; only the _caller's_
  interpretation of the return register was wrong — confirmed by
  disassembling the call site (`call erf@plt` immediately followed by
  `mov %rax,%r10; cvtsi2sd %r10d,%xmm0`, versus `sin`/`cos`/`sqrt`/
  `exp`/`log`, all present in `include/math.h` and all reading `xmm0`
  correctly).
  Reproducible with three lines of plain C, no mruby involved:
  `double y = erf(1.0);` prints `1072693248` (a plausible-_looking_
  large-integer bit pattern, not obviously garbage — which is what
  made this miscompile easy to spot in test _output_ but easy to miss
  by code inspection) instead of `~0.8427`.
  Fixed by adding `double erf(double);`/`double erfc(double);` to
  `include/math.h`.
  → unblocks: test_mruby's `mrbtest` — `Total: 1686, OK: 1677, KO: 0,
Crash: 0, Skip: 9`, now byte-for-byte matching the reference
  gcc-built mruby's own summary. **`test_mruby` is fully fixed.**

New regression test: `test/test_math_erf.c` — checks `erf`/`erfc`
against their true mathematical values (tight tolerance, not just "in
some plausible range" — a wrong-but-plausible-looking value like the
one this bug actually produced would trivially pass a loose bounds
check) plus the `erf(x) + erfc(x) == 1` identity. Full suite verified:
Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Unit tests
177/177, Link tests 5/5, 0 failed overall; confirmed clean (test
PASSes) on the mingw and arm64 cross-compile targets.

### Fixed (2026-08-08, cproc array/VLA-param type-modeling session)

- **Array-parameter bracket qualifier decayed to `T *const` on the wrong
  side** (test_cproc's `test/func-array-param.c`): `int a[const]` (C99
  6.7.6.3p7 — a qualifier inside a parameter's array-declarator `[]`
  qualifies the DECAYED POINTER PARAMETER itself, `int *const a`) was
  silently discarded by `type_suffix()`'s bracket-qualifier loop, and
  separately, qualifying a typedef'd array type (`const T c` where `T` is
  `typedef int T[]`) wrongly set the qualifier on the array type's own
  `->qual` in `declspec()` instead of pushing it down to the element type
  per C11 6.7.3p9 — both bugs made the decayed pointer come out qualified
  on the wrong side (`const int *` instead of `int *const`, or vice
  versa). Fixed by having `type_suffix()` record any qualifier found in
  the outermost (first) array dimension's `[...]` on the built array/VLA
  type's own `->qual` (otherwise always 0 for arrays), which
  `declarator_params()`'s array/VLA-to-pointer decay now transfers onto
  the resulting pointer type; and by adding `qualify_array_elem()` in
  `declspec()` to recurse a prefix qualifier down through a typedef'd
  array/VLA to its element type instead of setting it on the array itself.
- **`__builtin_types_compatible_p` never handled `TY_VLA`**
  (test_cproc's `test/compatible-vla-types.c`): `types_compatible_p_qual()`
  had no case for VLA pointer types or runtime-sized array type-names —
  the strict `a->kind != b->kind` check rejected any `TY_ARRAY` vs
  `TY_VLA` comparison outright (e.g. `int (*)[n]` vs `int (*)[3]`), and
  two same-kind VLAs fell through to the ordinary `TY_ARRAY` case's
  constant-length comparison, wrongly treating two different runtime
  lengths (which are never a compile-time compatibility criterion per
  C11 6.7.6.2p6-7) as incompatible. Fixed by handling `TY_ARRAY`/`TY_VLA`
  together in `types_compatible_p_qual()`: element types must still be
  compatible, but if either side is `TY_VLA` the length check is skipped
  entirely (always compatible), while two `TY_ARRAY`s keep the existing
  "either side unknown-length (size 0) is compatible" logic.

New regression tests: `test/test_array_param_qual_decay.c` (bracket-
qualifier decay to `int *const`, plus the typedef'd-array-qualifier
push-down case) and `test/test_vla_types_compatible.c` (VLA/unspecified-
length array vs fixed-array/VLA compatibility, including the
element-type-still-matters negative case). Both new upstream cproc
fixtures (`func-array-param.c`, `compatible-vla-types.c`) now compile
clean with `./rcc -std=c23 -c`, matching `gcc -std=c23 -c` (rc=0 on
both, cross-checked). `make -j$(nproc) rcc` stays warning-clean.

### Historical: the original test_mruby crash writeup (superseded above)

`mrbtest` SIGSEGVs (stack overflow) during `mrb_mruby_objectspace_gem_init`,
deep inside infinite mutual recursion:
`convert_type` → `mrb_type_convert` → `mrb_funcall_with_block` →
`ensure_block` → `mrb_funcall_argv` → `convert_type` → ... (repeats
identically). `mrb_type_convert`'s failure path formats an error
message via `mrb_vformat`'s `%T`/`%Y` specifiers, which itself calls
`mrb_obj_as_string` → `mrb_type_convert` on the value being described;
if _that_ nested conversion also fails, it recurses into formatting
its own failure, without ever terminating.

**Confirmed real (not pre-existing/environment)**: the identical
mruby 4.0.0 checkout built with real gcc instead of rcc passes cleanly
(`Total: 1686, OK: 1677, KO: 0, Crash: 0, Skip: 9`). Something rcc
generates makes an object's method dispatch (`mrb_respond_to`)
incorrectly report "doesn't respond to :to_s" for a value that should.

Investigated but NOT reproduced in isolation (at the time):

- `mrb_value` is `{ union { double f; void *p; mrb_int i; ...} value;
enum mrb_vtype tt; }` (`boxing_no.h`) — a small struct passed BY
  VALUE pervasively, including through `mrb_raisef`'s `...` varargs
  and `mrb_vformat`'s `va_list` forwarding. Hypothesized a SysV ABI
  eightbyte-classification bug (union merging an SSE-class `double`
  member with INTEGER-class `void*`/`mrb_int` members at the same
  offset should classify INTEGER, per the AMD64 ABI merge rule "if one
  class is INTEGER, the result is INTEGER") that could put the struct
  in the wrong registers when it happens to hold a pointer. Built
  several targeted repros (direct call, single-level varargs, va_list
  forwarded through a non-variadic helper with mixed preceding
  int/string args) mirroring `mrb_raisef` → `error_va` →
  `mrb_vformat`'s exact shape — all matched gcc's output correctly.
  Root cause is likely deeper in the real call chain (GC interaction,
  register pressure at `-O3`, or mruby's own symbol/method hash-table
  lookup) and needs the actual mruby VM environment (not a minimal
  repro) plus a `-O0`/`-O1` vs `-O3` bisection to isolate further.
  (Superseded: this build's own `MRB_WORD_BOXING` default, not
  `boxing_no.h`/`MRB_NO_BOXING`, was actually active — see the fix
  above.)

- `test/third_party/results.txt` — tab-separated: `rc\ttest_name\tduration`
- `test/third_party/logs/test_*.log` — per-project build + test output
- `test/third_party/test_<name>/` — failing project dirs (passing dirs auto-deleted)

Re-run a single test:
CC=$(pwd)/rcc bash test/linux*thirdparty.bash test*<name>

---

## rc=1 — Runtime Failures (builds OK, test fails)

| test             | symptom                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ---------------- |   |
| test_lua         | **fixed** — passes cleanly now (confirmed via a fresh individual run this session, `rc=0` in 36s); no rcc changes were needed specifically for it, resolved by the accumulated fixes from prior sessions                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| test_mruby       | **fixed** — was: assignment-expr-as-lvalue bug + missing `erf`/`erfc` declarations, see "Fixed (2026-08-08, continued — ...)" sections above; `Total: 1686, OK: 1677, KO: 0, Crash: 0` (matches gcc-built mruby exactly)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| test_curl        | **fixed** — was: configure "compiler does not halt on prototype mismatch"                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| test_c23doku     | needs arbitrary-precision `_BitInt` codegen (up to 11163 bits) — see "Needs fixing" item 1 below                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| test_c3          | CMake: missing LLD_COFF                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| test_coremarkpro | benchmark runner can't find perf logs                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| test_box3d       | C++ binary (g++ compiled, not rcc)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| test_glib        | **investigated, not an rcc bug** — `configure` fails before any compilation: `Package requirements (libpcre >= 8.31) were not met: Package 'libpcre' not found` (missing system dev package in this sandbox, not an rcc issue)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| test_got         | configure: missing libbsd-overlay                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| test_ksh93       | **all arith.sh failures fixed except one documented long-double precision limitation** — the `cos*cos + sin*sin > 1.01` hang and the `-0` sign-of-zero failures are fixed (long-double assignment-expression value, IEEE flonum truthiness with -0.0, and 64-bit pointer arithmetic/subtraction — see "Fixed (2026-08-12, ksh93 arith / IEEE truthiness / 64-bit pointer-arithmetic session)" below); ksh93's own full `shtests` suite now runs with only `arith.sh[327]` failing (3x: plain/C.UTF-8/shcomp): `typeset -lE20 val=123.01234567890` prints the double expansion `123.01234567890000449` — rcc computes `long double` internally in double precision by design (documented in codegen.c), so 20-significant-digit extended-precision rounding cannot hold; real 80-bit long double support is a large feature, not a quick fix. Build still compiles/links the entire `libast` + `iffe` 161/161, and the whole `shtests` suite passes except that one rounding check                                                                                                                                             |
| test_libgmp      | **shared/static library build now fully fixed** — was: configure-time "cannot determine 32-bit word directive" (stale, long-superseded by prior sessions' assembler fixes); this session found and fixed the last two real rcc bugs blocking the actual `libgmp.so`/`libgmp.a` link (forward-referenced local-label binding, `.hidden`/`.protected`/`.internal` ELF visibility — see "Fixed (2026-08-11, forward-referenced local-label binding / ELF visibility session)" below). The library's own `tests/mpn/t-*` runtime suite still shows 47 failures, confirmed **not an rcc bug** — bit-for-bit reproduces (identical exit codes) against the same GMP 6.3.0 source built with the system's real gcc+GNU as+GNU ld, a pre-existing GMP/environment incompatibility                                                                                                                                                                                                                                                                                                                                                     |
| test_muon        | **all rcc-related self-test failures fixed; 384/387 (3 env/muon failures identical to gcc-built muon)** — was: 342/387 with ~22 rcc-related failures (link-command generation). Two-part fix: rcc now falls back to the external (gcc) linker whenever the internal ELF/PE/Mach-O linker cannot honor a linker command (any `-Wl,` option or `-nodefaultlibs` — previously silently dropped, producing links with no DT_RUNPATH/DT_SONAME or unresolvable archive groups; see "Fixed (2026-08-12, external-link fallback / muon toolchain detection session)" below), and a bare `-Wl,-v`/`-l` invocation is a link-only probe instead of "no input files"; the harness now registers `rcc` as a muon compiler toolchain (inherit posix, linker `ld`) exactly like the existing `slimcc` patch, so muon detects rcc's linker as GNU ld and generates the full `-Wl,--as-needed/-rpath/--start-group/-soname` args. Remaining 3: `muon/wayland` (needs wayland-scanner tool), `common/183 partial dependency` + `common/251 add_project_dependencies` (zlib link path) — all reproduce identically with a fully gcc-built muon |
| test_neovim      | **investigated, not an rcc bug** — CMake configure fails before any compilation: `Could NOT find Luv (missing: LUV_LIBRARY LUV_INCLUDE_DIR)` (missing system Lua-libuv-binding dev package in this sandbox, not an rcc issue)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| test_nob         | **fixed** (2026-08-14, C `defer` session) — was: needs C's experimental `defer` statement (`-fdefer-ts`, WG14 N3199/TS 25755, not yet standardized); see "Fixed (2026-08-14, C `defer` session)" and "Needs fixing" item 5 above/below                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| test_rsync       | **fixed** — was: `undefined reference to 'preserve_acls'`/`'preserve_xattrs'` at link time; block-scope-`extern`-inside-dead-`static-inline`-function DCE bug, see "Fixed (2026-08-09, block-scope extern DCE session)" below                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| test_samba       | **two real rcc bugs found and fixed** (see "Fixed (2026-08-10, LONG_MAX/atomic-load session)" below) — configure now progresses far past its earlier `pyembed`/`Python.h` failure into unrelated dependency checks (pam, iconv, ncurses, readline, ...), currently blocked on `perl module "Parse::Yapp::Driver" not found` (missing build-time CPAN module in this sandbox, not an rcc issue)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| test_scrapscript | **fixed** — was: every test failed to even link (`undefined reference to '__start_const_heap'`); the `section()` attribute fix resolved linking (32/33 -> 17/33 failing), then the section sh_addralign fix below resolved the remaining 17 `SIGABRT`s (17/33 -> 0/33 failing, full suite green)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| test_tcpdump     | **fixed** — real rcc register-allocator bug found and fixed (deeply nested ternary in the radiotap decoder's bit-scan macro), see "Fixed (2026-08-10, continued — nested-ternary register-allocator session)" below; own `make check` now 0 failed, 636 passed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

---

## rc=2 — Build Failures (compile/link error)

Top root causes identified:

### Fixed (2026-08-06)

- `UINT_FAST64_MAX` / `INT_LEAST*_MAX` undeclared — **fixed** (stdint.h: added 24 missing limit macros)
  → unblocks: coreutils, diffutils, gpatch, gsed, gtar
- `__DATE__` / `__TIME__` undeclared — **fixed** (preprocess.c: added C89 predefined macros)
  → unblocks: mimalloc
- **memcmp**: with empty string
  → unblocks: lua
- **VA_OPT**: C23 variadic macro (bfs) — keyword registered but not expanded
  → unblocks: bfs
- **Prototype mismatch not diagnosed** — **fixed**, see "Fixed
  (2026-08-07, continued)" above for the full writeup
  → unblocks: curl

### Fixed (2026-08-08, shufflevector/SSE2-baseline session)

- **`__builtin_shufflevector` unimplemented** (parser.c) — a Clang
  builtin (`vec1, vec2, i0, i1, ..., iN-1`, all indices compile-time
  constants, output lane count = however many indices were given) that
  GCC 15's own `<avxintrin.h>`/`<avx2intrin.h>` now also use
  unconditionally in several `_mm_reduce_*`/`_mm256_reduce_*` function
  bodies. rcc has no bundled AVX/AVX2 headers, so any project that
  merely does `#include <immintrin.h>` (regardless of whether it calls
  any AVX function) falls through to the real system headers and hits
  this — the whole TU failed to parse ("expected an expression") at
  every one of those reduce-macro call sites, and a syntax error in
  the middle of a declaration list cascaded into spurious errors on
  every subsequent statement.
  Implemented generically at parse time (like the existing
  `__builtin_shuffle`, but with immediate indices instead of a runtime
  mask vector, and independent output length): binds both operand
  vectors to temps once, then for each index either gathers
  `vec1[idx]` (idx < N1), `vec2[idx - N1]` (N1 <= idx < N1+N2), or an
  arbitrary lane (idx < 0, Clang's "don't care" convention) into a
  freshly-sized result vector built via the existing
  `make_vector_type()`. Portable — no architecture-specific codegen,
  works identically on ARM64.
  → unblocks: test_blake3's `blake3_dispatch.c` (parses past the
  `<immintrin.h>` chain; blake3 itself still needs real AVX-512
  codegen it doesn't get from rcc, see below)
- **Large baseline-SSE2 gap in `include/emmintrin.h`** — once
  `__builtin_shufflevector` no longer aborted the parse early, GCC's
  own `avx2intrin.h` reduce macros went on to call `_mm_min_epi16`/
  `_mm_max_epi16` by name, which don't exist in rcc's _own_
  (deliberately minimal, hand-written) `emmintrin.h` — found to be
  missing an entire tier of plain SSE2 (no SSSE3+) intrinsics that
  real programs call directly too: `_mm_min_epi16`/`_mm_max_epi16`/
  `_mm_min_epu8`/`_mm_max_epu8`, the saturating `_mm_adds_`/`_mm_subs_`
  family (epi8/epi16/epu8/epu16), `_mm_avg_epu8`/`_mm_avg_epu16`,
  `_mm_mulhi_epi16`/`_mm_mulhi_epu16`, `_mm_madd_epi16`,
  `_mm_sad_epu8`, and the whole-_register_ byte shifts
  `_mm_srli_si128`/`_mm_slli_si128`/`_mm_bsrli_si128`/`_mm_bslli_si128`
  (distinct from the per-lane `_mm_s{r,l}li_epi{16,32,64}` that did
  exist). All implemented as plain lane-wise C loops (matching the
  file's existing style), every result cross-checked against real
  gcc's `<emmintrin.h>` output.
  → unblocks: test_libwebp (`sharpyuv_sse2.c` calls `_mm_max_epi16`/
  `_mm_min_epi16`/`_mm_madd_epi16` directly; `dec_sse2.c` calls
  `_mm_srli_si128`/`_mm_slli_si128` directly) — now builds, links, and
  its `cwebp`/`dwebp` tools successfully decode a real `.webp` image

New regression tests: `test/test_builtin_shufflevector.c` (portable,
no arch guard — exercises cross-operand selection, output-length
narrowing, single-vector reversal, and a non-`short` element type),
`test/test_sse2_minmax_madd_sat.c` (x86-only guarded like
`test_ia32_pause.c`'s convention). Full suite verified: Torture
3605/3609 (100% of non-skipped), Dg-error 34/34, Unit tests 175/175,
Link tests 5/5, 0 failed overall; confirmed clean (both new tests
PASS) on the mingw and arm64 cross-compile targets.

**Still blocked** (much larger undertakings, not attempted this
session): test*blake3 needs real AVX-512 (`avx512fintrin.h` pulls in
`__mmask8`/`_CMP_EQ_OQ`-style predicates and hundreds of
`\_\_builtin_ia32*\*` AVX-512 builtins rcc has no codegen for at all —
EVEX encodings, ZMM regs and k-masks; AVX2 (test_brotli) is now fully
implemented, see "Fixed (2026-08-12, AVX/AVX2 intrinsics session)"
below); test_fftw's failure
(`fftw3.h:483`, `\_\_float128`/`FFTW_DEFINE_API`quad-precision) is
unrelated to SIMD entirely. A genuine fix for blake3/brotli would mean
rcc shipping its own`<immintrin.h>`/`<avxintrin.h>`/`<avx2intrin.h>`(the way it already does for`<emmintrin.h>`/`<tmmintrin.h>`) instead
of falling through to GCC's real, AVX-512-chaining system headers —
a multi-session effort, not a quick win.

### Needs fixing

1. **C23 `_BitInt(N)`** — **test_cproc fixed** (see "Fixed (2026-08-08,
   root-cause/test_cproc-completion session)" below: `_BitInt` was never
   the real blocker for test_cproc — full type-system support has been
   added regardless). **Wide `_BitInt(N>64)` codegen is now fixed too**
   (see "Fixed (2026-08-13, wide `_BitInt(N>64)` session)" above: all
   operations route through the embedded slimcc bitint runtime).
   **test_c23doku remains skipped by decision** — its
   `brute_force.c`/`graph_color.c` declare `_BitInt(total * 3)` where
   `total = digit * digit`, i.e. up to 11163 bits for the 61x61 puzzle
   (175 64-bit legs). The runtime is width-agnostic so it would compile
   and run, but the project is not a target (no arbitrary-precision
   workload in scope).

2. **lib/tempname.c pattern** — **fixed** (2026-08-14, diffutils session):
   test_diffutils now builds and passes its whole test suite (29 PASS).
   Four stacked fixes: C23 `*_WIDTH` macros (`SIZE_WIDTH`/`PTRDIFF_WIDTH`/
   ...) in include/stdint.h; `__builtin___mempcpy_chk` and
   `__builtin___stpcpy_chk` fortify macros (the latter must keep stpcpy's
   end-pointer return); a parenthesized string-literal-chain initializer
   (`char s[] = ( "a" "b" )`, diffutils' C_ifdef_group_formats) that sized
   to 0 and dropped the guard bytes; and glibc fortify `__REDIRECT`
   (`readlinkat` → `__readlinkat_chk`) being wrongly applied to
   address-of/function-pointer uses, so careadlinkat's `readlinkat`
   function pointer called the 5-arg `_chk` variant with a garbage buflen
   and aborted on a buffer-overflow check.

3. **Object file passed as source** — test_heatshrink **fixed** (2026-08-14):
   `.os`/`.od` (PIC/PIE object) positional link inputs are now recognized
   as object files, not C sources. test_heatshrink passes (rc=0).

4. **Link failures (environment/upstream, not rcc)**: test_file
   (`undefined reference to 'isless'`, a glibc math.h macro rcc appears
   to emit as a real call instead of inlining — actually **needs
   re-triage**, tentatively rcc-side, see item 6 below), test_libgc /
   test_libjansson (**confirmed upstream bug, not rcc**: both
   independently call a nonexistent `__builtin_atomic_arith_add/sub/or`
   -- verified this session that real `gcc -c` also rejects that exact
   name with "implicit declaration of function", so any compiler
   advertising `__sync`/`__atomic` builtin support hits the identical
   broken macro in libgc's `include/private/gc_atomic_ops.h` and
   jansson's `src/jansson.h`; not an rcc-specific gap). Missing system
   libs/dev-headers unrelated to rcc: libseccomp, libzstd, ALSA
   (`alsa/asoundlib.h`, test_sokol), etc.

5. **C `defer` statement (WG14 N3199 / TS 25755, `-fdefer-ts` /
   `_Defer`)** — **fixed** (2026-08-14, C `defer` session): see "Fixed
   (2026-08-14, C `defer` session)" above. test_nob's own test suite
   (12 of its 14 `tests/*.c`, excluding a Win32-only test and one with
   unstable directory-listing order) now builds and runs to completion
   under `rcc -fdefer-ts`, output checksum matching exactly.

6. **Fresh clusters found in the 2026-08-14 full 221-target batch
   triage** (5 parallel scouts read every failing log; F16C and the
   unknown-flag-acceptance clusters below are already fixed, see
   "Fixed (2026-08-14, F16C intrinsics / unknown-flag acceptance
   session)" above — the rest are open, listed by cluster, not
   individually fixed this session):
   - **GNU C designated array-range initializer, `__builtin_*_overflow`
     family, ADCX/ADOX/STMXCSR/LDMXCSR, and PSHUFLW/PSHUFHW/PSLLD/
     PSRLD assembler gaps** — all **fixed**, see "Fixed (2026-08-14,
     array-range designator / overflow builtins / ADX+mxcsr session)"
     above.
   - **Inline-asm `"x"`/`"=x"` (xmm register) constraint doesn't
     correctly bind a `vector_size` C local to the asm operand** —
     found while writing this session's PSHUFLW/PSHUFHW regression
     test: even a bare `__asm__("movaps %1, %0" : "=x"(dst) :
"x"(src))` between two `vector_size(16)` locals reads/writes
     garbage instead of copying the vector. Not attempted this session
     (worked around by testing the assembler/encoder in isolation via
     a standalone `.S` file instead, matching
     `test_asm_aesni_sse2.c`'s existing convention) — needs its own
     dedicated repro+bisect into how inline-asm operand binding
     resolves register-class constraints for vector-typed C locals.
   - **Remaining assembler gaps**: a `salsa20_xmm6-asm.S` file the
     assembler can't process (test_libsodium), `_mm_cvt_ss2si`
     intrinsic (test_libopus).
   - **Parser rejects valid C in real project source** (each a distinct
     construct, not one root cause): a C99 flexible array member inside
     a struct (test_bfs, `struct ioq_thread threads[];`); a `static`
     function definition with a typedef'd return type
     (test_elk, `main.c:23`); an `extern` declaration using a
     forward-declared opaque struct pointer type (test_emacs,
     `xterm.h:1848`); an anonymous-union member pattern
     (test_glib, `goption.c:212`); a system header's own struct closing
     brace inside a nested context (test_liballegro5,
     `gdtlsconnection.h:108`); an empty-body context
     (test_lexbor, `normalization_forms.c:206`); designated-initializer
     macro tables (test_mquickjs, test_njs); a token-pasting macro used
     as a declarator (test_parrot); struct member access rcc reports as
     "no such member" on legitimate code (test_php, test_cfitsio,
     test_tcl); `sizeof` applied to an incomplete type inside a
     `static_assert`-style macro idiom (test_utillinux); an unclosed
     string literal lexer false-positive (test_gnutls,
     `config.h:2359`); conflicting-types false-positive between a local
     prototype and a generic-function expansion (test_gtar,
     `xattrs.c`); a `_Generic` dispatch macro rejecting a valid
     association (test_noplate); `dlfcn.h`'s `Dl_info` usage
     (test_nqp); wolfSSL's macro-generated union member declaration
     (test_wolfssl, `hash.h:109-254`); AVX2 `_mm256_set1_epi16`-family
     intrinsics still gap in some header path (test_wuffs) — separate
     from the fixed F16C cluster, needs its own look.
   - **Wrong runtime output / crash in rcc-compiled code** (candidate
     miscompiles — each needs its own dedicated repro+bisect, not
     attempted this session): test_box2d (rc=139, confirmed the
     crashing binary's C code was entirely rcc-compiled, not a non-rcc
     C++ binary as the generic SIGSEGV/rc=139 heuristic assumes),
     test_box3d, test_doom (linker drops global-variable definitions
     from one large TU), test_espruino, test_femtolisp, test_gzip
     (crashes running its own freshly-built binary to generate docs),
     test_hare, test_wren (`free(): invalid pointer`), test_xz
     (13/19 CTest failures incl. a SEGFAULT), test_zstd (SIGABRT during
     its own regression tests), test_yyjson (`test_number` subprocess
     crash, 11/12 other tests pass), test_libevent, test_libsamplerate,
     test_libpng, test_tinycc (`-nan` from an unsigned-long-long→double
     conversion), test_tomlc17, test_ruby (a `sizeof(char[1-2*cond])`
     negative-array-size compile-time-assert trick mis-evaluated),
     test_vlc (`stdckdint.h` probes abort), test_ocaml/test_mimalloc
     (linker reports the rcc-produced static archive itself as
     malformed — "file in wrong format"/"bad value" — worth checking
     archive-writing first since it may be one shared root cause across
     both), test_nanomsg (rcc misclassifies a `.so.N.N.N`-suffixed
     shared-library dev symlink as a C source file to compile).

   Every cluster above is grounded in a specific `file:line` and
   quoted rcc/linker diagnostic captured in
   `test/third_party/logs/<target>.log` from the 2026-08-14 batch run;
   re-read the log for exact context before starting a fix.

---

## rc=124 — Timeouts

| test              | notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ----------------- |   |
| test_bash         | **fixed** — `dstack` const-fold bug + small-struct return ABI bug + `-rdynamic` not implemented, see "Fixed (2026-08-08, ...)" sections above; own `make test` now runs to completion, `run-glob-bracket` also passes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| test_perl         | **fixed** — the spill-slot/locals-offset collision root-caused in "Investigated, not fixed (2026-08-10, register-spill/locals-collision session)" below is fixed, see "Fixed (2026-08-10, continued — spill/locals collision)" below; re-ran the real target fresh (`./Configure -des -Dcc=rcc ...`, `make -j3 test_prep && HARNESS_OPTIONS=j3 make test_harness`) and it now builds miniperl, the full `perl`, every extension, and `make test_harness` cleanly to completion — `rc=0` in 557s (previously segfaulted inside `Perl_upg_version()` before even reaching `lib/buildcustomize.pl`)                                                                                                                                                             |
| test_go           | **C bootstrap phase fully fixed; blocked one layer deeper** — a lexer infinite-loop DoS bug (any non-identifier-start non-ASCII byte hung rcc forever) and a missing `__attribute__((weak))` variable-linkage gap (two independent stacked bugs), see "Fixed (2026-08-11, continued — lexer non-ASCII infinite loop / weak variable attribute session)" below; `cmd/dist`/`lib9`/`libbio`/`liblink`/`5c`/`6c`/`8c`/`9c`/`5g`/`6g`/`8g` now build and self-bootstrap completely, reaching real `.go` stdlib compilation with the freshly-built `6g` — which then hits a likely miscompilation in `6g`'s own (Plan9 C, rcc-built) embedded-struct-field-promotion logic, several layers removed from rcc's own C-level correctness; not attempted this session |
| test_nginx        | **fixed** — `__sync_fetch_and_add`/sub/or/xor/and/nand's narrow-argument sign-extension bug (`ngx_atomic_fetch_add(lock, -1)` corrupted `ngx_rwlock_unlock()`, hanging every worker in `ngx_rwlock_wlock()` forever), see "Fixed (2026-08-11, continued — atomic fetch-op narrow-argument session)" below; reran the real `nginx-tests` suite fresh (`prove .`, 492 files) — all 2600 tests pass                                                                                                                                                                                                                                                                                                                                                             |
| test_groff        | **fixed** — passes cleanly now (confirmed via a fresh batch run this session); no rcc changes were needed specifically for it, resolved by the accumulated fixes from prior sessions                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| test_argtable3    | **fixed** — passes cleanly now (confirmed via a fresh batch run this session); no rcc changes were needed specifically for it, resolved by the accumulated fixes from prior sessions                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| test_httpparser   | **fixed** — was: `-funroll` label-aliasing bug, see "Fixed (2026-08-08, httpparser session)" above                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| test_libarchive   | **4/10 fixed, remaining 6 confirmed NOT an rcc bug** — the wide-string-literal alignment fix (4 stacked bugs, see "Fixed (2026-08-09, continued — wide string literal alignment: 3 stacked bugs)" below) unblocked `test_entry`/`test_archive_match_path`/`test_archive_match_time`/`test_filter_count`; the remaining 6 are a pre-existing PPMd arithmetic-decoder issue in this libarchive 3.8.8 checkout's own test corpus, reproducing identically with a fully gcc-built libarchive — see "Investigated: libarchive PPMd cluster ..." below                                                                                                                                                                                                             |
| test_liblz4       | **investigated, not an rcc bug** — `make test`'s `test-lz4-hugefile` step generates and round-trips a 4.2GB file; this sandbox's disk/CPU throughput alone exceeds the 420s harness timeout, not a correctness issue, see "Investigated: test_liblz4 ..." below                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| test_libpng       | **investigated, not an rcc bug** — `pngtest-all`'s strict byte-compare fails identically with a fully gcc-built libpng+pngtest too (upstream zlib-version-sensitive reference file, libpng's own documented caveat); remaining timeout is `pngimage-full`'s exhaustive transform-combination test running correctly but ~3x slower under rcc's codegen than gcc -O2 (222s vs 68s, both 100% PASS) — see "Investigated: test_libpng ..." below                                                                                                                                                                                                                                                                                                                |
| test_libressl     | **AES-NI/SSE2/GHASH/RC4 crypto asm fixed; blocked on new gap** — was untriaged; this session added missing AES-NI + several SSE2/SSSE3 instruction encoders (see "Fixed (2026-08-11, continued — AES-NI/SSE2 instruction encoder session)" below), unblocking `crypto/aes/aesni-*.S`, `crypto/modes/ghash-*.S`, `crypto/rc4/rc4-*.S`; now blocked on 21 `crypto/bn/arch/amd64/*.S` files using `.intel_syntax noprefix` — rcc's assembler has no Intel-syntax parsing mode at all, a large separate undertaking, not attempted this session                                                                                                                                                                                                                  |
| test_qbe_simplecc | **fixed** — GAS `/* */` block-comment handling in the inline assembler, a nested-designator compound-literal offset bug, and a register-allocator aliasing bug, see "Fixed (2026-08-09, qbe_simplecc session)" below; `qbe`'s own test suite now passes 59/59                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

---

## Quick Wins (next to fix)

**All previously listed quick wins are now fixed** — AVX2/AVX-512
codegen (test_blake3, test_brotli: see "Fixed (2026-08-12, AVX/AVX2
intrinsics session)" and "Fixed (2026-08-12, AVX-512 / EVEX intrinsics
session)" below), C23 `_BitInt(N)` (see "Fixed (2026-08-13, wide
`_BitInt(N>64)` session)" above), and C `defer` (see "Fixed
(2026-08-14, C `defer` session)" above; test_nob); test_c23doku stays
skipped by decision, arbitrary-precision bignum workload out of scope.
This section is stale history kept for context — no remaining genuine
gaps are tracked in "Needs fixing" above as of this session.

### Fixed (2026-08-08, VM-type evaluation-order session)

- **`__builtin_va_arg(ap, TYPE)` with a variably-modified `TYPE`** (parser.c)
  — the type-name parsed by `type_name()` for `__builtin_va_arg`'s second
  argument could itself be variably modified (e.g. `int (*)[++i]`, a
  pointer-to-VLA-array whose dimension is a non-constant expression with a
  side effect), but the resulting `Type`'s `vla_len_expr` just sat unused —
  nothing ever threaded it into the generated code, so the side effect
  (`++i`) silently never ran. Fixed by reusing `vla_freeze_dims()` (the
  existing mechanism a VM-typed cast already uses) to fold the embedded
  dimension expression's side effect into a hidden temp exactly once, ahead
  of the `va_arg` dereference.
  → found via cproc's `test/builtin-vaarg-vm.c`
- **Nested VLA-pointer-array declarator (`int (*p[f(2)])[f(3)];`) evaluated
  its dimension expressions unreliably** (parser.c) — an array-of-pointers
  local whose own length (`f(2)`) is a runtime dimension, and whose pointee
  element type is itself a VLA-array with its own runtime dimension
  (`f(3)`), built the correct `Type` shape, but neither dimension
  expression was frozen into a temp: `f(2)`'s raw expression node was
  embedded directly in the stack-allocation size computation _and_
  separately re-read (and thus re-evaluated) by any later `sizeof p`;
  `f(3)`'s raw node was never evaluated for the allocation at all and only
  ran — potentially more than once — whenever something like `sizeof **p`
  read it back out of the pointee type. Fixed by extending the existing
  `vla_freeze_dims()`-based dimension freeze (previously only applied when
  declaring a plain pointer-to-VLA local) to also apply when the
  declarator's own outermost type is itself a VLA: every dimension
  expression reachable through the type's pointer/VLA chain is now captured
  into a hidden local exactly once, ahead of the array's stack allocation,
  and every later use (sizeof, dereference) reads the frozen capture
  instead of re-running the expression.
  → found via cproc's `test/vla-nested.c`
- **`sizeof` on a VLA-typed _expression_ discarded the operand entirely
  instead of evaluating it** (parser.c) — C11 6.5.3.4p2 requires `sizeof`
  to evaluate its operand when the operand's own type is a VLA (not merely
  "variably modified" — e.g. a _pointer_ to a VLA doesn't trigger this,
  only a genuine VLA-typed expression like `*p` where `p` points at a
  VLA). The expression-operand branch computed the runtime size directly
  from the _type's_ captured `vla_len_expr` (`len * base_size`) but never
  emitted the operand `Node` itself into the generated code, so any side
  effect reaching the sizeof'd VLA lvalue (e.g. `sizeof(*(c++, p))`)
  silently never ran. Fixed by wrapping the size computation as
  `(operand, len * base_size)` (`ND_COMMA`) so the operand's side effects
  execute — evaluating a VLA/array-typed dereference never performs an
  actual memory load (arrays don't rvalue-convert), so this is safe even
  when the pointer is null.
  A second, related bug: `eval_const_expr`'s `ND_COMMA` case folded a
  comma expression to its right operand's constant value regardless of
  whether the _left_ operand was itself constant/side-effect-free
  (`return eval_const_expr(node->rhs, val)`), so an array-dimension
  expression like `int[(c++, 5)]` got silently constant-folded to `5`
  with `c++`'s side effect thrown away instead of surfacing as a genuine
  VLA dimension. Fixed by requiring both operands to independently fold
  as constants (mirrored in the float sibling `eval_const_fexpr`).
  → found via cproc's `test/sizeof-vla.c`
- **A cast's VM array-length expression nested behind a function-pointer
  return type was never evaluated** (parser.c, `vla_freeze_dims()`) — a
  cast like `(int (*(*)(void))[++l])0` (pointer to function returning
  pointer to a VM array) needs `++l` to run exactly once, same as the
  simpler `(int (*)[++l])0` case. `vla_freeze_dims()` only recursed
  through `TY_PTR`/`TY_VLA` `->base` chains, so a `TY_FUNC` in the middle
  of the chain (whose return type lives in `->return_ty`, not `->base`)
  stopped the walk before it ever reached the nested VLA — `++l` was
  parsed into the type but never threaded into any evaluated `Node`.
  Fixed by having `vla_freeze_dims()` also recurse into `TY_FUNC`'s
  `return_ty`.
  → found via cproc's `test/cast-vm.c`
- **A queued VM-`typeof` side-effect evaluation was dropped for any
  non-declaration statement** (parser.c) — `queue_vm_typeof_eval()`
  already correctly classified whether a `typeof(expr)` operand needed
  evaluating (VM after any top-level comma-operator array/VLA-to-pointer
  decay — e.g. `typeof(c++, p)` with `p` a pointer-to-VLA is VM and
  evaluated, but `typeof(p, c++)` decays to plain `int` and is not) and
  queued the evaluation onto a global `pending_vla_struct_capture` list,
  but that list was only ever flushed into the statement stream by the
  `declaration()` call site's caller. A bare (non-declaration) statement
  whose `typeof` appears inside a cast or compound literal — e.g.
  `(typeof(c++, p))0;` or a standalone `__builtin_va_arg(ap, typeof(out--,
p));` — went through the block-statement loop's generic `stmt()`
  fallback, which never checked `pending_vla_struct_capture` at all,
  silently discarding the queued side effect. Fixed by splicing any
  pending capture in ahead of the statement in that fallback too, matching
  the ordering the declaration path already uses.
  → found via cproc's `test/typeof-vm.c`

New regression tests: `test/test_vaarg_vm_type.c`,
`test/test_vla_nested_array_of_ptr.c`, `test/test_sizeof_comma_vla.c`,
`test/test_cast_vm_funcptr.c`, `test/test_typeof_comma_vm_stmt.c`.

### Fixed (2026-08-08, cproc scope/attribute/GNU-ternary session)

- **A tag or enum constant declared inside a function's parameter-type-list
  leaked into file scope permanently** (parser.c) — C11 6.2.1p4 gives an
  identifier declared inside a parameter list "function prototype scope":
  visible only within that parameter list and (for a definition) the
  function body, then reverting to whatever was visible before. rcc's
  `enum_consts`/`enum_htab` table had no scope save/restore around
  `declarator_params()` at all (unlike `locals`/`typedefs`/`tags`, which
  already have the identical checkpoint/restore pattern at every other
  scope boundary in the file), so `enum {A = 2}` inside `f`'s parameter
  list registered into the flat file-scope table and was never popped —
  the file-scope `enum {A = 1}` stayed permanently shadowed for the rest
  of the translation unit instead of reverting after `f`'s closing brace.
  Fixed by checkpointing `enum_consts`/`enum_htab` (via the existing
  `enum_scope_checkpoint()`/`enum_scope_restore()` helpers) once per
  top-level declarator in `program()`'s declaration loop, and restoring it
  at every exit of that declarator (prototype-only `;`, after a function
  body's closing `}`, and each comma-continuation), so the parameter
  list's own enum constants stay visible through the body and are then
  correctly popped.
  → found via cproc's `test/func-param-scope.c`
- **C23 `[[gnu::packed]]` / `[[__gnu__::packed]]` on a struct/union tag was
  silently ignored** (parser.c, `read_type_attrs()`) — the C23
  double-bracket attribute parser already handled the `namespace::name`
  syntax generically, but only to _skip past_ it; it never matched the
  attribute name against any known GNU attribute, so `gnu::packed` parsed
  cleanly and did nothing; the struct/union gained no packing at all
  (identical layout to the fully unpacked type) instead of the same effect
  as the legacy `__attribute__((packed))` syntax. Fixed by recognizing a
  `gnu`/`__gnu__` namespace with a `packed`/`__packed__` name and setting
  `attr->is_packed` (plus the `align=1` clamp the legacy path already
  applies), which struct/union member-layout computation already consumes
  via `struct_pack`.
  → found via cproc's `test/union-packed.c`
- **GNU `a ?: b` (omitted then-operand) evaluated `a` twice, and its
  correct usual-arithmetic-conversion result type was never actually
  reachable at codegen time** (codegen.c) — the parser already built the
  right AST (`node->then` is literally `node->cond`, or check*type's
  usual-arithmetic-conversion pass wraps it in an `ND_CAST` whose `lhs` is
  `node->cond`, exactly mirroring an ordinary `a ? a : b` ternary), but
  `ND_COND`'s codegen unconditionally called `gen(node->cond)` for the
  branch test \_and then separately* `gen(node->then)` for the true-branch
  value — since those are the same subexpression object, any side effect
  in the condition (e.g. `++x`) fired a second time on the true path.
  Fixed by detecting the omitted form (`node->then == node->cond`, or an
  `ND_CAST` wrapping it) and reusing the already-computed condition
  register instead of regenerating, applying the promotion cast in-place
  via a new `gen_cast_reg()` helper factored out of the `ND_CAST` case
  (previously inlined only inside `gen()`, now shared by both).
  → found via cproc's `test/conditional-omit.c`

New regression tests: `test/test_enum_param_scope_leak.c`,
`test/test_c23_attr_packed.c`, `test/test_gnu_ternary_omit_promote.c`.

### Fixed (2026-08-08, C23 enum type-modeling session)

- **C23 `enum tag : type` fixed underlying type wasn't complete until the
  whole enum finished parsing** (parser.c, `enum_specifier()`) — per C23
  N3030, a fixed-underlying-type enum is complete immediately after the
  `: type` specifier (before any enumerator, and even before the tag's
  own `{` if forward-declared), so it can be `sizeof()`'d or used via
  `typeof()` from _inside its own enumerator list_. rcc instead ran every
  enumerator's value through the generic int/expr-type/prev-type ladder
  (the same one used for a plain, non-fixed enum) and only retroactively
  overwrote every constant's type to the fixed type _after_ the closing
  `}` — so a self-referential `typeof(A)` used by a later enumerator in
  the same list (or `sizeof(enum E)` used by the very first one, e.g.
  `enum E : long long { A = sizeof(enum E), ... }`) still saw a
  provisional `int`/expression type, and `enum tag;`-only forward
  declarations never registered the tag's `Type*` before a subsequent
  `sizeof`/`typeof` needed it. Fixed by building the completed enum
  `Type` (and, when tagged, pushing its `EnumTag`) immediately once the
  `: type` is parsed and before the enumerator loop starts, so every
  enumerator gets that exact type from the start and self-references
  inside the body resolve correctly.
  → found via cproc's `test/enum-fixed.c`
- **`__builtin_types_compatible_p` treated any two enums of the same
  size/signedness as compatible, including two entirely separate
  declarations** (parser.c, `types_compatible_p_qual()`) — rcc's `Type`
  has no distinct `TY_ENUM` kind (an enum reuses its underlying integer
  `TypeKind`, flagged via `is_enum`), so the qualifier-stripped
  compatibility check's `default:` case (`a->size == b->size`) couldn't
  tell "two different `enum`s that happen to have the same underlying
  representation" from "the same `enum`, referenced twice" — two
  separately declared enums with identical enumerator values (even an
  anonymous `enum {...}` structurally matching a named one) were wrongly
  reported compatible, when C 6.2.7 makes enum identity nominal, not
  structural (the same rule struct/union already got right via `members`
  pointer identity). Fixed by adding a `Type *enum_id` field, set to the
  completed enum's own canonical `Type*` (and preserved across the
  plain-struct copies a bare `enum tag` type-name lookup makes), and
  having `types_compatible_p_qual()` require `a->enum_id == b->enum_id`
  whenever both sides are enums — an enum compared against a _non_-enum
  integer type of matching representation (e.g. `enum E : short` vs.
  plain `short`) is untouched by this and still falls through to the
  representation-only checks.
  → found via cproc's `test/enum-large-value.c`
- **Enumerator value → type promotion (no fixed underlying type) skipped
  the `long`/`unsigned long` tier, jumping straight from `int`/`unsigned`
  to `long long`/`unsigned long long`** (parser.c, `enum_specifier()`) —
  C23 6.7.3.4 (and matching GCC/clang behavior) picks each enumerator's
  individual type, and the enum's own overall type, as the narrowest of
  `int, unsigned int, long, unsigned long, long long, unsigned long long`
  that fits; on this LP64 target `long` and `long long` are both 8 bytes
  but are still distinct types, so always landing on `long long` produced
  a type `__builtin_types_compatible_p` correctly refused to call
  equivalent to plain `long` (e.g. `enum { A = -0x80000001L, B }`'s
  overall type came out `long long` instead of `long`). Also, min/max
  tracking across enumerators compared raw `int64_t` bit patterns
  directly, so a wide _unsigned_ value using the top bit (e.g.
  `B = -1ull`, i.e. `UINT64_MAX`) compared as a small _negative_ number
  and corrupted the whole enum's range computation. Fixed by tracking
  min/max as `__int128`, widened per-enumerator according to that
  enumerator's own signedness (so `UINT64_MAX` is tracked as itself, not
  as `-1`), and by adding the missing `long`/`unsigned long` tier between
  `int`/`unsigned` and `long long`/`unsigned long long` when picking both
  the enum's overall type and (already-correct) each enumerator's own
  during-processing type.
  → found via cproc's `test/enum-large-value.c`

New regression test: `test/test_enum_c23_wide_and_fixed.c`.

### Fixed (2026-08-08, root-cause/test_cproc-completion session)

**test_cproc now fully passes** the batch harness (`make check`:
196/196; the full `cctest_cproc.bash` conformance smoke-test corpus:
168/168 compile-and-run clean) — but the original TODO.md diagnosis
("expected specific operator" on `_BitInt(total * 3)`, attributed to
missing `_BitInt` support) turned out to be a **misdiagnosis carried
over from an earlier, different failure point**: that exact error text
is test*c23doku's `brute_force.c`, not anything in cproc's own corpus.
test_cproc's \_actual* blocker, found by bisecting cproc's own build down
to a single miscompiled object file, was a completely unrelated
pre-existing integer-literal bug (below) that had nothing to do with
`_BitInt` at all. Real C23 `_BitInt(N)` support (parsing, `wb`/`WB`
literal suffixes, `sizeof`/`typeof`/type-compatibility, up to 128-bit
codegen reusing native/`__int128` register paths) was added regardless,
since it's genuinely useful C23 conformance and cproc's own
`test/bitint-constant.c` needs it — but it was never test_cproc's
critical-path bug.

- **Hex/octal/binary integer-literal type selection compared the raw
  _signed_ bit pattern against `INT_MIN`/`INT_MAX` instead of the
  literal's true (always non-negative) magnitude** (parser.c, `new_num()`)
  — a literal token is never negative by construction (unary `-` is a
  separate, later operator), so a hex/octal constant whose value exceeds
  `INT64_MAX` (e.g. `0xffffffffffffffff` == `UINT64_MAX`) merely _looks_
  negative once its bit pattern is reinterpreted as a signed `int64_t`
  (`-1`). The old ladder's very first check, `val >= INT32_MIN && val <=
INT32_MAX`, used that raw signed value directly — `-1` trivially
  satisfies it — so a genuinely 64-bit-magnitude hex constant was
  silently typed as plain 4-byte `int` (`sizeof(0xffffffffffffffff) ==
4`), truncating every later use of it to 32 bits. Separately, even once
  a value correctly fell through past the `int` check, hex/octal/binary
  constants (C11 Table 6's _non-decimal_ candidate list, which — unlike
  decimal — includes the unsigned type of the same rank) never actually
  reached an unsigned fallback: the ladder's final `else` branch always
  picked signed `long long` regardless of radix. Rewrote the whole
  ladder (covers every suffix combination: none, `u`, `l`, `ll`, and
  their combinations) to compare magnitudes via the literal's _unsigned_
  64-bit representation throughout, and to offer the unsigned tier at
  every rank for non-decimal constants once the signed one overflows.
  → found by bisecting cproc's own object files (rcc-compiled vs.
  gcc-compiled) down to `expr.c`'s `inttype()` — the function that
  computes a _C23 `_BitInt` literal's own width_ via `0xffffffffffffffff
  > > (64 - i)`for every candidate`i`, entirely independent of the
literal's actual magnitude; since `0xffffffffffffffff`(no suffix)
always mis-typed to plain 4-byte`int`(bug above), every occurrence of
that shift silently became a 32-bit shift-by-a-32-bit-value instead of
the intended 64-bit logical shift, corrupting the width computation for
*every*`\_BitInt`literal cproc's own compiler (built by rcc) ever
processed — including the tiny ones in its own`test/bitint-constant.c`self-test, which was the actually-failing step`make check` reported.
- **`is_typename()` didn't recognize `_BitInt` as a type keyword**
  (keywords.gperf/keyword_ids.h) — `_BitInt` was only matched via
  `equalc()` string comparison inside `declspec()`'s own keyword chain,
  never registered in the `KW_TYPE`-flagged keyword table `is_typename()`
  consults for cast/`sizeof`/`_Generic`-association disambiguation, so
  `(_BitInt(N))expr` and similar cast-position uses of `_BitInt` failed
  to parse as a type at all. Registered as a real `KW_TYPE` keyword,
  matching `_Bool`'s existing entry.
- **`ND_DEREF` rejected a second `*` applied to an already-function-typed
  expression** (type.c) — C11 6.5.3.2p4 makes repeated `*` on a function
  designator idempotent (`(***f)()` == `f()`, since a function-typed
  rvalue implicitly decays back to pointer-to-function before any
  operator sees it, the same "decay" arrays undergo), but rcc's type
  check for `*` only accepted `TY_PTR`/`TY_ARRAY`/`TY_VLA` operands — the
  _first_ `*` on a function pointer correctly yields the bare function
  type back, but the _second_ `*` then saw that bare `TY_FUNC` operand
  and rejected it outright ("cannot apply '\*' to a non-pointer type").
  Fixed by special-casing a `TY_FUNC` operand to yield the same type back
  unchanged.
  → found via cproc's `test/func-noreturn.c`
- **C23 "label may precede a declaration or the end of a compound
  statement" was only implemented for plain identifier labels, not
  `case`/`default`** (parser.c) — the C23 relaxation (previously every
  label required a following _statement_, needing `case 0: ;` as an
  empty-statement workaround) had already been implemented for ordinary
  `label:` but not extracted to the near-identical `case`/`default`
  parsing blocks, so `switch (x) { case 0: }` (a case label immediately
  before the closing `}`) failed with "expected an expression" instead
  of parsing as an empty-statement case.
  → found via cproc's `test/unreachable.c`
- **A wide-string-literal (`u`/`U`/`L`/C23 `u8"..."`) whole-array
  designated initializer corrupted the token stream for any _subsequent_
  designator in the same braced initializer** (parser.c,
  `global_init_member()`/`local_init_member()`) — both functions' bypass
  check that routes a string-literal initializer value straight to
  `global_init_one()`/`local_init_one()` (which correctly handles narrow
  _and_ wide strings) only excluded `TY_CHAR`-based arrays from the
  generic "flatten into per-element initializers" fallback path; a
  wide-char-based array target (`unsigned short`/`unsigned int` element,
  string-literal _value_) fell through to that fallback instead, which
  misparses the whole string token as a single scalar initializer
  element and leaves the token cursor in the wrong place for whatever
  designator follows (`.s = u"ab", .s[2] = 99,` — the `.s[2]` designator
  after a wide-string whole-array init failed with "expected an
  expression"). Fixed by widening the bypass condition to any
  string-literal value, matching `global_init_one()`'s own already-
  correct narrow/wide dispatch.
  → found via cproc's `test/initializer-replace-static-string-wide.c`
- **A wide string literal's _expression_ type was a pre-decayed pointer,
  not the array type C requires** (parser.c, `primary()`'s `TK_STR`
  branch) — the plain-`char`/pre-C23-`u8` cases already correctly built
  `array_of(ty_char, len+1)` ("Use array type so sizeof works correctly;
  decays to pointer where needed" per the existing comment), but every
  wide-prefix case (`L`, `u`, `U`, C23 `u8`) built `pointer_to(elem_ty)`
  directly instead — a real bug independent of `typeof`, since `sizeof
L"ab"` and ordinary array-decay-dependent uses were _also_ wrong, just
  less commonly hit. `typeof()` in particular must see the un-decayed
  array (`typeof(u8"abc") == unsigned char[4]`, not `unsigned char *`)
  since `typeof`/`__typeof__` doesn't apply lvalue conversion. Fixed by
  building `array_of(elem_ty, N+1)` for every prefix, computing `N` as
  the _decoded codepoint count_ (via the same UTF-8 decode loop
  `global_init_one()`'s wide-string writer already uses to count
  elements), not the raw UTF-8 byte length.
  → found via cproc's `test/string-u8-type.c`
- **Function-type parameter comparison never stripped the parameter's own
  top-level qualifiers** (parser.c, `type_equal()`'s `TY_FUNC` case) — C11
  6.7.6.3p15 requires a parameter's own top-level qualifiers to be
  disregarded for function-type compatibility (only qualifiers on what a
  pointer parameter points to matter), including recursively when the
  parameter is itself a function type reached through decay (`int(const
double)` as a parameter decays to `int(*)(double)`, whose _own_
  parameter `const double` must still compare compatible with plain
  `double`). The parameter-list comparison loop called `type_equal()`
  directly on each pair without ever clearing this qualifier, so any
  qualified scalar parameter — nested arbitrarily deep through
  function-pointer-parameter chains — broke redeclaration matching.
  Fixed by comparing qualifier-stripped shallow copies for each
  parameter pair.
  → found via cproc's `test/compatible-function-types.c`
- **`__builtin_types_compatible_p` was a hand-rolled, incomplete
  scalar/pointer/array/struct switch** (parser.c) — missing a `TY_FUNC`
  case entirely (fell through to a `size == size` default, meaningless
  for function types); its `TY_ARRAY` case compared the unused
  `->array_len` field (never populated by `array_of()` — only
  `vla_of()`'s constant-VLA fallback sets it — so it read `0` for both
  sides and treated _every_ array pair, regardless of actual declared
  length, as compatible); and its `TY_PTR`/`TY_ARRAY` cases only checked
  one level of base-type shape (kind/size/unsigned/qual), not full
  recursive structural compatibility. Rewrote entirely as two new
  functions, `types_compatible_p()` (disregards the two top-level
  argument types' own qualifiers, matching GCC's documented behavior)
  delegating to `types_compatible_p_qual()` (fully recursive, qualifier-
  aware structural compatibility through `TY_PTR`/`TY_ARRAY`/`TY_FUNC`/
  `TY_STRUCT`/`TY_UNION`/`TY_COMPLEX`/`TY_BITINT`, with C11 6.7.6.3p15
  parameter-qualifier-stripping built in and the same
  correct-array-length computation as the fix above).
  → found via cproc's `test/compatible-array-types.c`
- **The `test_cproc` batch-harness step never actually ran end-to-end** —
  `test/linux_thirdparty.bash`'s `test_cproc()` function (imported
  wholesale from slimcc's own test harness, see commit `86508a8f`)
  references a companion script, `test/cctest_cproc.bash`, that was never
  copied over — every batch run's `test_cproc` classification prior to
  this session was actually failing on "script not found", not on
  anything specific to cproc's own build. Ported the missing script
  verbatim from slimcc's upstream `scripts/cctest_cproc.bash` (a smoke
  test that compiles + runs, or verifies expected-to-fail, every file in
  michaelforney/cproc's own `test/*.c` corpus directly with `$CC`,
  independent of cproc's own `make check`), adding two rcc-specific
  entries to its `skip_files` (see below), matching the exact convention
  the file already uses for slimcc's own two skips.
- **Automatic/VLA storage over-alignment beyond the ABI-guaranteed 16
  bytes is unimplemented** — `alignas(32) char x;` (a plain stack local)
  and `char alignas(64) a[n];` (a VLA) both need the _stack frame itself_
  dynamically realigned in the prologue (a distinct RSP-relative base for
  every local beyond the natural 16-byte guarantee, while incoming-
  argument addressing, VLA/`alloca` interaction, and Windows SEH/ARM64
  AAPCS64 unwind info all still need their existing RBP-relative
  assumptions preserved) — a large, invasive, cross-cutting prologue/
  epilogue redesign, not attempted this session. Documented as a known
  gap via `cctest_cproc.bash`'s `skip_files` (`alignas-local-strict`,
  `alignas-vla-strict`), matching how the same file already documents
  slimcc's own two unimplemented-builtin skips (`builtin-inff`,
  `builtin-nanf`).
- **`eval_const_expr()`'s `ND_EQ`/`ND_NE`/`ND_LT`/`ND_LE` compared
  operands at raw 64-bit width instead of their actual common type's
  width** (parser.c) — found _after_ landing the C23 enum-type-modeling
  fix above (once enum constants started actually being typed
  `unsigned`/`long`/etc. instead of always `int`, this pre-existing bug
  went from dormant to load-bearing): comparing an `unsigned int` value
  against a same-width `int` value whose bit pattern has the top bit set
  (e.g. `(int)0x80000000` == `-2147483648`, sign-extended to
  `0xFFFFFFFF80000000` as a 64-bit `long long`) against the _bit-
  identical_ unsigned 32-bit value `0x80000000` (`2147483648` as a
  64-bit `long long`) via `(unsigned long long)lhs == (unsigned long
long)rhs` compares two different 64-bit patterns, not the equal
  32-bit ones they both represent — the wider sign extension was never
  re-truncated to the comparison's actual common width before the
  unsigned reinterpretation. Only manifests at `-O1`+ (opt.c's `ND_IF`
  dead-branch-elimination is the only caller that constant-folds a
  comparison at all — at `-O0` the same comparison runs as ordinary,
  always-correct runtime code). Fixed by truncating each operand to the
  _wider_ of the two operand types' own byte width before the unsigned
  reinterpretation (new `uval_at_width()` helper), for all four
  comparison operators.
  → found via `test_enum_c23_wide_and_fixed`'s own `PU_A != (int)
0x80000000` check regressing at `-O1` after the enum fix landed.

New regression tests: `test/test_const_fold_signed_unsigned_cmp.c`
(the `eval_const_expr` width bug, verified at both `-O0` and `-O1`
against real gcc). The five C23 `_BitInt`/parser fixes above landed
alongside four parallel subagent sessions (documented separately above:
"cproc scope/attribute/GNU-ternary", "C23 enum type-modeling",
"VM-type evaluation-order", "cproc array/VLA-param type-modeling") that
together closed out every remaining `cctest_cproc.bash` failure.
**Full suite verified**: TCC 118/118, Unit tests 189/189, C-testsuite
220/220, NCC Compliance 15/15, Torture 3605/3609 (100% of non-skipped),
Dg-error 34/34, Link tests 5/5 — 0 failed overall, on native Linux
x86-64; mingw and arm64 cross-compile targets also re-verified clean.
`test_cproc`'s own batch-harness run: `PASS=1 FAIL=0`.

### Fixed (2026-08-08, mingw cross-compile verification session)

The prior session's claim that "mingw and arm64 cross-compile targets
also re-verified clean" was **wrong for mingw** — a full `run_tests.exe
./rcc.exe --all --parallel` run under Wine surfaced one genuine
regression from the same session's C23 enum type-modeling fix, invisible
on native Linux/arm64 because both are LP64.

- **The enum overall-type ladder's `long`/`unsigned long` tier used
  hardcoded `INT64_MAX`/`UINT64_MAX` bounds instead of the platform's
  actual `long` size** (parser.c, `enum_specifier()`) — `long` is 8
  bytes on LP64 (Linux/macOS) but only 4 bytes on LLP64 (Windows/mingw,
  same range as `int`). The ladder's `long` bounds check
  (`min_val >= -INT64_MAX-1 && max_val <= INT64_MAX`) assumed the LP64
  case unconditionally, so on mingw a value needing genuine 64-bit
  signed range (e.g. GCC torture's `c23-enum-1.c`:
  `enum e1 { e1a = -__LONG_LONG_MAX__ - 1, ..., e1c = __LONG_LONG_MAX__ }`)
  wrongly satisfied the check and picked 4-byte `ty_long` instead of
  8-byte `ty_llong`, truncating the enum's values and failing
  `static_assert (sizeof (enum e1) >= sizeof (long long))`. Fixed by
  computing the tier's bounds from `ty_long`/`ty_ulong`'s actual
  `->size` (matching the same LP64-vs-LLP64 pattern already used
  elsewhere in the codebase, e.g. the integer-literal ladder). On
  mingw this correctly makes the `long` tier unreachable for anything
  not already representable in `int` (since `long`'s range equals
  `int`'s there), skipping straight to `long long` — matching real
  GCC's own behavior on Windows.
  → found via `test/torture/c23-enum-1.c` regressing under
  `./mingw-test.sh` (0 mingw-cross failures before this session's enum
  work, 1 after — caught by actually running the full mingw suite
  rather than trusting the untested claim).
- **`test/test_enum_c23_wide_and_fixed.c`'s own `PromoteLong`/
  `PromoteUnsignedLong` cases hardcoded the LP64 answer** (`long`/
  `unsigned long`) for a value needing more than 32 signed bits — a
  test-only portability bug, not an rcc bug, but it would have failed
  the same way as a false regression once the ladder fix above made
  mingw's `long` tier correctly unreachable. Fixed by selecting the
  expected type via `__LONG_MAX__ > __INT_MAX__` (matching
  `c23-enum-1.c`'s own `TYPE_CHECK` convention), so the test asserts
  `long` on LP64 and `long long` on LLP64.

**Full suite re-verified after the fix**: mingw cross — TCC 118/118,
Unit tests 187/187 (188 total, 1 pre-existing skip unrelated to this
bug), C-testsuite 220/220, NCC Compliance 15/15, Torture 3574/3574
non-skipped (100%), Dg-error 34/34 — 0 failed. Native Linux x86-64 and
arm64 cross (both LP64, unaffected by this LLP64-only bug) re-verified
clean: Linux Torture 3605/3609 (100% non-skipped), 0 failed overall.

### Session summary (2026-08-09, ARM64 stur/ldur frame-offset session)

Picked the "ARM64 small-struct return ABI" TODO item as the next
scoped, already-diagnosed bug to fix; found on investigation that the
documented classification gap was already fixed (uncredited, in an
earlier session) and the entry was stale, but the regression evidence
behind it (`00204.c`) was still genuinely failing for an unrelated
reason — see "Fixed rcc bug: ARM64 `stur`/`ldur` frame-offset immediate
overflow" above for the real root cause and fix. `00204.c` now passes
byte-for-byte at both `-O0` and `-O1`.

### Fixed rcc bug: `__attribute__((section("name")))` on a global was silently unparsed

`read_type_attrs()` had no case at all for the `section`/`__section__`
GCC attribute — it fell through to the generic "unrecognized attribute,
skip its parenthesized argument" path, so a global declared with a
`section("name")` attribute landed in the ordinary `.data`/`.rodata`/
`.bss` like any other global, never in a section literally named
`"name"`. Any code relying on the common linker-collected-array idiom
(`extern char __start_name[]; extern char __stop_name[];` — real GNU
ld automatically synthesizes those two symbols for any section whose
name is a valid C identifier, bracketing every input object's
contribution to it) failed at link time with "undefined reference to
`\_\_start_name'": nothing had ever created a section by that name for
the linker to synthesize boundaries around.

Fixed in three places:

- **parser.c** — `read_type_attrs()` now recognizes `section("name")`/
  `__section__("name")` (mirroring the existing `alias("target")`
  parsing exactly) and threads the name through a new
  `pending_section_name` (matching `pending_alias_target`'s own
  set-at-parse/consume-at-declaration/reset-at-every-declaration-
  boundary pattern) onto the declared global's new `LVar::section_name`
  field.
- **codegen.c** — a global with a non-NULL `section_name` is now routed
  to a custom ELF section via the existing (previously inline-asm-only)
  `objfile_find_or_add_section()`, with `SHF_ALLOC` (plus `SHF_WRITE`
  unless the variable is `const`-qualified) instead of the default
  `.data`/`.bss`/`.rodata` placement — always through the "has
  initializer data" emission path, even for a zero-initialized
  section() global, since a named section is virtually always a
  single deliberately-isolated marker object, not a candidate for the
  NOBITS/BSS space optimization. Separately, `cg_set_section()`'s
  `default:` case for any section id ≥ `SEC_NUM` (a custom section)
  was silently defaulting to `.text` instead of resolving the section's
  own growable buffer — a latent bug independent of this feature
  (nothing previously routed a _global variable's data_ through a
  custom section id at all, only inline-asm's own already-section-aware
  emission path) that would have silently corrupted `.text` with
  strayed variable bytes the moment anything did.
- **link_elf.c** — rcc's own native ELF linker now synthesizes
  `__start_<name>`/`__stop_<name>` for any section whose name is a
  valid C identifier, matching real GNU ld, inserted right before the
  existing "identify unresolved undefined symbols" pass (all input
  objects are already loaded by this point, so every named section's
  final size is already known — no second, post-layout pass needed).
  Previously only the GCC/external-linker fallback path could resolve
  such a reference at all (via the real system `ld`), so source using
  this idiom only worked when rcc's native linker happened to bail out
  to that fallback for some unrelated reason.

→ found via GCC c-testsuite's `00204.c` investigation surfacing the
underlying rcc-arm64 struct-return-ABI TODO item as already fixed (see
above); chasing `00204.c`'s own remaining SIGSEGV (the real, unrelated
`stur`/`ldur` bug documented above) led to picking the next open TODO
item, `test_scrapscript`'s "rcc compile fails (exit 1) during Python
test harness" — which turned out to actually be a **link** failure
(`undefined reference to '__start_const_heap'`/`'__stop_const_heap'`)
from scrapscript's own runtime GC placing a "const heap" boundary
marker via `__attribute__((section("const_heap")))`, specifically so
`in_const_heap()` can tell a constant, pre-allocated object apart from
one living in the mutable GC heap.

**Note**: while isolating a regression test for this fix, also found a
separate, pre-existing, unrelated native-linker gap — any use of
`fprintf(stderr, ...)` (confirmed with a three-line minimal repro, no
`section()` involved at all) makes `rcc_link()`'s native ELF linker
return -1 and silently fall back to the GCC/system-`ld` path. Not
investigated further this session (the fallback itself works
correctly, and this session's own regression test avoids the trigger
by using `printf` instead so it still exercises the native-linker path
being fixed here) — worth a future session's attention since it's
presumably a very common pattern to trip over.

New regression test: `test/test_attribute_section.c` — three
separately-declared `section("name")`/`__section__("name")` globals
(exercising both spellings), verifying `__stop_name - __start_name`
correctly brackets all three contiguous entries (not just a single
lone marker), every entry's data is intact and in the right place, and
an ordinary global with no `section` attribute is unaffected. Compiles
and links via rcc's own native linker on both x86-64 and arm64 Linux
(confirmed by the _absence_ of an `RCC_LINK_DEBUG=1` fallback
message — a stronger check than merely "the program produces the
right answer somehow", which the GCC-fallback path alone could also
satisfy) and via mingw cross (through mingw's own `ld`, which supports
the identical `__start_`/`__stop_` PE-COFF convention).
**Full suite verified after the fix**: native Linux x86-64 — Torture
3605/3609 (100% non-skipped) 0 failed, Dg-error 34/34, Link 5/5, 0
failed overall; arm64 cross — c-testsuite 220/220, Torture 3599/3609
(6 pre-existing unrelated runtime failures, unchanged from the
`stur`/`ldur` fix above), Dg-error 34/34, 0 new failures; mingw
cross — Torture 3574/3578 (100% non-skipped) 0 failed, Dg-error 34/34,
0 failed overall.

### Fixed rcc bug: custom `section()` global had no ELF `sh_addralign`, corrupting scrapscript's GC

The `section()` attribute fix above (committed same day) correctly
placed each `__attribute__((section("name")))` global's _bytes_ — but
`objfile_find_or_add_section()` always created the section with ELF
`sh_addralign = 1` ("no constraint"), regardless of what was placed in
it. rcc's own native ELF linker was then free to place that section's
first byte at _any_ file offset — including one only 4-byte aligned —
silently breaking the natural alignment every `uintptr_t`/pointer/
`size_t` field inside the section's contents actually needs.

scrapscript's GC uses a classic tagged-pointer scheme: a heap object's
address always has its low 3 bits clear (`kObjectAlignment = 8`), and
`ptrto()`/`as_heap_object()` set/clear a single low tag bit to mark
"this is a heap pointer" vs "immediate small int". A `const_variant_0`
global (`struct variant`, holding a `uintptr_t` tag + a `struct
object*` value — both needing 8-byte alignment) placed via
`__attribute__((section("const_heap")))` landed at `0x405514` in one
run — only 4-byte aligned. The struct's own _bytes_ were still
byte-perfect (verified with `readelf -x`/gdb: tag=`TAG_VARIANT`,
`variant.tag=Tag_foo`, `variant.value` all correct) and the tagged
pointer handed to `print()` was numerically correct too — but
`is_heap_object()`'s `(uword)obj & kPrimaryTagMask(7) == 1` check
silently failed on that misaligned address, so `print()` fell through
every `is_*()` branch and hit its `abort()` "unknown tag" fallback —
17/17 remaining `test_scrapscript` failures were exactly this SIGABRT,
all originating in `scrap_main()`'s very first `print()` call.

Fixed by threading a real per-section alignment through to the ELF
writer:

- **obj.h/obj.c** — `ExtraSection` gained an `align` field (default 1,
  raised via the new `objfile_section_align()`, never lowered — a
  section can receive multiple globals with different alignment
  needs, so it tracks the max).
- **codegen.c** — after resolving a `section()` global's target
  section id, calls `objfile_section_align(cg_obj, data_sec,
var->ty->align)` so the section's requirement reflects every global
  ever placed into it.
- **asm.c** — an explicit `.balign`/`.align`/`.p2align` directive
  targeting a custom (inline-asm `.section`-declared) section now also
  raises that section's `sh_addralign`, matching real GAS's own
  behavior (previously only the byte offset _within_ the section's
  buffer was aligned, not the section's own load-address requirement).
- **elf_write.c** — the extra-section file-offset layout loop now
  aligns each section's starting offset to `max(16, its own
sh_addralign)` instead of a blanket 16, and the ELF section header
  is written with the real alignment instead of a hardcoded `1`.
  (rcc's own linker load address is `image_base + file_offset` with an
  always-page-aligned `image_base`, so aligning the file offset is
  equivalent to aligning the final virtual address for any alignment
  up to a page.)

Scoped to the ELF writer only (native Linux + arm64 cross) since that's
where the actual failure lived; `macho_write.c`'s extra-section path
already unconditionally uses 8-byte alignment (a `section()`-holding
global that needs more than that on macOS is a pre-existing,
unexercised gap, out of scope here), and `coff_write.c`/PE-COFF
characteristics don't encode a per-section alignment at all yet (also
pre-existing, not triggered by any current test).

→ found by tracing `test_scrapscript`'s remaining 17 `SIGABRT`s (see
the "partially fixed" note in the section() writeup above) with gdb:
confirmed the tagged pointer and the underlying struct bytes were both
correct at the point of failure, then `readelf -S` on the crashing
binary showed `const_heap`'s `sh_addralign` was `1` and its actual
load address only 4-byte aligned.

Regression test: extended `test/test_attribute_section.c` with an
explicit `(uintptr_t)__start_my_registry % _Alignof(struct
registry_entry) == 0` assertion (`struct registry_entry` already
contains a `const char *` field, so it requires 8-byte alignment).
Confirmed this assertion fails (`exit 5`) on the pre-fix binary and
passes on the fixed one. Full `test_scrapscript` suite (33 tests, 1
skipped) now passes 100%, up from 17/33 failing.
**Verified**: native Linux x86-64 — `test_attribute_section` PASS,
full `test_scrapscript` compiler_tests suite 32/32 passed (1 skipped);
mingw cross and arm64 cross — `test_attribute_section` PASS via
`mingw-test.sh`/`arm64-test.sh`.

### Added: `__attribute__((section("SEG,SECT")))` support for Mach-O (macOS)

The `section()` attribute fix above was ELF-only; the Apple branch of
`test_attribute_section.c` was left as a stub (`(void)e1; ...; return
0;`) because macho_write.c's custom-section support pre-dated this
session and only handled a single flat name (no explicit segment),
and link_macho.c had no concept of a boundary-symbol synthesis at
all. Implemented properly this session, entirely from code review +
local structural verification -- no real macOS available in this
sandbox; both touched files (macho_write.c, link_macho.c) are only
ever compiled by the project's own build when `$(CC) -dumpmachine`
contains "apple" (see Makefile's `SRCS +=` split), so CI's
macos-latest job is the only real test surface.

- **macho_write.c** — `macho_seg_sect_name()` replaces the old
  `macho_section_name()`/`macho_segname_from_flags()` pair: a
  `section()` string containing a comma (Apple's required
  "SEGMENT,SECTION" form, e.g. `"__DATA,const_heap"` -- a bare
  single-component name is a real clang error on Mach-O) is split
  verbatim into the two fixed 16-byte segname/sectname fields; a
  comma-less name (ported GAS `.section` asm with no segment) keeps
  the previous code/data-flags-based inference.
- **link_macho.c** (`link_load_object`) — a loaded section whose
  _sectname_ half doesn't start with `__` (Apple reserves that prefix
  for its own system sections -- `__cstring`, `__common`,
  `__la_symbol_ptr`, ...) is necessarily a user's own `section()`
  global; kept as its own distinct internal `"SEG,SECT"`-named
  section instead of folding into the generic `.data`/`.rdata`
  bucket every _other_ non-well-known section still uses.
- **link_macho.c** (`link_macho`) — two additions:
  1. Boundary-symbol synthesis for ld64/clang's own
     `section$start$SEG$SECT`/`section$end$SEG$SECT` linker-symbol
     convention (the Mach-O equivalent of ELF's `__start_`/`__stop_`,
     used via `extern char x[] __asm("section$start$__DATA$name")`
     -- a plain `__start_name` has no meaning on Mach-O at all).
     Runs _before_ the existing external-symbol-strategy scan: an
     unresolved boundary reference is loaded via a GOT-relative
     instruction and would otherwise look like a genuine external
     symbol, bouncing the whole link to the system linker.
  2. The `mo_secs` write-side classification recognizes an internal
     `"SEG,SECT"` name (set by the loader change above) and emits
     that exact segment/section pair instead of the generic
     `__DATA,__data` fallback.

`test/test_attribute_section.c` rewritten with a real (much smaller)
`#ifdef __APPLE__` split instead of the old two-full-`main()`-bodies
stub: only the `section()` argument string and the boundary-symbol
`extern` declarations differ per platform (`__asm("section$start$...")`
labels on Apple); the entire `main()` body -- including the alignment
regression check from the fix above -- is shared and unconditional.

**Verification** (no real macOS in this sandbox, see above):

- `link_macho.c` compiles clean standalone on Linux (it has no
  `#ifdef __APPLE__` file guard, unlike macho_write.c, so the
  project's real build never exercises it here either -- confirmed
  with a direct `gcc -c src/link_macho.c`).
- `macho_write.c` (entirely `#ifdef __APPLE__`-gated, so _never_
  compiled by this project's own native/mingw/arm64 builds) syntax-
  checked clean with `gcc -fsyntax-only -D__APPLE__` -- safe since it
  only uses portable `<stdio.h>`/`<string.h>`/`<stdint.h>`, no real
  Apple SDK headers.
- Compiled `test_attribute_section.c` through rcc itself with
  `-D__APPLE__` forced (still targeting rcc's real ELF backend, since
  this sandbox has no Mach-O target) to exercise the _parser/codegen_
  side of the Apple branch: `readelf` on the result confirms the
  section is correctly named `__DATA,my_registry` (8-byte aligned,
  48 bytes = 3 \* 16-byte entries) and the two `extern char x[]`
  declarations correctly emit as undefined symbols literally named
  `section$start$__DATA$my_registry`/`section$end$__DATA$my_registry`
  -- exactly the names link_macho.c's new synthesis pattern-matches.
- `test_attribute_section` still PASSes on native Linux x86-64, mingw
  cross, and arm64 cross (unaffected -- neither touched file is part
  of any of those three builds).
- Pushed to CI; the macos-latest job is the authoritative check for
  this change and needs to be watched explicitly.

### Fixed (2026-08-09, issue #4 continuation — 7 bugs via scout-parallel triage)

Continued triaging remaining test/third_party/TODO.md failures (GitHub
issue #4's checklist) by running a fresh batch of previously-unchecked
small/medium projects and dispatching parallel read-only scout
investigations into the most promising leads. Six genuine rcc bugs
found and fixed, all with regression tests, full suite verified after
each:

1. **declarator() SIGSEGV on an unclosed `(`** (parser.c) — the C11
   6.7.6p3 nested-paren disambiguation recursed into declarator() with
   the terminal TK_EOF token once its matching-paren scan ran out of
   input; TK_EOF's own `->next` is NULL (the lexer's genuine
   end-of-list sentinel), and the immediately following `tok->next`
   dereference flowed that NULL into skip_attributes()/
   read_type_attrs(), segfaulting on `tok->kw`. Found via ksh93's own
   AT&T ast-open build probe (`src/cmd/INIT/C+probe`), which
   deliberately compiles a bare `(` as a negative-compilation-check.
   Fixed by diagnosing "expected ')'" instead of recursing into an
   exhausted token stream.
2. **CRLF backslash-newline macro continuation never spliced**
   (preprocess.c) — `splice_lines_with_counts()` only matched `\`
   immediately followed by `\n`, missing the `\` `\r` `\n` form used by
   CRLF-terminated third-party sources. Found via test_unqlite's
   CRLF-terminated amalgamated `unqlite.c`.
3. **`__attribute__((mode(TI)))` unrecognized, plus a broader trailing-
   mode()-attribute leak** (parser.c) — TI (128-bit) was missing from
   the QI/HI/SI/DI mode table; fixing it surfaced a pre-existing bug
   where a _trailing_ mode() attribute (GCC's own convention, written
   after the identifier) was parsed but never applied in that
   declarator() call, leaking the pending_mode flag into whatever
   declarator() ran next and silently retyping an unrelated
   declaration. Factored a shared apply_pending_mode() helper called
   from every declarator() exit path. Found via test_libtommath's
   `typedef unsigned long mp_word __attribute__((mode(TI)));`.
4. **`__builtin_cpu_init()` unimplemented** (preprocess.c + parser.c) —
   GCC/clang's companion to the already-implemented
   `__builtin_cpu_supports()`; added as a true no-op stub (rcc's
   `__rcc_cpu_supports` re-queries cpuid directly every call, no cache
   to populate) mirroring the existing wiring exactly. Found via
   test_libucl's bundled mum.h.
5. **`infer_array_type()` sized a pointer array by a string literal's
   length** (parser.c) — `T *arr[] = { "literal" }` (a correctly-typed
   ONE-pointer array) was misclassified as C11 6.7.9p14's char-array-
   sized-by-string-literal case, with no check that the element type
   was actually scalar/non-pointer, unlike every sibling STRLIT-sizes-
   array call site in the file. Fixed with the same scalarish*base-
   style guard (excludes ARRAY/STRUCT/UNION/PTR). Found via
   test_flatcc's gperf-generated `fb_reserved_kw_vec_prefixes[] = {
   "vec*" }`, which crashed calling strlen() on garbage 4 elements past
   the array's real end.
6. **Plain `gnu_inline` function export** (codegen.c) — `fn_exported`
   implemented pure C99 inline linkage unconditionally; real GCC's
   GNU89 semantics (what `__attribute__((gnu_inline))` explicitly opts
   into) mean a plain (non-extern) gnu_inline function DOES get an
   ordinary global-linkage definition. Missing this made gperf-
   generated code using the common `#ifdef __GNUC_STDC_INLINE__
__attribute__((gnu_inline)) #endif` portability idiom compile fine
   per-TU but emit as a local symbol, invisible to any other TU — only
   surfacing at final-executable link time (a shared-library link
   tolerates the undefined symbol). Found via test_hoedown's gperf-
   generated `html_blocks.c`'s `hoedown_find_block_tag`.
7. **`int64_t`/`intptr_t`/`intmax_t` (+ unsigned) used `long long`
   unconditionally** (include/stdint.h) — on LP64 targets (native
   Linux/macOS x86-64 and arm64), these must be `long`/`unsigned long`
   to match glibc's own convention (`bits/types.h`'s
   `__int64_t`/`__intmax_t`, guarded on `__WORDSIZE == 64`) — the same
   class of bug the file's existing `ptrdiff_t` note already documents,
   just for four more names. A TU pulling in both this header and a
   real glibc header re-declaring these names (very common — countless
   system headers transitively include `<bits/stdint-intn.h>`) hit a
   real "conflicting types" error at every int64_t/intmax_t/intptr_t-
   parametered function once prototype/definition redeclaration
   diagnostics were added. Kept `long long` on `_WIN32` (LLP64, where
   `long` is only 4 bytes; matches MSVC/mingw's own convention there —
   this branch is unchanged from before, so mingw is unaffected by this
   fix). Found via test_libtommath's `MP_INIT_INT(mp_init_i64,
mp_set_i64, int64_t)`, whose macro-expanded definition disagreed
   with its own header-declared prototype once glibc's transitively-
   included `<bits/types.h>` re-typedef'd `int64_t` as `long`.

5 of 5 deep-dived third-party targets are now confirmed fixed:
test_unqlite, test_libucl, test_hoedown, test_flatcc, test_libtommath
all build clean; the `gnu_inline` fix has no single-project home (found
via test_hoedown but is a general codegen bug, verified via a dedicated
two-TU link test instead).

New regression tests: `test/test_err_unclosed_paren.c`,
`test/test_crlf_line_continuation.c` (genuine CRLF file),
`test/test_mode_ti_attribute.c`, `test/test_builtin_cpu_init.c`,
`test/test_ptr_array_strlit_size.c`, `test/test_stdint_int64_glibc_abi.c`,
plus `test/test-link.sh` case 7 (gnu*inline needs real two-TU linking,
which the single-file `test/test*\*.c` harness can't express).

**Full suite verified after every fix**: TCC 118/118, Unit 197/197,
Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Link 6/6
(native Linux x86-64); C-testsuite 220/220 on both mingw cross and
arm64 cross, plus the specific new/affected unit tests confirmed
passing on both cross targets (`_WIN32` branch of the stdint.h fix is
behaviorally unchanged on mingw by construction).

### Fixed (2026-08-09, continued — `__builtin_cpu_supports`/`__builtin_cpu_init` broken on mingw)

- **`__builtin_cpu_supports`/`__builtin_cpu_init` (added in the session
  above via `test_libucl`) were silently unresolved on the mingw/Windows
  cross target** — CI's `test (windows-latest)` job failed to link
  `test_builtin_cpu_init` with `undefined reference to
'__rcc_cpu_supports'`. Root cause: the two were implemented as real
  functions injected into parser.c's per-TU synthetic prelude, but only
  inside the `#else` (pure SysV x86-64) branch of that prelude's
  va_list-ABI `#if`/`#elif` ladder — the sibling `#elif defined(_WIN32)`
  branch never got them at all, so `__builtin_cpu_supports`/
  `__builtin_cpu_init` calls survived `preprocess.c`'s `define_pre`
  rename into `__rcc_cpu_supports`/`__rcc_cpu_init` but those symbols
  were simply never defined on that target.
- First fix attempt — hoisting the two functions out of the SysV-only
  branch into a block shared by both non-ARM64 x86-64 targets (the raw
  `cpuid` instruction is identical under both calling conventions) —
  built clean and fixed the link error, but broke 4 unrelated existing
  mingw unit tests (`test_asm_two_bugs_popcnt_alt`, `test_skip_maxdiff`,
  `test_x86_priv_insns`, `test_x86_pushf_invlpg`: expected raw-asm byte
  sequences went missing from `.text`). Bisected (confirmed absent
  without the change, present with it): `opt.c`'s
  `eliminate_unused_static_inline()` — the pass that would normally
  drop these two never-called `static inline` functions back out again
  — is unconditionally disabled on `_WIN32` (see that function's own
  header comment: dropping unused `static inline` bodies there was
  found to shift unrelated object-file layout enough to corrupt an
  emulated-TLS access elsewhere, a separate pre-existing tradeoff).
  Emitting two permanently-live dead functions into every mingw TU
  shifted the affected tests' own code layout enough to break their
  literal-byte-sequence assertions — real, but purely a landmine of
  that already-disabled DCE pass, not of the ABI-sharing idea itself.
- Actual fix: reimplemented both as pure preprocessor macros
  (`preprocess.c`, `define_macro`) instead of real injected functions —
  `__builtin_cpu_supports(f)` expands to a GNU statement expression
  doing the CPUID query inline at the call site, and `__builtin_cpu_init`
  expands to `((void)0)`. Removed the now-unused function-injection
  block from parser.c's prelude entirely. A macro expands to zero bytes
  unless a real call site invokes it, on every target uniformly, with no
  dependency on DCE at all — categorically sidesteps the disabled-pass
  landmine while fixing the same mingw link failure.
  → unblocks: `__builtin_cpu_supports`/`__builtin_cpu_init` on the mingw
  cross target (previously only worked on native Linux x86-64).

**Full suite re-verified after this fix**: native Linux x86-64 —
Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Link 6/6, 0
failed; mingw cross — `test_builtin_cpu_init` now PASSes (previously
an unresolved-symbol link failure), all 4 previously-regressed tests
(`test_asm_two_bugs_popcnt_alt`, `test_skip_maxdiff`,
`test_x86_priv_insns`, `test_x86_pushf_invlpg`) confirmed passing, TCC
118/118, Compliance 15/15 (`test_peep`'s intermittent TIMEOUT under
this sandbox's qemu/emulation load is pre-existing and reproduces
identically without this change — unrelated); arm64 cross —
`test_builtin_cpu_init` PASSes, C-testsuite 220/220, Torture 3599/3609
with the same 6 pre-existing unrelated complex-number/imaginary-
constant runtime failures documented above, 0 new failures.

### Fixed (2026-08-09, continued — `-o` driver flag rejected the joined `-oFILE` form)

- **`-o`'s argument was only ever accepted as a separate argv element**
  (main.c) — every other rcc driver flag that takes a path argument
  (`-I`, `-D`, `-U`, `-MF`, `-isystem`/`-iquote`/`-idirafter`) already
  accepts both `-Xpath` (joined) and `-X path` (separate) via a
  `strncmp` prefix check with an empty-remainder fallback to the next
  argv; `-o` alone used a plain `!strcmp(argv[i], "-o")`, so a real
  `-oFILE` invocation (no space — valid GCC/Clang syntax) fell through
  to the generic "ignored unknown option" catch-all: both the flag and
  its output path were silently discarded, and the compile proceeded
  writing to whatever _other_ output rule applied instead of the
  caller's chosen path.
  → found via test_samba's waf-based configure: every "does this
  construct compile" probe invokes `rcc ... test.c -c
-o<hashed-tmpdir>/test.c.1.o`; each one silently produced no object
  at the path waf checked for, cascading into bogus "header not
  found"/"does not build" results throughout configure (including a
  misleading "no such member" on `struct utsname`'s perfectly valid
  `sysname` field, from the same broken `CHECK_CODE` probe).
  Fixed by giving `-o` the exact same `strncmp`-prefix-with-fallback
  handling every sibling path-argument flag already uses.

New regression test: `test/test_opt_o_joined.c` — compiles with both
`-oFILE` (joined) and `-o FILE` (separate), asserting the requested
object file actually exists after each. Full suite verified: Torture
3605/3609 (100% of non-skipped), Dg-error 34/34, Link 6/6, Unit tests
all passing, 0 failed overall (native Linux x86-64).

(test_samba itself still doesn't build end-to-end after this fix —
waf's C-compiler bootstrap probe is only the first of many
configure-time checks; not re-triaged further this session.)

### Fixed (2026-08-09, continued — local char-array `{ STRLIT }` initializer corrupted)

- **A local (function-scope, non-static, non-constexpr) char/wide-char
  array initialized by a brace-wrapped single string literal —
  `char arr[] = { "text" };`, C11 6.7.9p14's superfluous-but-legal
  brace form — silently produced wrong, non-deterministic bytes**
  (parser.c, `local_init_one`). `global_init_one` (globals/statics/
  constexpr locals) and `infer_array_type` (array-declarator sizing)
  both already unwrap this shape before dispatching; `local_init_one`
  — the separate codegen path that builds runtime `ND_ASSIGN`
  statements for an ordinary local's initializer — did not. It fell
  into the generic "array with braces" per-element loop, saw exactly
  one initializer-list element (the string literal), and treated it as
  a single _scalar_ initializer for the array's `char` element type:
  the string literal decays to `char*`, and assigning that pointer
  value into a `char`-typed element silently truncated it to its low
  byte — element 0 held an arbitrary, ASLR/link-layout-dependent byte,
  and every other element was left at its zero-initialized default.
  → found via test_liblz4's `programs/lz4io.c`:
  `LZ4IO_toHuman()`'s `const char units[] = {"\0KMGTPEZY"};` followed
  by `units[i]` (the KB/MB/GB/... unit-suffix lookup) — `lz4 -l`
  (list mode) printed a bare number like `"3.00"` instead of `"3.00M"`
  for any file needing a unit suffix, failing lz4's own
  `test-lz4-list.py` test suite (`test_uncompressed_size`).
  Fixed by giving `local_init_one` the identical unwrap
  `global_init_one` already has, including its `brace_close` bookkeeping
  so the loop correctly resumes past the closing `}` once unwrapped
  (the first fix attempt omitted this and broke token-stream resync for
  every caller, turning it into a hard parse error instead of silently
  wrong bytes — caught immediately by the existing full-suite rebuild).

New regression test: `test/test_local_char_array_brace_strlit.c` —
the exact NUL-prefixed-string shape from the bug, plus a plain local
char array, a trailing comma inside the brace, a wide-string local,
and a regression guard for the sibling "one-pointer-array-of-`char*`"
case (`const char *arr[] = {"vec_"}`, TY_PTR-excluded, must keep
assigning the address rather than being treated as a char array).
Full suite verified: Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link 6/6, 0 failed overall (native Linux x86-64); confirmed
clean (both the new test and `test_ptr_array_strlit_size` PASS) on
the mingw cross target.

### Fixed (2026-08-09, continued — wide string literal alignment: 3 stacked bugs)

- \*\*Wide string literals (`L"..."`) could land at any byte offset in
  `.rodata`, misaligned for `wchar_t` — glibc's vectorized
  `wcslen()`/`wcscmp()`/`wmemcmp()` read multiple `wchar_t` at once under
  the assumption that any `wchar_t` object satisfies `_Alignof(wchar_t)`
  (4 on Linux/macOS, 2 on Windows), true of every object a real compiler
  emits, string literals included. Three separate, stacked bugs conspired
  to break this:
  1. **codegen.c's string-literal emission loop packed every literal
     (narrow and wide) back-to-back with no padding** — a wide literal
     landed at whatever odd byte offset the preceding literal's length
     left behind. Fixed: `secbuf_align(cg_sec, s->elem_size)` before each
     wide literal's bytes.
  2. **Two call sites that register a string literal used purely for its
     _address_ hardcoded `elem_size=1` regardless of the literal's real
     prefix** — `read_global_label_initializer()` (a global/const pointer
     initializer, e.g. `const wchar_t *p = L"text";`, the exact shape of
     libarchive's `fileflags[]` flag-name table) and `extract_reloc()`'s
     `ND_STR` case (an `&expr` reloc extraction), which additionally
     re-registered a wholly redundant _second_ StrLit instead of reusing
     the correctly-tagged one `primary()` already created for the same
     token. Fixed: a new `str_lit_elem_size()` helper (mirroring
     `primary()`'s own prefix→size switch) for the first; reusing
     `node->str_id` for the second.
  3. **elf_write.c hardcoded `.rodata`'s ELF `sh_addralign` to `1` in
     every `.o` it wrote** — even after fixes #1/#2 correctly self-pad a
     literal _within_ one compilation unit, the linker (rcc's own, or a
     real system `ld`) reads `sh_addralign` to decide how to place one
     object's section relative to another's once merged; declaring `1`
     ("no constraint") let it concatenate this object's `.rodata`
     immediately after another's at any offset, silently destroying the
     intra-file padding. This is why the bug reproduced in libarchive's
     real, multi-file build but not in any single-file minimal repro.
     Fixed: `SecBuf` now tracks the max alignment any `secbuf_align()`
     call has requested (mirroring the existing pattern already used for
     `__attribute__((section("name")))` globals); `elf_write.c`'s
     `.data`/`.rodata`/`.tdata` section headers use the tracked value
     instead of a hardcoded constant. `link_elf.c` (rcc's own linker)
     already correctly _consumed_ `sh_addralign` when merging sections —
     it was only ever being fed the wrong (hardcoded) value.
     A fourth, independently-discovered bug surfaced while writing the
     regression test: **`infer_array_type()` sized a wide array declarator
     using NUL-terminated `utf8_len()` instead of length-bounded UTF-8
     codepoint counting** — `wchar_t units[] = L"\0KMGTPEZY";` (an embedded
     NUL before the literal's true end, the exact `LZ4IO_toHuman()`/
     `fileflags` table shape but as a top-level global) sized the array as
     1 element (stopping at the leading NUL) instead of 10, leaving every
     byte past the first uninitialized garbage. Fixed: replaced the
     `utf8_len()` call with the same `tok->len`-bounded `decode_utf8()`
     loop `primary()`/`global_initializer()`'s wide-string branches already
     use.
     → found via test_libarchive: `archive_entry_copy_symlink_w()`/
     `archive_entry_symlink_w()` truncated every wide string by one
     character (`archive_mstring_copy_wcs_len()`'s internal `wcslen()`
     misread the misaligned literal); `ae_wcstofflags()`'s `fileflags[]`
     table lookup silently dropped bits from the parsed flag set for the
     same reason. Unblocked `test_entry`, `test_archive_match_path`,
     `test_archive_match_time`, and `test_filter_count` (4 of libarchive's
     10 originally-failing non-fuzz tests; the other 6 are an unrelated
     PPMd-codec issue, not yet triaged).

New regression tests: `test/test_wide_string_alignment.c` (all four
fixed code paths: local pointer, global array with embedded NUL,
struct-field pointer, `&L"literal"`); `test/test-link.sh` case 8
(2-TU link, deliberately misaligning the second object's `.rodata` via
an odd-length narrow literal in the first, guards the linker-level
fix specifically — the one bug class no single-file test can catch).
Full suite verified: Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link 8/8 (incl. the new case), 0 failed overall (native Linux
x86-64); confirmed clean (both new tests PASS) on the mingw and arm64
cross targets.

### Investigated: libarchive PPMd cluster (7zip/RAR/ZIP) — confirmed NOT an rcc bug

The 6 remaining non-fuzz `test_libarchive` failures after the wide
string alignment fix above — `test_read_format_7zip_ppmd`,
`test_read_format_rar_compress_best`, `test_read_format_rar_multivolume`,
`test_read_format_rar_ppmd_lzss_conversion`,
`test_read_format_zip_ppmd_multi`(`_blockread`),
`test_read_format_zip_ppmd_one_file`(`_blockread`),
`test_read_format_zip_winzip_aes256_large_ppmd` — all fail with genuine
PPMd arithmetic-decoder errors ("Invalid symbol", "Invalid PPMd
sequence", "Invalid location to Huffman tree specified", "Truncated
7z file data").

**Confirmed pre-existing, not an rcc bug**: rebuilding `archive_ppmd7.c`/
`archive_ppmd8.c` and `libarchive_test` with real `gcc` (not rcc)
reproduces the identical failures against the same `.uu`-encoded
reference archives. This checkout's PPMd-family test corpus is
incompatible with (or the PPMd implementation itself has a pre-existing
bug against) this specific libarchive 3.8.8 checkout, independent of
which compiler builds it. Not investigated further — no rcc fix is
possible or needed here; any fix belongs upstream in libarchive.

### Fixed (2026-08-09, qbe_simplecc session)

**test_qbe_simplecc now fully passes**: qbe's own `tools/test.sh all`
(`qbe` itself, and `scc`, built with `CC=rcc`) went from 42/59 to
59/59 once all three bugs below were fixed. All three are genuine,
broadly-impactful rcc bugs — none specific to qbe — found by comparing
a real-gcc-built `qbe` (which passed all 59 tests cleanly, ruling out
a pre-existing qbe/test-corpus issue) against the rcc-built one.

- **The inline assembler had no handling at all for GAS-style C
  `/* ... */` block comments** (`src/asm.c`, `assemble_inline()`) —
  only `#`/`;`/`//` line comments were recognized. A block comment on
  its own physical line was parsed as a real instruction whose
  mnemonic was literally the comment's opening `/*`
  (`warning: unknown x86 instruction: /*`), silently dropping every
  instruction after it in that inline-asm block; a trailing block
  comment glued onto a real instruction's operands
  (`movl $1, %eax /* comment */`) was fed straight into the operand
  parser as if it were part of the operand text, corrupting or
  dropping the instruction. Fixed by a new `strip_block_comments()`
  lexer pre-pass, run before macro expansion (mirroring real GAS's own
  pipeline order), that blanks commented bytes to spaces while
  preserving embedded newlines so the per-line assembler's line-number
  tracking stays accurate across a multi-line comment; a `"` string
  literal is scanned but left untouched, so a directive like
  `.ascii "/* not a comment */"` keeps its real content.
  → found via QBE's own generated assembly, which annotates every
  function with an `/* end function NAME */`-style trailing comment.
- **A compound literal's designator-chain parser resolved a
  multi-level EXPLICIT designator (e.g. `.bits.i`, reaching a union
  member through its own NAMED — not anonymous — field) to the LEAF
  member's offset within its immediate parent instead of that offset
  PLUS every intermediate member's own offset within the outer struct**
  (`parser.c`, the `(type){...}` expression-context struct-literal
  handler). `find_member_by_name()`'s existing anonymous-member
  recursion already returns a synthetic member with the correctly
  combined offset for a designator reaching through an _unnamed_
  struct/union — but a NAMED intermediate member (like a struct's own
  `union { ... } bits;`) requires each step of the designator chain to
  be resolved and its offset accumulated explicitly; the chain walker
  only tracked the final `find_member_by_name()` result, discarding
  every intermediate step's own offset. Since every union member sits
  at offset 0 within its union, `.bits.i = val` silently wrote `val`
  at offset 0 of the _whole_ struct instead of `bits`'s real offset —
  aliasing (and corrupting) whichever earlier field happened to start
  there too. Fixed by accumulating each step's `Member->offset` across
  the chain and, when it differs from the leaf's own immediate-parent
  offset, building a synthetic `Member` with the combined offset (the
  same "synthetic member, combined offset" pattern
  `find_member_by_name()` already uses for anonymous chains).
  → found via qbe's own `struct Con { enum {...} type; Sym sym; union
{ int64_t i; double d; float s; } bits; char flt; };`, constructed
  throughout `amd64/sysv.c`/`isel.c` as `(Con){.type = CBits, .bits.i =
val}` — every constant qbe's own x86-64 backend created this way had
  its `.type` tag silently overwritten with the low bits of the
  intended `.bits.i` value, later crashing `noimm()`'s classification
  switch (`die("invalid constant")`) or `amd64/emit.c`
  (`die("unreachable")`) on a garbage `.type`.
- **The register allocator's spill mechanism could alias two
  concurrently-live virtual registers onto the same physical register**
  (`codegen.c`, `alloc_reg()`) — under register pressure (e.g. a
  bitfield read-modify-write's "old value, masked" register and its
  "new value, masked+shifted" register, needed simultaneously right
  before being ORed together, nested inside evaluating the LAST of 5
  register-class call arguments), `alloc_reg()` could legitimately pick
  an already-allocated-but-not-yet-freed VReg's own physical register
  as the spill victim for a brand new allocation. Since `VReg` identity
  IS the physical register index in this allocator, the new allocation
  then returned the EXACT SAME index as the VReg it just evicted — the
  two "different" VRegs aliased one physical register while the caller
  still believed both were independently live. A second, related bug:
  even without that specific aliasing, the allocator tracked only ONE
  spill slot per register index; a register spilled a second time
  while an earlier spill of the same index was still outstanding
  clobbered the first spill's saved bytes. Fixed in two parts: (1) the
  single spill slot per register index became a per-index stack
  (`push_spill_slot()`/`pop_spill_slot()`), so a re-spill of the same
  physical register while an earlier spill is still outstanding pushes
  a fresh slot instead of clobbering it; (2) a new `alloc_reg_avoid2()`
  entry point, used by the bitfield read-modify-write's second register
  allocation, that never returns either of two explicitly-named
  already-live registers, forcing the allocator to spill a genuinely
  outer/enclosing value instead of aliasing onto a register the
  immediate caller still needs.
  → found via qbe's own `(Ref){RCon, 1}`-shaped positional compound
  literal (`#define CON_Z (Ref){RCon, 1}` in `all.h`, `struct Ref {
uint type:3; uint val:29; }`) used as the last of 5 register-class
  call arguments in `amd64/sysv.c`'s `selvaarg()`/`selcall()`: the
  bitfield merge degenerated into a self-`or reg,reg` no-op that
  silently dropped `CON_Z`'s `.type = RCon` tag (leaving it `0`,
  `RTmp`, so the constant printed as a bogus register reference `R1`
  instead of the literal `0`), and a narrower reproduction (no
  intervening struct args) instead corrupted the read-modify-write's
  own address register and segfaulted on the write-through.

New regression tests: `test/test_asm_block_comment.c` (own-line,
trailing, and multi-line-spanning block comments in inline asm);
`test/test_compound_literal_nested.c` (new `.bits.i`-through-a-named-
union cases, both as a plain local initializer and as an expression-
context compound literal); `test/test_bitfields.c` (new ternary +
positional-compound-literal-as-last-of-5-args case, both branches).
Full suite verified: Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link 7/7, RCC Unit tests 0 failed, 0 failed overall (native
Linux x86-64); `test_qbe_simplecc`'s own `tools/test.sh all` 59/59.

### Investigated: test_liblz4 — confirmed NOT an rcc bug (sandbox throughput, not correctness)

`test_liblz4`'s harness target (`make test`) transitively depends on
`tests/Makefile`'s `test-lz4-hugefile` (`test-lz4-fast-hugefile` +
`test-lz4hc-hugefile`), which `datagen -g4200MB`s a 4.2GB file and
round-trips it through both the fast and HC compressors with full
content verification. In this sandbox that alone takes well over the
harness's 420s per-target timeout — confirmed by running it standalone
and watching it progress steadily (through several minutes of
`Read : NNN MiB ==> ...%` progress output) with **zero** diffs,
decompression mismatches, or crashes, just slow linear throughput
(~0.5-1 MiB/s effective for the largest passes under this sandbox's
CPU/disk contention).

Re-ran the rest of the suite explicitly _excluding_ the hugefile
targets (`test-lz4-essentials`, `test-lz4-opt-parser`,
`test-lz4-sparse`, `test-lz4-dict`, `test-lz4-skippable`, `test-lz4c`,
`test-fullbench`, `test-fuzzer -T90s`, `test-frametest -v -T90s`,
`test-amalgamation`, `listTest`, `test-decompress-partial`) — every
one passed cleanly (including `listTest`'s own 10-case Python unit
suite, `Ran 10 tests in 0.405s / OK`), no failures, no crashes. Zero
`FAIL`/`error:`/mismatch markers in either run's full output (the only
text-string hits for "corrupti"/"error" are literal words inside
lorem-ipsum-style test _fixture data_, not diagnostics).

**Confirmed pre-existing sandbox limitation, not an rcc bug**: no
fix needed or possible on rcc's side — the harness's fixed 420s
timeout is simply too short for a 4.2GB compress/decompress round
trip on this machine's I/O throughput. (The one genuine rcc bug
`test_liblz4` did surface — `programs/lz4io.c`'s `LZ4IO_toHuman()`
NUL-prefixed brace-string-literal initializer — was found and fixed
separately; see "Fixed (2026-08-09, continued — local char-array `{
STRLIT }` initializer corrupted)" above.)

### Investigated: test_libpng — confirmed NOT an rcc bug (upstream test sensitivity + codegen speed, not correctness)

`make check`'s `check-TESTS` run reported `FAIL: tests/pngtest-all`
then hit the harness's 420s timeout mid-way through the suite's final
test, `tests/pngimage-full`. Investigated both independently:

- **`pngtest-all`'s failure**: the very first sub-check,
  `pngtest --strict ./pngtest.png`, round-trips the checked-in
  reference PNG through libpng's own read+write API and does a
  byte-for-byte comparison of the result against the original file.
  `pngtest.c` itself documents why this can legitimately fail (see the
  `\nFiles %s and %s are different\n... Was %s written with the same
... zlib version (%s)?` diagnostic it prints) — the comparison is
  sensitive to the _exact_ zlib build/version used to originally
  encode the reference file, not just to libpng's own read/write
  correctness. Confirmed with a fully **gcc**-rebuilt libpng16 +
  pngtest (`make CC=gcc clean && make CC=gcc`, no rcc involved at
  all): identical failure, byte-for-byte identical diagnostic output.
  This environment's `zlib 1.3.1.zlib-ng` simply doesn't reproduce
  whatever build originally compressed `pngtest.png` — a pre-existing,
  compiler-independent environment mismatch, not an rcc bug.
- **The timeout**: `tests/pngimage-full` runs
  `pngimage --exhaustive --list-combos` (every read-transform
  combination) against the whole `contrib/pngsuite/` corpus. Timed
  standalone: gcc-built `pngimage` completes in 68s, 100% PASS;
  rcc-built `pngimage` completes in 222s, **also 100% PASS** — same
  correct output, just ~3.3x slower under rcc's much simpler
  (peephole-only, no vectorization/scheduling) optimizer versus gcc
  -O2's mature pipeline on this pixel-transform-heavy nested-loop
  workload. Combined with the rest of `check-TESTS` (configure, full
  library + 8 test-program build, then every earlier TESTS entry) and
  this sandbox's own resource contention, the whole `make check`
  invocation exceeds the harness's fixed 420s budget — a genuine
  performance gap versus gcc, not a correctness defect, and out of
  scope for a targeted bug fix (closing a 3x gap with gcc -O2's
  optimizer is a different, much larger undertaking than this
  project's third-party test failures otherwise represent).

**Confirmed pre-existing, not an rcc bug**: no fix applied or needed;
rcc-generated code is correct throughout, just slower than gcc -O2 on
this specific exhaustive-combinatorial workload, and the strict
byte-compare failure is upstream/environmental, reproducing identically
with a 100% gcc-built libpng.

### Fixed (2026-08-09, block-scope extern DCE session)

- **A block-scope `extern` declaration of a global inside a never-called
  `static inline` function got that global silently dropped from the
  final object entirely, once the enclosing (dead) function was
  eliminated** (parser.c/opt.c) — `new_var()` unconditionally stamps
  `decl_fn_name = parser_current_fn` on every non-local `LVar` it
  creates while parsing inside a function body. That tagging exists so
  a true block-scope `static` local (which owns its _own_, function-
  lifetime-scoped storage — see the `attr.is_static` branch right above
  the `attr.is_extern` one) gets spliced out of `prog->globals` too when
  `opt.c`'s `eliminate_unused_static_inline()` drops its never-called
  enclosing `static inline` function as dead code (see that function's
  own "Second pass: drop any global whose decl*fn_name names an omitted
  function" comment). The block-scope-`extern` branch reused the same
  `new_var(name, ty, false)` call to register (or find) the \_referenced*
  global, so that global inherited the same "belongs to this function,
  drop it if the function dies" tag — even though a block-scope `extern`
  never owns the storage it names; it may equally be, and here was,
  independently defined at file scope elsewhere in the same TU. Once
  the enclosing function was recognized as dead code, `opt.c`'s second
  DCE pass spliced the (still fully live, later-defined) global out of
  `prog->globals` right alongside it — the object file kept the earlier
  `U`-typed reference emitted while codegening the (never actually
  emitted) dead function's body, but the real definition never got
  emitted at all, producing a hard link failure
  ("undefined reference to `preserve_acls'") in any program that also
references the same global from a *live* function.
Fixed by clearing `gvar->decl_fn_name = NULL` immediately after
creating (or reusing) the global in the block-scope-`extern`branch —
an extern reference is never owned by the function lexically
containing it, so it must never be tied to that function's DCE
lifecycle.
→ found via rsync's`options.c`: `extern int preserve_acls;`/`extern int preserve_xattrs;`declared inside small`static inline`accessor helpers that configure.sh-detected feature gates leave
entirely unused on this platform, with the real`int preserve_acls =
  0;`/`int preserve_xattrs = 0;`definitions elsewhere in the same
file — rsync's own`make test` failed to link
(`undefined reference to 'preserve_acls'`) until this fix.

New regression test: `test/test_extern_block_scope_dce.c` (two
never-called `static inline` functions each block-scope-`extern`-
declare a global that is defined at file scope afterward and used only
from a third, live function — reproduces the exact drop-with-the-dead-
function pattern without needing rsync itself).

Full suite verified: Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, RCC Unit tests 0 failed, Link tests 7/7, 0 failed overall
(native Linux x86-64); `test_rsync`'s own `make test` now links and
runs to completion.

### Fixed (2026-08-10, output-extension-naming / include_next session)

- **`rcc -c foo.cxx` (no `-o`) produced `foo.cxx.o`, not `foo.o`**
  (main.c, `replace_ext()`) — rcc accepts any input extension as
  compilable C (no `-x`-style language gate: the input-classification
  loop in `main()` only routes `.o`/`.lo`/`.a` and shared-library paths
  to the linker, everything else is a compile input regardless of
  extension), but the default output-name helper only stripped
  `.c`/`.i`/`.s` before appending the target extension; any other
  accepted extension (`.C`, `.cc`, `.cxx`, `.cpp`, ...) fell through to
  literally appending `.o` to the _whole_ filename including its own
  extension, instead of replacing it - the universal `cc -c` convention
  every real compiler driver follows regardless of which extension it
  dispatches on. Fixed by always stripping whatever the final extension
  is (a bare `strrchr` cut), matching real compiler-driver behavior;
  files with no extension at all are unaffected (`new_ext` is still
  just appended).
  → found via ksh93's own build-time C-compiler probe
  (`src/cmd/INIT/C+probe`): it compiles a trial `test.cxx` first (glibc
  headers make this parse as valid C too, since rcc has no separate
  C++ front end), globs `test.*` for the produced object, and expects
  exactly `test.o` once the probe reverts to plain `.c` — the malformed
  `test.cxx.o` name broke that bookkeeping and made the probe
  misreport rcc as "not a C compiler", aborting ksh93's whole build
  before a single real source file was compiled.
- **`#include_next` from one of rcc's own bundled headers could be
  shadowed by an unrelated same-named header sitting in a user `-I`
  directory, instead of reaching the real system header** (preprocess.c,
  `resolve_include_next()`) — `#include_next`'s whole purpose is a
  compiler's own "fixed" header reaching past itself to the platform's
  genuine header of the same name (e.g. rcc's `include/wchar.h` doing
  `#include_next <wchar.h>` to reach glibc's `<wchar.h>` for
  `wint_t`/`mbstate_t`, which rcc's own deliberately-thin wrapper
  doesn't define itself). `resolve_include_next()` continued the search
  through `build_search_dirs()`'s full combined list (`RCC_INCDIR`,
  then every user `-I` directory, then the real system dirs) starting
  right after wherever the current file was found - so continuing from
  `RCC_INCDIR` walked through every `-I` directory before ever reaching
  the system dirs, and a project header of the same name sitting in one
  of them (a legitimate, unrelated file - not a forwarding wrapper) got
  found first, silently defeating the whole `#include_next`. Fixed by
  recognizing a continuation from `RCC_INCDIR` (or its `"include"`
  fallback) specifically and skipping straight to the real system
  include chain, bypassing every user `-I` directory entirely -
  matching how GCC's own private "fixed" include directory is never
  shadowed by an unrelated project header for this exact reason.
  → found via ksh93's `ast_wchar.h`: its
  `#include <../include/wchar.h>` resolves (through `RCC_INCDIR`, since
  `RCC_INCDIR/../include/wchar.h` collapses right back to
  `RCC_INCDIR/wchar.h`) to rcc's own `include/wchar.h`, whose
  `#include_next <wchar.h>` needs to reach glibc's real `<wchar.h>` -
  but ksh93's build passes `-Istd -I.../src/lib/libast/std`, and that
  directory contains its own `libast/std/wchar.h` wrapper (itself just
  `#include <ast_wchar.h>`, guarded right back into a no-op by
  `ast_wchar.h`'s own include guard) - found and used _first_, leaving
  `wint_t` permanently undeclared and cascading into "expected ';' or
  ','"/"type defaults to int" parse errors on every `wint_t`-returning
  declaration in the header.

New regression tests: `test/test_output_ext_naming.c` (drives rcc as a
subprocess across `.c`/`.C`/`.cc`/`.cxx`/`.cpp`/`.i`/`.s`, confirming
the default `-c` output name in each case), `test/test_include_next_skips_user_dirs.c`
(a decoy same-named header in a `-I` directory, confirming
`#include_next <wchar.h>` from rcc's own bundled header reaches the
real system one, not the decoy). Both fail against the pre-fix binary
and pass with the fix. Full suite verified: Torture 3605/3609 (100% of
non-skipped), Dg-error 34/34, RCC Unit tests 0 failed, Link tests 7/7,
0 failed overall (native Linux x86-64); ksh93's own mamprobe
C-compiler sniff now passes and the build reaches real source
compilation (see the `test_ksh93` table row above for what still
blocks it beyond this point).

### Investigated, not fixed (2026-08-10, register-spill/locals-collision session)

**test_perl**: `miniperl -f write_buildcustomize.pl` (i.e. _every_
rcc-built perl, since this file loads on startup) segfaults inside
`Perl_upg_version()`, dereferencing a NULL first argument. Traced the
call chain back to `S_enable_feature_bundle()` (feature.h, inlined into
op.c/opmini.c): a 9-level nested `(setnv(comp_ver, X), vcmp(ver,
upg_version(comp_ver, FALSE)) >= 0) ? N : ...` ternary chain, where
`ver` (the function's own SV\* parameter) and `comp_ver` (a
`sv_newmortal()` result) both need to stay live across every one of the
9 branches' own `setnv`/`vcmp`/`upg_version` calls.

**Root cause, confirmed via a minimal reproduction and disassembly**:
`alloc_reg()`'s spill mechanism (codegen.c) and ordinary declared-local
stack-offset assignment (`var->offset`, from parsing) are two entirely
independent counters that both address memory as `-N(%rbp)` starting
from small values - `next_spill_slot` begins at a hardcoded `8` every
function, with zero awareness of `fn->stack_size` (the function's own
locals region size). Under light register pressure the two never
collide (spilling is rare and shallow); under heavy pressure - a long
chain of nested ternaries, each needing several registers concurrently
live across many inner calls - `alloc_reg()`'s spill-victim selection
eventually has to spill deep into indices whose `next_spill_slot`
address lands exactly on a live local's own `var->offset`. In the
minimal repro this silently overwrote a `SV *` parameter's home slot
mid-expression with an unrelated branch-local boolean temporary.

**Attempted fix**: reset `next_spill_slot` after Pass 1 (dry-run
discovery) to start past `fn->stack_size + fn_struct_ret_total +
fn_trampoline_total` - the same three regions the existing frame-size
computation (`need = fn->stack_size + fn_struct_ret_total +
fn_trampoline_total + 32`) already sums. This measurably fixed the
targeted collision (confirmed via disassembly: the exact clobbering
instruction moved off the locals' own offsets) and fixed the minimal
reproduction's crash pattern, but **broke 16 previously-passing torture
tests** (`va-arg-2` first, then `bitops-1`, `pr38969`, `pr44858`, and
others) when run through the full `make check-all` regression suite -
`next_spill_slot`'s new starting point collides with _other_
function-specific regions that also key off `fn->stack_size`
independently of the frame-size `need` computation, at minimum the
x86-64 SysV variadic register-save area (`va_reg_save_ofs =
current_fn_stack_size + 176`, set once during Pass 1's param handling
and never revisited). Patching that one case (skip past
`va_reg_save_ofs` too, for variadic functions) fixed `va-arg-2` but
`bitops-1` - a plain, non-variadic, no-varargs test - still failed
identically, meaning at least one more region collides with the new
`next_spill_slot` start point that wasn't identified in the time
available this session.

**Decision**: reverted the codegen.c change entirely rather than ship
a partially-verified fix that trades one regression class for another;
`test_perl` remains unfixed. This is a real, reproducible bug worth
revisiting, but properly fixing it needs a systematic audit of every
per-function stack region that's computed independently of
`next_spill_slot` (struct-ret staging, trampoline slots, variadic
register-save area, the `spill_logand`/`spill_atomic_old` fixed slots
allocated once via `init_spill_slots()`, and any others), unifying them
behind one single, non-overlapping stack-layout allocator instead of
several independent hardcoded-base counters - a bigger undertaking than
a single-session fix.

### Fixed (2026-08-10, continued — spill/locals collision)

Root cause was as diagnosed above (`next_spill_slot` grows from a
hardcoded low base, independent of `need`), but the prior session's
"reset to `fn->stack_size + fn_struct_ret_total + fn_trampoline_total`"
approach mis-timed the reset: `need` (locals + struct-ret + trampoline
, spill max-folded in) was already fixing the prologue's frame size and
the callee-saved save area's placement _before_ the reset could apply,
so Pass 2 could still walk past it.

Fixed by computing the spill region's actual growth during Pass 1
(`next_spill_slot - spill_reserved_base`, the base right after
`init_spill_slots()`) and _adding_ it onto `need` (not maxing), then
resetting `next_spill_slot` to the pre-growth `need` so Pass 2's
identical push_spill_slot() replay lands in the newly reserved region
instead of the low base. Applied on both the x86-64 and ARM64
prologue sites (same `need`/spill-region structure on both).

This surfaced a second, previously-latent bug: the `__cleanup__`
epilogue stashes RAX (+RDX for a GP-pair struct return) via
`spill_offset()` _after_ Pass 2's body walk — code Pass 1 never sees
(it only walks `fn->body`). With spills re-anchored to start exactly
at `need`, this invisible extra push landed exactly on the
callee-saved register save area, which also starts at `need`,
corrupting whichever callee-saved register a struct-return path was
using (caught by `101_cleanup`'s `test_cleanup2` in `make check-all`,
not by the original repro). Fixed by budgeting the cleanup epilogue's
known, deterministic extra spill usage into `need` up front. ARM64's
cleanup path uses a dedicated callee-saved register (x19/x20) instead
of `spill_offset()`, so it isn't affected.

New regression test: `test/test_spill_locals_collision.c` (both bugs;
segfaults pre-fix on native x86-64 and mingw, `assert`-fails on the
intermediate fix that only covered bug 1). Full suite verified: TCC
118/118, Unit tests 205/205, Compliance 15/15, C-testsuite 220/220,
Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Link 7/7 — 0
failed overall (native Linux x86-64); the new test also confirmed
passing standalone on the mingw and arm64 cross targets. Re-ran the
real `test_perl` third-party target end to end (`./Configure -des
-Dcc=rcc ...`, `make -j3 test_prep && HARNESS_OPTIONS=j3 make
test_harness`) to confirm against the actual reported crash, not just
the synthetic repro: it now builds miniperl, the full `perl`, and
every extension, and `make test_harness` completes cleanly, `rc=0` in
557s.

### Fixed (2026-08-10, include_next noop-forward refinement)

The prior session's `#include_next` fix ("output-extension-naming /
include*next session" above) was too broad: it unconditionally skipped
\_every* user `-I` directory when continuing an `#include_next` from
one of rcc's own bundled headers (RCC*INCDIR), on the theory that a
same-named user header could only ever be an accidental shadow of the
real system one. That's true for the case it fixed (ksh93's own
`std/wchar.h` is a trivial `#include <ast_wchar.h>` forwarder, which
at that point in the chain is already open and hence a guard-induced
no-op) but false in general: a user `-I` header can also be a
\_legitimate, non-forwarding replacement*. ksh93 hits exactly that
case one step later: `ast_wchar.h`'s own preamble does `#ifndef
_SFSTDIO_H #include <stdio.h> #endif` specifically to pull in glibc's
`<stdio.h>` (which defines `__FILE`, needed by glibc's `<wchar.h>`
declarations like `open_wmemstream`/`fwide`) before `_SFSTDIO_H` gets
hijacked elsewhere — but `#include <stdio.h>` resolves through rcc's
own bundled `include/stdio.h` first, whose own `#include_next
<stdio.h>` used to skip straight past ksh93's `-Istd/stdio.h`
(→ `ast_stdio.h`, which _does_ provide `__FILE` via `#define __FILE
FILE`) to glibc's `<stdio.h>` directly — silently discarding the
`__FILE` definition ksh93 relies on and leaving glibc's `<wchar.h>`
failing to parse ("expected specific operator" on every `__FILE *`
parameter) once `#include_next <wchar.h>` (fixed last session)
actually reached it. Confirmed cross-checked against real gcc: given
the identical rcc-generated build tree, gcc resolves `<stdio.h>`
straight to `-Istd/stdio.h` and compiles clean; rcc's blanket skip
bypassed it.

Fixed by replacing the blanket skip with a narrower check
(`is_noop_forward_to_active()`, preprocess.c): a user `-I` candidate is
only skipped when its entire content, once comments/blank lines are
stripped, reduces to a single `#include` whose resolved target is
already open on the include stack (walking `lvl`) — i.e. it would
contribute nothing back. Anything else (like `ast_stdio.h`'s real
content) is accepted normally, restoring the original linear
`#include_next` search order. Also fixed the `start`-index detection
that search relies on (which directory supplied the _current_ file) to
normalize path separators via `canonical_path()` before comparing —
`full_path()`'s raw `_fullpath()` output on Windows mixed backslashes
into paths built from rcc's (forward-slash) `RCC_INCDIR`, so the
match never fired there and `start` silently stayed 0, re-finding
rcc's own header instead of continuing the search at all (only
visible cross-platform, since native Linux's `realpath()`-based
`full_path()` never produces backslashes to begin with).

Unblocked ksh93's `misc/fastfind.c` (the compile from the previous
session's own reproduction) and the whole `libast` build gets much
further (`hash/*.c`, `string/ccmap*.c`, `port/getcodeset.c`) before
hitting a new, unrelated, not-yet-investigated gap in
`string/chresc.c` (`CC_bel`/`CC_esc`/`CC_vt` undeclared — see
test_ksh93's own TODO row above).

Regression test: `test/test_include_next_skips_user_dirs.c`, rewritten
with two cases in one file — a trivial forwarder must still be
skipped (the original fix's own regression coverage, updated to use a
forwarding decoy instead of an unconditional `#error` one, since the
narrower heuristic doesn't blanket-skip on content it can't reason
about), and a real non-forwarding `-I` override must still be found
(this session's regression). Both fail against the overly-broad
intermediate fix; both pass with this session's refinement. Full suite
verified: TCC 118/118, Unit tests 205/205, Compliance 15/15,
C-testsuite 220/220, Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link 7/7 — 0 failed overall (native Linux x86-64); both
regression tests also confirmed passing standalone on the mingw and
arm64 cross targets (the path-separator fix above was needed
specifically for mingw's case 2 to pass).

### Fixed (2026-08-10, include_next self-reference / ND_COMMA null-deref session)

Continued ksh93/libast now that the `__FILE`/`stdio.h` gap was fixed:
`misc/fastfind.c` and `string/chresc.c` compiled, but `comp/iconv.c`
(ast's own iconv fallback implementation, which needs `_ICONV_LIST_PRIVATE_`-
gated _raw_ `iconv_t` — the real system one, not macro-renamed to
`_ast_iconv_t`) still failed with `iconv_t` undeclared, and separately
`string/chresc.c` still had `CC_bel`/`CC_esc`/`CC_vt` undeclared (from
`ccode.h`, reached transitively through `<iconv.h>` on real gcc).

**Root cause**: ast's own `ast_iconv.h`/`ast_wchar.h`/`ast_stdio.h`
(iffe-generated) reach "the platform's native header" via `#include
<../include/X.h>` — a relative escape that, joined against
`resolve_include()`'s first search entry (`RCC_INCDIR`, whose own
basename literally _is_ "include"), lexically collapses right back
onto RCC*INCDIR's own bundled copy of the same name
(`.../rcc/include/../include/iconv.h` \_is*
`.../rcc/include/iconv.h`) — not the real system header the idiom is
trying to reach. When that bundled copy is still active on the
include stack (always true here: this fires from _inside_ its own
`#include_next` continuation), its include guard fires on this
second, self-referential visit and the whole body — including the
`#include_next` line meant to reach glibc — silently no-ops. Whether
this actually breaks anything downstream turned out to depend on
luck: `wchar.h` happened to work because `ast_wchar.h` was already
independently pre-opened via a different chain before this collision
could matter; `iconv.h` had no such luck.

Fixed by extracting `resolve_include()`'s original logic into a new
`resolve_include_raw()`, and having the real `resolve_include()`
(used by the actual `#include`/`__has_include`/`#embed` directive
handlers) skip a match at the `RCC_INCDIR`/"include"-fallback search
position when it resolves to a file already active on the include
stack, continuing to the next candidate — mirroring
`resolve_include_next()`'s own no-op-forward handling for the same
self-reference shape (added the prior session). Deliberately kept
`is_noop_forward_to_active()`'s own internal peek on
`resolve_include_raw()` instead: it needs the _unfiltered_ answer to
recognize this exact self-reference collision as its own "already
active" signal in the first place — using the filtered wrapper there
would hide the very case it exists to detect.

**Second bug, unrelated**: with `comp/iconv.c` compiling further,
`sfio/sfvprintf.c` (ast's own printf-family implementation) crashed
rcc outright (SIGSEGV, not a diagnostic). Root cause:
`add_type_internal()`'s `ND_COMMA` case (type.c) computed `node->ty =
node->rhs->ty;` then unconditionally dereferenced `node->ty->kind` to
apply array/function decay — but several node kinds are
statement-like and never get a type assigned at all (`ND_NULL`,
`ND_ZERO_INIT`, `ND_LABEL`, ...), and a comma expression's rightmost
operand can legally be one of those (e.g. `__builtin_apply()`
synthesizes a bare `ND_NULL` placeholder). `node->rhs->ty` being NULL
there dereferenced garbage. Fixed by only applying the decay when
`node->ty` is non-NULL, leaving a void-rhs comma expression's own
type NULL too instead of crashing.

`test_ksh93`'s `libast` now compiles and links **completely** (every
`comp/*.c`, `sfio/*.c`, `string/*.c`, `hash/*.c`, `misc/*.c`) and
reaches `cmd/INIT`'s own `iffe` self-test suite — 159/161 passing (2
narrow, iffe-internal struct-introspection failures, not
investigated this session) — by far the deepest any session has
gotten into this target.

New regression tests: `test/test_iconv_chain.c` (mirrors the real
`ast_iconv.h` shape directly: a synthetic `-I` iconv.h override that
also defines an unrelated marker macro must still be reached, not
shadowed by rcc's own bundled `include/iconv.h` — reproduces cleanly
without needing the actual ksh93 tree); `test/test_comma_null_rhs.c`
(the `__builtin_apply()`-based ND_COMMA repro — SIGSEGVs the unfixed
compiler outright, passes cleanly fixed). Also converted rcc's own
bundled `include/iconv.h` to the same `#ifdef _WIN32 / __APPLE__ /
#else #include_next #endif` pattern `stdio.h`/`wchar.h`/`limits.h`
already use (it was the one bundled header with no platform chaining
at all, self-contained on every target — the actual reason
`resolve_include_next()`'s "already correctly walks past a no-op
forwarder" fix from the prior session never got exercised for iconv
specifically until this session added the chain itself). Full suite
verified: TCC 118/118, Unit tests 207/207, Compliance 15/15,
C-testsuite 220/220, Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link 7/7 — 0 failed overall (native Linux x86-64); both new
tests, plus the full previously-existing include_next/limits-chain
regression coverage, also confirmed passing standalone on the mingw
and arm64 cross targets.

### Fixed (2026-08-10, continued — sizeof/cast incomplete-type validation)

`test_ksh93`'s own `cmd/INIT` `iffe` self-test suite (159/161 passing
per the session above) had 2 failures, both in `TEST 12 'non-opaque
mem'`: iffe's `mem` operation probes whether a struct type is opaque
(forward-declared, never defined) by generating small `.c` files and
checking whether rcc accepts or rejects them — and rcc accepted two
forms of invalid C it should have rejected, silently agreeing with
both the "opaque" and "non-opaque" cases regardless of which was
actually true.

- **`sizeof` on an incomplete struct/union type never errored**
  (parser.c) — C11 6.5.3.4p1 requires `sizeof`'s operand to have a
  complete type; rcc's `sizeof` handling (both the `sizeof(type-name)`
  and `sizeof expr` forms) just read the type's `->size` field
  directly (0 for a struct/union that was only ever forward-declared,
  never defined with a `{...}` body) and returned that as a plain
  integer constant, instead of rejecting the expression. Reproduced
  directly: `typedef struct opaque OPAQUE; static OPAQUE i; int n =
sizeof(i);` compiled cleanly under rcc (`sizeof` silently returning 0) where gcc correctly errors twice over (`invalid application of
'sizeof' to incomplete type` and `storage size of 'i' isn't known`).
  Fixed by rejecting `sizeof` on any struct/union type whose `->size
== 0 && !->members` (the exact "still incomplete" test the codebase
  already uses at half a dozen other sites — `_Alignof`, `_Generic`,
  `alignas`, ...) in both the type-name and expression forms.
- **Casts had no scalar-type validation at all** (parser.c) — C11
  6.5.4p2: a cast's type name must be void or scalar, and (unless the
  target is void) the operand must also be scalar; struct/union
  values are never castable to or from anything else. rcc's cast
  parsing built the `ND_CAST` node completely unconditionally, so
  `(unsigned long)some_struct_value` silently "worked" — codegen just
  read/reinterpreted whatever bytes happened to be at the struct's own
  address as if they were the target scalar type. Fixed by validating,
  for any cast whose target isn't `void`: the target isn't a
  (non-vector) struct, and the operand isn't a (non-vector) struct or
  union — with two GCC-documented leniencies preserved exactly:
  casting a value to a union type (the GNU union-cast extension) is allowed when the
  operand's type matches one of the union's own members (checked via
  the existing `types_compatible_p()`), and casting a value to its own
  identical struct type is a tolerated no-op identity cast (unlike
  casting between two distinct struct types, even ones with identical
  member layout, which stays rejected). An array-typed operand is
  exempt outright — real C already decays it to a pointer before a
  cast ever sees it, so `(long)some_array` is ordinary, unrelated
  code, not an aggregate-cast violation.
  First implementation attempt over-tightened this and broke 24
  previously-passing torture/TCC/c-testsuite tests before landing on
  the exemptions above — all real, common C idioms: `(ptr_t)some_array`
  (array decay), `(t_be)0x100000000ULL` where `t_be` is a
  type-punning union (the GNU union-cast extension), and
  `(struct S)w->t.s` where the cast is redundant (`w->t.s` already has
  type `struct S`) but harmless — a pattern generated code and macros
  commonly produce.
  → unblocks: `test_ksh93`'s `iffe` self-test TEST 12 — both the
  `OPAQUE` case (rcc now correctly reports "is OPAQUE a type or
  typedef ... no", matching gcc) and the `NONOPAQUE` case (rcc now
  correctly reports "is NONOPAQUE a non-opaque struct ... yes") pass;
  `iffe`'s own self-test suite is now 161/161.

New regression test: `test/test_incomplete_sizeof_cast.c` — 9 cases
covering both the two invalid-C rejections this fix adds (opaque
`sizeof`, both type-name and expression forms; struct-to-scalar and
scalar-to-non-union-struct casts; cross-struct-type casts) and every
leniency it must preserve (sizeof/cast on a genuinely complete struct;
same-type struct-cast identity no-op; GNU union-cast from a matching
member type, rejected from a non-matching one; array-to-integer cast
via pointer decay). Full suite verified: TCC 118/118, Unit tests
207/207, Compliance 15/15, C-testsuite 220/220, Torture 3605/3609
(100% of non-skipped), Dg-error 34/34, Link 7/7 — 0 failed overall
(native Linux x86-64); confirmed clean on the mingw and arm64
cross-compile targets.

With both fixes above, `test_ksh93`'s full `bin/package make` now
completes without error (builds and installs `ksh`, `shcomp`,
`libshell.so`/`.a`, every man page and `fun/*` script) — the whole
build that used to die partway through `libast` now finishes cleanly.
`bin/package test` goes on to run ksh93's own real functional
regression suite (`cmd/ksh93/tests/*.sh` via `shtests`) for the first
time this project has ever reached it; `alias`/`append` pass cleanly,
`arith.sh` surfaces one further, unrelated lead (`cos*cos + sin*sin >
1.01`, a floating-point precision question, not yet root-caused) —
left for a future session rather than expanding this one's scope.
(Resolved 2026-08-12: that symptom was an rcc codegen bug — the
long-double assignment-expression value returning 0 — not precision;
see "Fixed (2026-08-12, ksh93 arith / IEEE truthiness / 64-bit
pointer-arithmetic session)" below.)

### Fixed (2026-08-10, continued — ARM64 long double return value)

While cross-checking the earlier x86-64 `long double` ABI fix (see
"Fixed (2026-08-10, register-spill/locals-collision session)" test
file above) against the arm64 cross target, `test_long_double_abi`
SIGABRTed under qemu-aarch64 even though the x86-64-side bugs it
targets don't apply to AAPCS64's classification at all. Two more,
independent ARM64-only bugs (codegen.c), the mirror image of the
x86-64 ST0/XMM0 gap fixed above:

- **`gen_funcall`'s post-call return-value read treated a `long
double`-returning call exactly like a plain `double` one** — a bare
  `fmov x{r}, d0`. AAPCS64 returns `long double` as a genuine 128-bit
  binary128 quad in v0, not a value narrowed into d0's low 64 bits;
  reading only d0 silently reinterpreted binary128's mantissa/exponent
  layout as binary64 garbage (confirmed via a 4-line
  `fabsl(-2.5L)` repro compiled and disassembled directly: the callee's
  real quad result in v0/q0 was there, but the caller only ever read
  d0). Fixed by narrowing the quad via libgcc's `__trunctfdf2` (the
  same routine `ND_VA_ARG`'s existing long-double narrowing already
  uses) before treating the result as an ordinary double.
- **A function's own `return` statement had the same bug in reverse**,
  for both flonum-to-long-double and integer-to-long-double returns
  (`long double f(long double x) { return x; }` and `long double
g(int n) { return n; }`): the value was placed directly in d0 (a
  plain double) instead of being widened onto the full v0 quad via
  libgcc's `__extenddftf2` (the same routine the argument pre-pass in
  `gen_funcall` already uses) — the caller's `fmov x{r}, d0`
  return-value read (previous bug, once fixed) would then narrow back
  down from an un-widened, garbage upper half.

ARM64's argument classification and prologue parameter loading already
handled `long double` correctly (a dedicated HFA/long-double path
independent of the ordinary float classification) — only the two
return-value sites above had the gap.

**Apple ARM64 correction (caught by CI, not local testing)**: the
first push of this fix unconditionally applied both `__trunctfdf2`/
`__extenddftf2` calls under `#ifdef ARCH_ARM64`, which broke macOS
CI's `test (macos-latest)` job (Torture regressed on `930622-2`,
`conversion`, and 3 others — the exact long-double tests this fix
targets). Root cause: Apple's arm64 ABI defines `long double` as
_identical_ to `double` (see `type.c`'s `ty_ldouble`: `size=8` under
`__APPLE__` vs `size=16` everywhere else) — Darwin has no 128-bit
binary128 `long double` at all, so there is no widen/narrow step to
do, and calling `__trunctfdf2`/`__extenddftf2` on an already-plain-
double value corrupted it. Both call sites are now further guarded
`#ifndef __APPLE__` (Linux ARM64 only); this repo has no local Darwin
cross-toolchain to test-compile against directly (only mingw and
Linux/arm64 cross are supported per this project's conventions), so
the fix was verified via `gcc -D__APPLE__ -DARCH_ARM64 -fsyntax-only`
(confirms the guarded block reduces to a no-op, restoring the
pre-existing — already-correct — Apple codegen path exactly) plus
CI's own macOS job on the follow-up push.

Extended the existing `test/test_long_double_abi.c` (previously
SysV-x86-64-only in scope) to also run on Linux/arm64 — it already had
no arm64 guard in `main()`, so once both bugs above were fixed it
passed unmodified; only its header comment was extended to document
the two ARM64 bugs alongside the four x86-64 ones. Full suite verified
clean on all three targets: native Linux x86-64 (TCC 118/118, Unit
tests 209/209, Compliance 15/15, C-testsuite 220/220, Torture 3605/3609
— 100% of non-skipped, Dg-error 34/34, Link tests 7/7, 0 failed
overall), mingw cross (`test_long_double_abi` cleanly skips via its
existing `_WIN32` guard — Win64's long-double-by-hidden-pointer ABI
remains a separate, out-of-scope gap, see above), and arm64 cross
(Torture 3599/3609 — 6 pre-existing, unrelated complex-number/
imaginary-constant failures documented above, unaffected by this fix;
`test_long_double_abi` now passes where it previously SIGABRTed).

### Fixed (2026-08-10, continued — LONG_MAX/atomic-load session)

While investigating the previously-uninvestigated `test_glib`,
`test_neovim`, `test_samba`, `test_tcpdump` rows above, several were
first blocked purely by missing system dev packages in this sandbox
(`libpcre`/`libpcap`/`libluv`/`libuv` — installed via `dnf`, not an
rcc concern). Once installed, `test_samba`'s waf configure hit a
genuine rcc bug on its very first substantive probe (`#include
<Python.h>` for the `pyembed` check):

- **`include/limits.h` hardcoded `LONG_MIN`/`LONG_MAX`/`ULONG_MAX` to
  their 32-bit values unconditionally** — even though the very same
  file's C23 `LONG_WIDTH`/`ULONG_WIDTH` macros a few lines below were
  already (separately, inconsistently) hardcoded to the correct
  64-bit LP64 value. On Linux/macOS x86-64 and ARM64 (`long` is 8
  bytes), `LONG_MAX` was wrong by construction.
  A plain `printf("%ld", LONG_MAX)` doesn't actually observe this: the
  header ends in `#include_next <limits.h>` chaining to glibc's real
  header, whose own (correct) redefinition wins by the time `main()`
  reads it. The bug only becomes externally visible when some OTHER
  header further down the SAME include chain computes something FROM
  `LONG_MAX`'s value at the point it's reached — before glibc's own
  correct redefinition — and bakes in the wrong answer permanently.
  glibc's `<bits/xopen_lim.h>` (pulled in transitively by
  `_XOPEN_SOURCE_EXTENDED`, itself pulled in by samba's own build
  flags ahead of `#include <Python.h>`) does exactly this for
  `LONG_BIT` (`#if LONG_MAX == 2147483647 then 32 else 64`): with the
  bug, `LONG_BIT` baked in 32 despite `LONG_MAX` itself later reading
  back correctly as 64-bit. CPython's own `pyport.h` cross-checks `#if
LONG_BIT != 8 * SIZEOF_LONG` and refuses to compile rather than
  silently miscompile — `"LONG_BIT definition appears wrong for
platform (bad gcc/glibc config?)."` — which is how this was found.
  Fixed by deriving `LONG_MIN`/`LONG_MAX`/`ULONG_MAX` and
  `LONG_WIDTH`/`ULONG_WIDTH` from `__LONG_MAX__`/`__SIZEOF_LONG__`/
  `__LONG_WIDTH__` (already correctly baked into `gcc_predefined.h` at
  build time from the real target gcc's own `-dM -E` dump: LP64 64-bit
  on Linux/macOS, LLP64 32-bit on Windows/mingw — confirmed by
  directly diffing `x86_64-w64-mingw32-gcc`'s own predefined-macro
  dump) instead of hardcoding either platform's value.

With that fixed, `#include <Python.h>` progressed to a second, deeper
bug inside `pyatomic_gcc.h`:

- **`__atomic_load(ptr, retptr, order)` — the non-`_n`, 3-argument
  form — was parsed identically to `__atomic_load_n(ptr, order)`'s
  2-argument form** (parser.c), silently misreading `retptr` itself as
  the memory-order argument (`parse_memory_order()` choked on the `&`
  it didn't expect: `"expected specific operator"`).
  `__atomic_store`'s parser entry already handles the identical
  ptr/valptr asymmetry between its `_n` and non-`_n` forms;
  `__atomic_load`'s entry never did. Fixed by parsing the extra
  `retptr` argument for the non-`_n` form and desugaring the whole
  call to `*retptr = __atomic_load_n(ptr, order)` (an `ND_ASSIGN` of
  an `ND_DEREF` over the existing `ND_ATOMIC_LOAD` node), mirroring
  `__atomic_store`'s reverse desugaring.
- **`ND_ATOMIC_LOAD`'s codegen never special-cased `float`/`double`
  target types at all** (codegen.c) — rcc represents a flonum VALUE
  sitting in a GP register as its DOUBLE bit pattern (float widened to
  double; see `ND_DEREF`'s identical `is_flonum` branch, used by every
  other flonum memory read), but `ND_ATOMIC_LOAD` just read the raw
  same-size bytes into a GP register like any integer load. For a
  `double` this happened to work by accident (its raw 8-byte pattern
  already IS a valid double bit pattern) — which is exactly what let
  this go unnoticed until a `float` atomic exposed it: the raw 4-byte
  pattern, zero-extended into the low 32 bits, got misread by every
  later flonum consumer as a genuine (garbage) DOUBLE bit pattern
  (confirmed via disassembly: `movq %r11,%xmm0; cvtsd2ss %xmm0,%xmm0`
  — treating an unwidened float pattern as if it were already a
  double's). Fixed by adding the same movss/cvtss2sd/movq-to-GP (x86-64)
  or ldr/fcvt/fmov (ARM64) widening sequence `ND_DEREF` already uses,
  with a plain barrier substituted for ARM64's raw-GP `ldar`
  acquire-ordered load (which only handles integer registers) when the
  target type is a flonum.
  Note: `__atomic_load_n`/`__atomic_exchange_n`/etc. (the `_n`,
  register-return builtins) don't actually accept `float`/`double` at
  all in real C — both gcc and clang reject them at compile time
  (`"operand type 'float *' is incompatible"`); only the generic
  `__atomic_load`/`__atomic_store` forms (which route through a
  caller-supplied pointer, exactly the form this session's bug 1 fix
  unblocks) accept arbitrary types including flonums. rcc doesn't
  currently diagnose the invalid `_n`-on-flonum case either (a
  separate, minor, missing-diagnostic gap, not a miscompile — out of
  scope here).

New regression tests: `test/test_limits_long_lp64.c` (LP64
`LONG_MIN`/`LONG_MAX`/`ULONG_MAX` values, plus the exact
`_XOPEN_SOURCE_EXTENDED`/`LONG_BIT` cross-check reproduction) and
`test/test_atomic_load_flonum.c` (`__atomic_load` on `float`/`double`
via a caller-supplied retptr, both `__ATOMIC_RELAXED` and
`__ATOMIC_SEQ_CST`, plus a cross-check that the pre-existing plain
integer `__atomic_load`/`__atomic_load_n` forms keep working). Full
suite verified clean on all three targets: native Linux x86-64 (TCC
118/118, Unit tests 211/211, Compliance 15/15, C-testsuite 220/220,
Torture 3605/3609 — 100% of non-skipped, Dg-error 34/34, Link tests
7/7, 0 failed overall), mingw cross (both new tests PASS), and arm64
cross (both new tests PASS).

With both fixes, `test_samba`'s waf configure now progresses far past
its earlier `Python.h`-triggered failure into unrelated dependency
checks (pam, iconv, ncurses, readline, ...), currently blocked on a
missing build-time Perl CPAN module (`Parse::Yapp::Driver`) in this
sandbox — an environment gap, not an rcc issue. `test_neovim` also now
configures past its `Luv`/`Libuv` dependency checks (after installing
beyond this session's scope. `test_glib` progressed from a
configure-time failure (missing `libpcre`) all the way to real
compilation; `test_tcpdump` progressed from a configure-time failure
(missing `libpcap`) all the way to a genuine rcc register-allocator
bug in its own radiotap decoder, root-caused and fixed separately —
see the next section.

### Fixed (2026-08-10, continued — nested-ternary register-allocator session)

Continuing the `test_tcpdump` investigation from above: once
`libpcap-devel` was installed, tcpdump built and ran cleanly except
for 2 of its own 636 tests (`802.11_exthdr`, `802.11_rx-stbc`) —
_wrong output_, not a crash: real radiotap capture files decoded to a
plausible-looking but incorrect frame summary (`[bit 17]` instead of
the golden `[bit 32]`/`[bit 15]`, MCS/GI/RX-STBC fields silently
missing).

- **A deeply nested ternary expression, with a non-trivial
  sub-expression re-embedded at every level, silently computed the
  wrong result** (codegen.c, `ND_COND`) — root-caused to
  `print-802_11.c`'s `BITNO_32`/`BITNO_16`/`BITNO_8`/`BITNO_4`/
  `BITNO_2` macro chain (a classic bit-scan idiom: `((x) >> N) ? K +
inner((x) >> N) : inner(x)`, 5 levels deep), used to find the lowest
  set bit of radiotap's 32-bit "present flags" word
  (`bitno = BITNO_32(present ^ next_present);`). rcc's own
  preprocessor expansion of that macro chain was confirmed
  byte-for-byte identical to gcc's (`-E` diff) — the bug was purely in
  codegen, and only at 4+ levels of ternary nesting with a
  multi-token argument (a bare-variable argument, or 3 levels of
  nesting, were not enough to trigger it; isolated via a from-scratch
  minimized C repro built independently of tcpdump's own source,
  cross-checked against a `__builtin_ctz`-based oracle across all 32
  bit positions).
  Root cause: `ND_COND`'s result register was `alloc_reg()`'d up
  front, before evaluating the condition or either branch, and held
  reserved through the ENTIRE recursive evaluation of both. Harmless
  for a single, non-nested ternary, but for a NESTED one -- each
  branch itself another `ND_COND` -- every nesting level's own result
  register stacked up simultaneously even though none of them held a
  real value until the very end, exhausting the 12-GP-register x86-64
  pool several levels sooner than an equivalent unnested computation
  of the same total complexity would, forcing spills under pressure
  that shouldn't have existed. The register allocator's spill/reload
  bookkeeping under that specific pressure pattern then silently
  corrupted one live value (confirmed via disassembly: a spilled
  register was reloaded from its stack slot and immediately
  overwritten by an unrelated store before ever being read back —
  the exact "an alloc_reg() that spilled an outer expression's live
  value... a subsequent fresh allocation... can then silently reclaim
  it" failure mode already described in several other comments
  throughout codegen.c, here triggered by `ND_COND`'s own avoidable
  register-pressure overhead rather than genuine call-argument
  complexity).
  Fixed by deferring the result register's allocation until each
  branch's own value is actually ready to be moved into it (right
  before the first `asm_mov_reg_reg` that needs it), instead of
  reserving it for the whole subtree up front -- cutting one
  register's worth of artificial pressure per nesting level, on both
  the x86-64 and ARM64 codegen paths. A `void`-typed branch (e.g.
  `(void)0`) correctly yields no register at all now, rather than
  wastefully allocating one that's never written.

New regression test: `test/test_deep_nested_ternary_regalloc.c` — the
exact `BITNO_32` chain applied to every one of the 32 possible single
set bits, cross-checked against a boring/obviously-correct reference
implementation, plus the precise tcpdump-derived
`present ^ next_present` shape. Full suite verified: TCC 118/118, Unit
tests 212/212, Compliance 15/15, C-testsuite 220/220, Torture
3605/3609 (100% of non-skipped), Dg-error 34/34, Link tests 7/7, 0
failed overall; confirmed clean (both the new test and the existing
`test_gnu_ternary_omit_promote.c`, which exercises `ND_COND`'s other
recent fix, PASS) on the mingw and arm64 cross-compile targets.
`test_tcpdump`'s own `make check` now passes all 636 of its tests
(rebuilt `print-802_11.c` and relinked against the fixed compiler,
confirmed `802.11_exthdr`/`802.11_rx-stbc` now match their golden
output exactly); a full fresh harness run (`test/linux_thirdparty.bash
test_tcpdump`) confirms `rc=0`. **`test_tcpdump` is fully fixed.**

### Fixed (2026-08-11, forward-referenced local-label binding / ELF visibility session)

Continued investigating `test_libgmp` (GNU MP), whose row above still
carried a stale, long-superseded status from an early configure-time
failure; many prior sessions' worth of assembler fixes (not all
individually logged here) had already gotten `libgmp.so` most of the
way through its own build. This session's fresh `make check-all`-driven
rebuild hit a genuine linker failure building the shared library:

- **A local label (no `.globl`) referenced via a forward `call`/`jmp`/
  `%rip`-relative `lea` _before_ its own definition ended up bound
  `STB_GLOBAL` in the assembled object's symbol table instead of the
  correct `STB_LOCAL`** (`src/asm.c`) — even though it was never
  `.globl`'d anywhere. Root cause: `ensure_sym()`, used by every
  forward-reference site, creates the not-yet-defined symbol with a
  _speculative_ `SB_GLOBAL` binding (needed so a reference that turns
  out to be genuinely external — never locally defined in this
  translation unit — still produces a valid, linkable relocation). But
  once the label _was_ later defined locally via `define_label()`,
  nothing ever downgraded that guess back to `SB_LOCAL`:
  `define_label()` only ever _upgraded_ LOCAL→GLOBAL on an explicit
  `.globl`, never the reverse. Confirmed via a minimal repro (`call
Lbar` / `lea Lbar(%rip), %rax` above `Lbar:`, no `.globl`): rcc
  emitted `Lbar` as `GLOBAL`, real GNU `as` as `LOCAL`.
  This silently broke real multi-object-file linking: GMP's own
  `mpn/x86_64/{mul,sqr,mullo,redc_1,mod_34lsub1,...}_basecase.asm`
  files each define their own _private_, identically-named local
  helper labels (`Ltab`, `Laddmul_outer_0..3`, ...), forward-referenced
  from earlier in the same file via exactly this shape (a
  computed-dispatch `lea LABEL(%rip), %r14` / later `jmp *%r14`
  idiom). Once wrongly promoted to GLOBAL, linking `libgmp.so` from all
  of those objects together failed outright: `ld: multiple definition
of 'Ltab'` / `'Laddmul_outer_1'` / etc. — real, disjoint per-file
  local symbols colliding as if they were the same global one.
  Fixed by adding `ObjSym.bind_pinned` (`obj.h`): true only once a
  _real_ `.globl`/`.weak` directive has set a symbol's binding
  explicitly (both directive handlers now set it). `define_label()`'s
  two call sites now gate `is_global`/`is_weak` on `bind_pinned` rather
  than the symbol's raw current binding, and `define_label()` itself
  downgrades an unpinned `SB_GLOBAL` back to `SB_LOCAL` when a label
  turns out to be defined without ever being pinned — while a real
  `.globl` (before _or_ after the label, both orders checked) still
  correctly pins it GLOBAL.
- **`.hidden`/`.protected`/`.internal` (ELF symbol visibility) were not
  recognized at all**, silently falling through to a no-op regardless
  of the source — surfaced immediately once the binding bug above was
  fixed and the library build reached its next real linker error:
  `relocation R_X86_64_PC32 against symbol 'mpn_invert_limb_table' can
not be used when making a shared object; recompile with -fPIC`. Root
  cause: GMP's own `PROTECT()` m4 macro (used on internal-linkage-but-
  cross-object-file data tables like `mpn_invert_limb_table`, referenced
  from a _different_ `.asm` file via a plain `%rip`-relative LEA)
  expands to `.hidden`; with no visibility ever recorded, the symbol
  stayed at default ELF visibility despite being GLOBAL-bound —
  `ld -shared` correctly refuses a direct PC32 relocation against a
  default-visibility GLOBAL symbol (which could in principle be
  interposed by another shared object at load time, something a bare
  PC-relative displacement can't express); `STV_HIDDEN` tells the
  linker this symbol can never be interposed, making the direct
  reference safe.
  Fixed by adding `ObjSym.visibility` (`obj.h`,
  `STV_DEFAULT`/`STV_INTERNAL`/`STV_HIDDEN`/`STV_PROTECTED`) and a
  `.hidden`/`.protected`/`.internal` directive handler (`src/asm.c`)
  that sets it (creating a not-yet-seen symbol as a `SEC_UNDEF` stub if
  needed, exactly like `.globl`/`.weak`); `elf_write.c` now emits it as
  both the local- and global-symbol table entries' `st_other` byte
  instead of always writing 0. Verified byte-for-byte identical to real
  GNU `as`'s own `objdump -t` output (`.hidden`/`.protected`/
  `.internal` name-column prefix) for the same source.

With both fixed, `libgmp.so`/`libgmp.a` now build and link completely
clean (previously never got past the linker at all). The resulting
library's own `tests/mpn/t-*` runtime suite still shows 47 failures
(`t-mul`, `t-invert`, `t-bdiv`, `t-hgcd`, `t-gcd_11`, ... — SIGABRT/
SIGSEGV or wrong values, all inside `mpn_mul_basecase`/`toom22_mul`/
`toom33_mul`/`dcpi1_bdiv_qr`) — **investigated and confirmed NOT an rcc
bug**: built the identical GMP 6.3.0 source tree with the system's real
`gcc`+GNU `as`+GNU `ld` instead (`CC="gcc -std=gnu17"` to work around
this GCC 15's C23-default-prototype rejection of GMP's own 2004-era
`configure` probe, unrelated to rcc) as a correctness oracle: every one
of the same tests (`t-invert`, `t-mul`, `t-mullo`, `t-sizeinbase`,
`t-gcd_11`, `t-fib2m`, `t-bdiv`) crashes with the _identical_ exit code
against the real-gcc-built library too. This is a pre-existing GMP
6.3.0 / environment incompatibility (this old release's `k8`-tuned
hand-written asm vs. this sandbox's specific CPU/kernel/glibc
combination, or a genuine upstream GMP bug at this vintage) —
completely unrelated to and unaffected by either fix above, matching
this project's own "no pre-existing rcc bugs" invariant. Not chased
further (out of scope: real 3rd-party source, not an rcc regression).

New regression tests: `test/test_asm_forward_local_label_binding.c` (4
cases: forward-referenced `call`-target and `%rip`-LEA-target labels
both must bind LOCAL; two different translation units defining the
same-named local label `Ltab` this way — the exact real libgmp
shape — must both bind LOCAL rather than colliding as GLOBAL; an
explicit `.globl` must still correctly pin GLOBAL) and
`test/test_asm_hidden_visibility.c` (4 cases: `.hidden`/`.protected`/
`.internal` each produce the matching ELF visibility, byte-for-byte
checked against real GNU `as`'s own `objdump -t` output shape; a plain
`.globl` with no visibility directive stays at default visibility).
Full suite verified: Unit tests 214/214, Compliance 15/15,
C-testsuite 220/220, Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link tests 7/7 — 0 failed overall (native Linux x86-64).

### Fixed (2026-08-11, continued — AES-NI/SSE2 instruction encoder session)

Investigated `test_libressl` (LibreSSL, an OpenSSL fork) fresh — never
previously triaged (its row above just showed "—"). `./configure &&
make check` failed immediately assembling
`crypto/aes/aesni-elf-x86_64.S` (OpenSSL/LibreSSL's hand-optimized
AES-NI implementation, `crypto/aes/asm/aesni-x86_64.pl`-generated):

- **rcc's assembler had zero support for the AES-NI instruction set**
  (`AESENC`/`AESENCLAST`/`AESDEC`/`AESDECLAST`/`AESIMC`/
  `AESKEYGENASSIST`) — `error: unknown x86 instruction: aesenc` (etc.)
  on every one of the 200+ AES-round instructions in the file.
- **Several SSE2/SSSE3 instructions whose x86_enc.c encoders already
  existed** (used internally by `codegen.c` for `vector_size` types:
  `SHUFPS`, `PSHUFB`, and the whole packed-integer family `PADDD`/
  `PSUBD`/`PADDQ`/`PSUBQ`/`PADDW`/`PSUBW`/`PADDB`/`PSUBB`/`PAND`/`POR`/
  `PCMPEQD`/`PCMPGTD`) **were never wired into the raw-assembly-text
  mnemonic dispatch at all** (`src/asm.c`'s `encode_x86()`) — only
  `PXOR` was. A hand-written `.S` file using any of the others hit the
  same "unknown x86 instruction" error despite the encoder existing.
- **Several more instructions had no encoder at all**: `PSHUFD`
  (dword-lane shuffle), the "group 14" shift-by-immediate family
  `PSLLDQ`/`PSRLDQ`/`PSLLQ`/`PSRLQ`, and `PINSRW` (word-lane insert
  from a memory operand) — needed by `crypto/modes/ghash-*.S` (GCM)
  and `crypto/rc4/rc4-*.S` (RC4's SSE2 fast path) respectively, found
  by iterating the batch harness once each new gap surfaced.

Fixed by adding the missing encoders (`src/x86_enc.c`/`.h`:
`x86_aesenc`/`x86_aesenclast`/`x86_aesdec`/`x86_aesdeclast`/
`x86_aesimc`/`x86_aeskeygenassist`, `x86_pshufd`, a shared
`group14_shift_imm()` helper backing `x86_pslldq`/`x86_psrldq`/
`x86_psllq`/`x86_psrlq`, `x86_pinsrw_rm`) and wiring every one of the
above — new and pre-existing — into `encode_x86()`'s mnemonic
dispatch. Every encoding verified byte-for-byte identical to real GNU
`as`'s own output for the same source (register operands `%xmm0`-
`%xmm3`/memory base `%rbx`, all `< 4`, to sidestep a separate,
harmless, purely cosmetic pre-existing quirk noted below).

With these fixed, `crypto/aes/aesni-elf-x86_64.S`,
`crypto/modes/ghash-elf-x86_64.S`, and `crypto/rc4/rc4-elf-x86_64.S`
all now assemble cleanly. `libressl`'s build then reaches a **new,
separate, and substantially larger gap**: 21 files under
`crypto/bn/arch/amd64/` (OpenSSL/LibreSSL's `s2n-bignum`-derived
constant-time bignum arithmetic — `bignum_mul_4_8.S`,
`bignum_modadd.S`, `bignum_sqr_6_12.S`, ...) open with `.intel_syntax
noprefix` and use Intel-syntax operand order/register spelling
throughout (`adcx r10, rax` — no `%`/`$` sigils, reversed
dst/src order, `[base+index*scale+disp]`-style memory syntax where
used) — **rcc's assembler has no Intel-syntax parsing mode
whatsoever**, only AT&T. This is a genuinely large, separate
undertaking (a full parallel operand-order/memory-syntax parser for
every instruction `.intel_syntax` sections can reach, not a handful of
missing mnemonics) — out of scope for this session, in the same
category as the already-documented AVX-512/`_BitInt`/`defer`-statement
gaps under "Needs fixing" below. Left for a future session.

**Pre-existing, unrelated, purely cosmetic quirk noted in passing**:
`maybe_rex()` (`src/x86_enc.c`)'s "does this operand need a REX
prefix" threshold is `>= X86_RSP` (4) — correct for its originally
intended 8-bit-register-remap use (SPL/BPL/SIL/DIL vs. AH/BH/CH/DH,
where 4-7 genuinely does need REX) and for real extended-register
(R8-15/XMM8-15) detection, but several callers (all the new XMM
encoders above included, following the exact same pattern every
pre-existing XMM encoder already uses) also apply it to XMM register
indices and memory base/index registers in the 4-7 range, where the
correct threshold is 8, not 4 — emitting one semantically-inert extra
REX `0x40` byte (always a pure no-op for non-8-bit operands) instead
of omitting it. Confirmed via disassembly comparison against real GNU
`as`: correct bytes decode identically either way, just not
byte-for-byte GNU-as-identical when an operand register/memory-base
index happens to land in [4,7]. Not fixed — it's genuinely harmless,
affects dozens of already-shipped, already-tested XMM encoders beyond
the scope of this session's fix, and a proper fix (splitting
`maybe_rex()`'s GP-8-bit-register use from its XMM/general-register
use into two correctly-thresholded helpers, then re-auditing every
existing call site) is its own separate, carefully-scoped refactor.

New regression test: `test/test_asm_aesni_sse2.c` — 25 sub-cases (one
per instruction), each checked against its exact, GNU-as-confirmed
encoded bytes via `objdump -s`. Full suite verified: Unit tests
215/215, Compliance 15/15, C-testsuite 220/220, Torture 3605/3609
(100% of non-skipped), Dg-error 34/34, Link tests 7/7 — 0 failed
overall (native Linux x86-64); the new test also confirmed passing
standalone on the mingw cross target.

### Fixed (2026-08-11, continued — atomic fetch-op narrow-argument session)

**test_nginx**: `nginx-tests/upstream_resolve_reload.t` hung until its
900s harness timeout — every worker process spun forever inside
`ngx_rwlock_wlock()`'s CAS retry loop, never observing the lock drop
back to 0.

**Root cause** (codegen.c, `ND_ATOMIC_FETCH_OP`): nginx's
`ngx_atomic_fetch_add(lock, -1)` macro-expands to
`__sync_fetch_and_add(lock, -1)` on platforms with GCC atomic builtins
(not the `ngx_atomic_amd64.h` inline-asm fallback, which is only used
when the builtins are unavailable). The value argument's own static
type (`-1` is a plain 4-byte `int`) is narrower than the pointee type
being operated on (`ngx_atomic_t` = `intptr_t`, 8 bytes) — a completely
ordinary implicit-conversion shape the parser leaves as-is (`node->rhs`
keeps its own `int` type; only `node->ty`, the _result_ type, is set
from the pointee). Codegen's `gen(node->rhs)` correctly materializes
the value at its own (4-byte) width, but every op path (x86-64 `lock
xadd`, the cmpxchg-loop combine for or/xor/and/nand, and ARM64's
ldxr/stxr loop) then operates on the full pointee-width (8-byte)
register without first widening it. On x86-64, writing a 32-bit
register implicitly zeroes the register's upper 32 bits, so `-1`
landed as its _zero-extended_ bit pattern (`0x00000000FFFFFFFF`)
instead of sign-extended (`0xFFFFFFFFFFFFFFFF`) — turning
`ngx_rwlock_unlock()`'s intended "subtract 1" into "add 4294967295",
which left the lock word permanently non-zero and every subsequent
`ngx_rwlock_wlock()` spinning forever. Confirmed via a minimal
standalone repro and `objdump` disassembly (the value materialized as
`mov $1,%r11d; neg %r11d` — no `movslq` sign-extension — then fed
straight into a 64-bit `lock xadd`), and via `gdb` stepping an
unoptimized debug build to rule out inlining or a second codegen path
before finding the actual `__sync_fetch_and_add` builtin parse site
(`parser.c`) and its `ND_ATOMIC_FETCH_OP` codegen case.

Fixed by sign/zero-extending `r_val` (the generated value register) to
the pointee's width immediately after `gen(node->rhs)`, before any
op-specific codegen, using the same source-signedness rule
(`node->rhs->ty->is_unsigned`) every other implicit narrow-to-wide
integer conversion in codegen.c already follows. Applies uniformly to
all six ops (add/sub/or/xor/and/nand) and both architectures, since the
extension now happens once, ahead of the `#ifdef ARCH_ARM64` split.

Rebuilt nginx with the fixed compiler and reran the _actual_
`nginx-tests` suite end to end (not just the one previously-hung test):
`prove .` — 492 files, 2600 tests, **all successful**.

**Second, unrelated bug found investigating the same rebuild**: a
`resolve_include_next()` (preprocess.c) gap where `RCC_INCDIR` (an
absolute path baked into the binary — every build, installed or not,
defaults it to `/usr/local/include/rcc`) and the relative `"include"`
search-path fallback (added whenever it differs from `RCC_INCDIR`) can
both physically exist with byte-identical bundled-header content — the
normal shape once `make install` has ever run on a machine that also
has a source checkout on hand. `#include <stdio.h>` resolves through
`RCC_INCDIR` first (checked before the fallback); `#include_next
<stdio.h>` from _inside_ that file used to advance past only
`RCC_INCDIR` and land on the fallback's identical copy next — a real,
non-trivial file (not a one-line forwarder, so the existing
`is_noop_forward_to_active()` no-op detector doesn't catch it), but
its own include guard is already defined by the first copy, so its
entire body — including its own `#include_next <stdio.h>` that would
reach the real system header — silently no-ops. `#include_next`
"succeeds" at a file that contributes nothing, leaving `FILE` (and
everything else glibc's real `<stdio.h>` declares) undeclared, with no
error. Fixed by having the current-directory scan skip past _both_
bundled-header slots together whenever either one matches, not just
whichever slot's physical path happened to match. (This specific
failure mode was surfaced by a stale, install-poisoned `preprocess.o`
left over from an earlier `make install` in this session rather than a
normally-reachable path — `make clean && make` alone also "fixes" it
by rebuilding with consistent flags — but the underlying search-order
bug is real and independently reproducible without any stale build
state involved, so it's fixed and covered on its own merits.)

New regression tests: `test/test_atomic_fetch_op_narrow_arg.c` (6
cases: the exact `fetch_add(long*, -1)` shape, a narrow non-constant
variable operand, fetch_sub/or/and, and an unsigned narrow value that
must zero- rather than sign-extend); `test/test_include_next_dup_incdir.c`
(builds a byte-identical duplicate of rcc's own bundled `include/stdio.h`
at the relative `"include"` fallback location and confirms
`#include_next` still reaches the real system header instead of being
swallowed). Full suite verified: Unit tests 217/217, Compliance 15/15,
C-testsuite 220/220, Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link tests 7/7 — 0 failed overall (native Linux x86-64); both
new tests also confirmed passing standalone on the mingw and arm64
cross targets.

### Fixed (2026-08-11, continued — lexer non-ASCII infinite loop / weak variable attribute session)

**test_go**: `go tool dist`'s C bootstrap phase (`cmd/dist`, then `lib9`/
`libbio`/`liblink` and the `5c`/`6c`/`8c`/`9c`/`5g`/`6g`/`8g` Plan9-style
compilers, all C) previously either hung indefinitely (timing out the
420s harness budget with no diagnostic at all) or, once that was fixed,
failed to link with "multiple definition" errors. Both were genuine rcc
bugs, found by attaching gdb to the hung/stuck compile and sampling its
call stack.

- **Any non-ASCII byte that decoded to a non-identifier-start codepoint
  hung the lexer forever** (lexer.c, `lex_one()`) — dispatching a byte
  `>= 0x80`, the lexer decoded one UTF-8 codepoint and checked
  `is32_ident1()` (valid identifier-_start_ character?); when it wasn't
  (e.g. U+00B7 MIDDLE DOT — General Category Po, punctuation, not a
  letter — appearing bare, not as part of an adjacent ASCII identifier's
  own continuation scan), the code did a bare `continue` without ever
  advancing `p`. The outer dispatch loop then re-examined the identical
  byte position, re-decoded the identical codepoint, and reached the
  identical "not identifier-start" verdict again — forever. This needs
  no malformed UTF-8, just an ordinary punctuation-class Unicode
  character outside a string/char literal and outside an identifier's
  own continuation scan — a genuine DoS-class bug reachable by any
  source file, not specific to Go. Trigger: `include/runtime/funcdata.h`
  (included by nearly every `lib9`/`libbio`/`liblink` `.c` file)
  defines `NO_LOCAL_POINTERS` as a macro whose replacement list contains
  `runtime\xc2\xb7no_pointers_stackmap(SB)` — Plan9/Go's
  "package·symbol" assembly-name convention (U+00B7 is one of the
  handful of characters Unicode's `PropList.txt` explicitly lists under
  `Other_ID_Continue` for exactly this legacy use, which real GCC
  honors — a separate, smaller gap not fixed this session, since it
  only affects _how the identifier is tokenized_ — one token vs. three
  — not whether compilation terminates or succeeds). A `#define`'s
  replacement list is tokenized unconditionally regardless of whether
  the macro is ever expanded, so every file that merely `#include`s
  this header hung, whether or not it called `NO_LOCAL_POINTERS`.
  Found via `gdb -p <stuck-pid> -batch -ex bt`, repeated across several
  seconds: 8 of 10 samples landed in the identical
  `binary_search`/`isTR39_start(cp=183)` call chain (183 = 0xB7).
  Fixed by always advancing `p` on this path — past the decoded
  codepoint's bytes when `decode_utf8()` made progress, or past one
  byte otherwise (a malformed sequence that didn't decode at all) — and
  emitting the skipped bytes as their own preprocessing token (C11
  6.4p3: any leftover non-white-space character forms its own
  pp-token), so a macro body that merely stores this text still
  reproduces it byte-for-byte if ever expanded/stringized.
- **`**attribute**((weak)) on a global variable was silently dropped**,
  in two independent, stacked ways:
  1. **Parser** (parser.c, `declarator()`) — a _trailing_ weak
     attribute right after the declared identifier (`int x
__attribute__((weak));`) was parsed into a local `trail_attr`
     struct and then simply never read; only the separate _pointer_-
     attribute case just above it (`int *p __attribute__((weak))`,
     function-pointer-shaped declarators) propagated into
     `pending_weak`. A _prefix_ weak attribute
     (`__attribute__((weak)) int x;`) parsed correctly into
     `attr.is_weak`, but that was equally unused for a plain variable
     (see next point) — so _neither_ spelling worked for a variable
     before this fix, only for functions.
  2. **Codegen** (codegen.c, the `prog->globals` emission loop) — even
     once `var->is_weak` was correctly set, the `.bss`/`.data` symbol-
     binding choice only ever checked `var->is_static` (`SB_LOCAL` vs.
     `SB_GLOBAL`), never `var->is_weak` — so a weak variable's own
     definition still carried `STB_GLOBAL`, not `STB_WEAK`, in the
     emitted object.
     Trigger: `include/u.h`'s `AUTOLIB(x)` macro — `#define AUTOLIB(x) int
__p9l_autolib_ ## x __attribute__ ((weak));` — used once per
     translation unit throughout `lib9`/`libbio`/`liblink` to "tip off 9l
     to autolink" a library. Every file including a given library's header
     (e.g. `bio.h`) emits its own `__p9l_autolib_bio` with the identical
     name; without real weak linkage every one of those became a hard
     "multiple definition of `__p9l_autolib_bio`" link error building
     `liblink.a`/`libbio.a`'s own archive members, instead of the silently-
     merged single definition weak linkage exists for. Confirmed via
     `objdump -t`/`nm`: rcc emitted `g O .bss` where real GCC (on the
     identical source) emits `w O .bss` / `V`.

With both fixed, rcc successfully builds the entire go1.4 C bootstrap
chain end to end: `cmd/dist`, `lib9`, `libbio`, `liblink`, and the
`5c`/`6c`/`8c`/`9c`/`5g`/`6g`/`8g` compilers/assemblers/linkers, which
then successfully self-bootstrap and begin compiling the Go standard
library's own `.go` sources with the freshly-built `6g`. That later
stage now hits `runtime/mprof.go:487: r.Stack0 undefined (type
*BlockProfileRecord has no field or method Stack0)` — `Stack0` is a
field of `StackRecord`, anonymously embedded in `BlockProfileRecord`
for Go's standard field-promotion rules to make `r.Stack0` resolve to
`r.StackRecord.Stack0`; the rcc-built `6g` fails to resolve it. This is
several layers removed from rcc itself (a possible miscompilation
inside the _bootstrapped Go compiler's own_ embedded-struct-field-
promotion logic, written in Plan9 C, itself compiled by rcc — not
anything in the `.go` sources or in rcc's own C-level correctness) and
needs a dedicated, properly-scoped investigation into `6g`'s own gc/\*.c
sources; not attempted this session. `test_go` moves from completely
untriaged (timed out with zero diagnostic information) to "C bootstrap
phase fully fixed, blocked on one further layer" — a real, substantial
step forward even though the target doesn't fully pass yet.

New regression tests: `test/test_lexer_nonascii_infinite_loop.c` (the
exact middle-dot-in-macro-body trigger, with a `timeout`-guarded
subprocess invocation so a regression fails cleanly instead of hanging
the suite; plus a genuinely valid Unicode identifier to confirm the fix
didn't break the normal path); `test/test_weak_variable_attribute.c`
(4 cases: trailing weak on a tentative/`.bss` global, trailing weak on
an initialized/`.data` global, prefix weak, and an ordinary non-weak
global that must NOT become weak as a side effect — verified via `nm`'s
clean 3-field output rather than `objdump -t`'s fixed-width flag
column, whose internal spaces defeat naive whitespace-based parsing).
Full suite verified: Unit tests 219/219, Compliance 15/15, C-testsuite
220/220, Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Link
tests 7/7 — 0 failed overall (native Linux x86-64); both new tests also
confirmed passing standalone on the mingw and arm64 cross targets (the
lexer test's POSIX `timeout N cmd` guard needed a Windows-specific
no-op, since cmd.exe's own built-in `timeout` takes an incompatible
`/T <seconds>` syntax).

### Fixed (2026-08-11, continued — linker command unquoted-path session)

Investigated `test_muon` (never previously triaged beyond "muon
self-tests, some pass, some fail"). `build/muon -C build test` hit a
genuine rcc bug on its very first compiler-capability probe:

- **The fallback GCC-linker invocation built its `system()` command
  line by substituting every path (the `-o` output path, each object
  file, the bundled mingw/darwin runtime object) via a bare, unquoted
  `%s`** (`main.c`) — reached whenever the native linker declines
  (e.g. any program needing dynamic libc symbols like printf/fprintf).
  A path containing a space split into extra shell words: `ld` then
  reported `cannot find <tail-after-the-space>: No such file or
directory` instead of ever seeing the single, intended path.
  Trigger: muon's own test harness names one of its native
  compiler-probe build subdirectories literally `4 tryrun` (a space in
  the directory name); linking a trivial printf-using probe program
  into `.../native/4 tryrun/.muon/compiler_check_exe` hit this exactly
  — `ld: cannot find tryrun/.muon/compiler_check_exe: No such file or
directory` — breaking every one of muon's own compiler-capability
  probes that used it (`c compiler: runs String should succeed`, the
  very first check muon's own toolchain detection performs).
  Fixed by double-quoting every path substituted into the linker
  command string, matching the existing `path_is_shell_safe()`-gated
  double-quote convention the `-S` disassembly invocation already
  uses (same rationale: double-quote-and-reject beats attempting to
  escape across both POSIX sh and cmd.exe dialects) — rejecting a path
  containing a genuine shell metacharacter with a clean error instead
  of either breaking or, worse, being injectable.

With this fixed, `test_muon`'s own suite moved from immediately
breaking on its first probe to 337/387 passing (87%); of the 32
remaining failures, the 10-case `common/273 both libraries` cluster
was cross-checked against real GCC linking the identical rcc-produced
`.so`/`.o` files (`gcc -no-pie -o main main.p/src/main.c.o
libwith_library.so -lm`) and fails **identically** — a genuine
transitive-shared-library `-rpath-link` limitation in muon's own
generated `build.ninja` link rule, not an rcc bug. The remaining ~22
failures were not individually triaged this session; left for a
future one.

New regression test: `test/test_link_path_with_space.c` (compiles and
links a printf-using program into an output path containing a space,
then runs the resulting binary and checks its output — reproduces the
exact muon directory-naming shape). Full suite verified: Unit tests
220/220, Compliance 15/15, C-testsuite 220/220, Torture 3605/3609
(100% of non-skipped), Dg-error 34/34, Link tests 7/7 — 0 failed
overall (native Linux x86-64); the new test also confirmed passing
standalone on the mingw and arm64 cross targets.

### Fixed (2026-08-11, continued — alloca() argument-count crash session)

Continued investigating `test_muon`'s remaining failures (of 32, one
session earlier fixed the linker-path bug that got 337/387 passing).
`common/36 has function` — muon's own "does the C library provide
`alloca`" capability probe — compiles `int main(void) { return
alloca(); }` (calling `alloca` with zero arguments, deliberately: the
probe only cares whether the _name_ resolves, not whether the call is
well-formed) and expects a clean compile error.

- **`alloca()` called with the wrong number of arguments crashed rcc
  internally instead of producing a diagnostic** (codegen.c,
  `gen_funcall()`) — `alloca` gets special codegen (inlined stack
  adjustment, no real function call) purely by name match
  (`call_target == bi_s_alloca`), without needing any declaration in
  scope — unlike an ordinary function, there's no prototype for the
  normal argument-count checker to validate against. Every
  specialized alloca codegen path (there are several, one per
  register-pressure/architecture variant) unconditionally read
  `node->args` (the size expression) assuming exactly one argument was
  given; calling `alloca()` with zero arguments left `gen(node->args)`
  reading a NULL `Node`, surfacing many calls later as an opaque
  internal `"Invalid register -1 in main"` crash instead of any real
  diagnostic. The reverse (too _many_ arguments, e.g. `alloca(16,
32)`) silently compiled clean too — the extra argument was simply
  never read, not rejected. Real GCC treats `alloca` as a builtin with
  a known one-argument prototype even without any declaration in
  scope, so it cleanly reports `"too few arguments to function
'alloca'; expected 1, have 0"` for the zero-argument case.
  Fixed by validating the argument count right where `alloca`'s
  special codegen is first recognized (before any of the specialized
  paths run), rejecting anything other than exactly one argument with
  the same GCC-style diagnostic.

New regression test: `test/test_alloca_argcount.c` (zero arguments
must be a clean compile error, not a crash; too many arguments must
also be a clean compile error, not a silent accept; the correct
single-argument form must still compile, link, and run correctly).
Full suite verified: Unit tests 221/221, Compliance 15/15, C-testsuite
220/220, Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Link
tests 7/7 — 0 failed overall (native Linux x86-64); the new test also
confirmed passing standalone on the mingw and arm64 cross targets.

### Fixed (2026-08-11, continued — \_\_has_builtin session)

Continued investigating `test_muon`'s remaining failures after the
`alloca()` argument-count crash fix above (which unblocked the crash
but didn't, by itself, flip `common/36 has function`'s overall
verdict — see below). muon's own `has function alloca` capability
probe runs several fallback checks; the last of them is the modern
`__has_builtin` idiom:

```c
#ifdef __has_builtin
  #if !__has_builtin(__builtin_alloca)
    #error "__builtin_alloca not found"
  #endif
#elif ! defined(alloca)
  __builtin_alloca;
#endif
```

- **`__has_builtin` (a clang/GCC preprocessor extension, now the
  idiomatic feature-detection guard used throughout modern
  portable/build-system code) was not implemented at all** — `#ifdef
__has_builtin` was unconditionally false, so every such probe fell
  through to the `#elif` branch, referencing the bare identifier
  `alloca`/`__builtin_alloca` as an ordinary (never declared)
  variable and failing to compile — even for builtins rcc genuinely
  implements. Implemented `__has_builtin(NAME)` following the exact
  pattern already used for `__has_include`/`__has_c_attribute`:
  registered as a predefined `#ifdef`-visible macro plus a special
  case in the `#if`-expression evaluator (`eval_primary_tok`) that
  looks `NAME` up in a sorted, mechanically-extracted table of every
  `__builtin_*` string rcc's parser/codegen dispatch on by exact name
  (190 entries: parser.c's `declspec()`/`unary()` builtin chain,
  codegen.c's `gen_funcall()` `bi_s_*` table, and preprocess.c's
  `__builtin_X` -> library-name macro aliases), checked via
  `bsearch()`.
- **Second, subtler bug found while verifying the first fix**: unlike
  real GCC/clang (where `__builtin_alloca` et al. are genuine
  front-end-recognized identifiers, never macros, so `__has_builtin`'s
  argument reaches it completely unexpanded), rcc implements several
  `__builtin_X` names as plain preprocessor object macros that alias
  straight to the underlying library function name purely as an
  internal codegen-dispatch convenience (e.g. `__builtin_alloca` ->
  `alloca`, `__builtin_memcpy` -> `memcpy`). `__has_builtin`'s
  argument correctly undergoes ordinary macro expansion (verified
  against real GCC: `#define FOO __builtin_expect` then
  `__has_builtin(FOO)` is true on both) — but that same, correct
  expansion silently turned `__has_builtin(__builtin_alloca)`'s
  argument into the bare `alloca` by the time the lookup table ran,
  missing the table (which only has `__builtin_`-prefixed keys)
  entirely and wrongly reporting `NO`. An initial attempt to fix this
  by protecting `__has_builtin`'s argument from expansion entirely
  (mirroring `defined(X)`'s protection) was verified WRONG and
  reverted: it silently regressed GCC torture's own
  `c23-has-c-attribute-2.c` (`#define foo deprecated` then
  `__has_c_attribute(foo)`, which GCC _does_ expect to expand `foo` to
  `deprecated` first). Fixed instead by having `has_builtin_val()`
  check the looked-up name against the table both as given AND
  re-prefixed with `__builtin_`, so both the unexpanded form (for
  builtins rcc doesn't alias, e.g. `__builtin_popcount`) and the
  post-alias-expansion bare form (`alloca`, `memcpy`, ...) resolve
  correctly, with zero risk to the pre-existing, correct
  `__has_c_attribute` expansion behavior.

New regression test: `test/test_has_builtin.c` — `#ifdef`/`defined()`
visibility, several genuinely-implemented builtins (including the
alloca-aliasing case) reporting true, an unregistered name reporting
false rather than erroring, the exact muon-derived `__has_builtin`
fallback probe shape compiling cleanly, and a runtime check that
`__builtin_alloca` genuinely works once `__has_builtin` confirms it.
Verified this test fails without the fix (A/B, `git stash`-style
revert) and passes with it. Also re-ran the four
`c23-has-c-attribute-*` and `c23-attr-syntax-8` GCC torture tests
directly to confirm no regression from the expansion-order fix. Full
suite verified: Unit tests 222/222, Compliance 15/15, C-testsuite
220/220, Torture 3605/3609 (100% of non-skipped), Dg-error 34/34,
Link tests 7/7 — 0 failed overall (native Linux x86-64); the new test
and the `c23-has-c-attribute-2` torture test both confirmed passing
standalone on the mingw and arm64 cross targets.

With this fix, `test_muon`'s own `common/36 has function` row flips
from failed to passing (338/387 muon self-tests now pass, up from
337/387 after the `alloca()` crash fix alone — that fix alone wasn't
sufficient to flip this specific test's verdict, since muon's
redeclared-signature probe still correctly fails to compile/link
either way, matching real GCC's own behavior for that same construct;
only the `__has_builtin` fallback probe's success actually flips
`has function alloca`'s overall YES/NO answer). The remaining 31
`test_muon` failures include the previously-confirmed-NOT-an-rcc-bug
`common/273 both libraries` cluster (8 of the 31) plus ~23 not
individually triaged this session — left for a future session.

### Fixed (2026-08-11, continued — -Werror unknown-flag session)

Continued investigating `test_muon`'s remaining failures.
`common/104 has arg` — muon's own "does the compiler support this
argument" capability probe — checks `-Werror -fiambroken` (a
deliberately nonexistent flag name, "I am broken") specifically to
confirm `-Werror` can promote an unsupported-argument probe into a
hard failure; expects it to fail to compile.

- **A bare `-Werror` combined with a genuinely unrecognized
  non-warning flag (e.g. `-fiambroken`) silently kept compiling
  instead of erroring** (main.c) — real GCC/Clang always hard-error
  on an unrecognized _non-warning_ flag (`-f.../-m...`, as opposed to
  `-W...`), unconditionally, even _without_ `-Werror` at all (verified
  directly: `gcc -c t.c -fiambroken` errors with no `-Werror`
  present). rcc's driver deliberately tolerates flags it doesn't
  implement (many third-party Makefiles pass compiler-specific flags
  unconditionally) by warning and continuing — but had no mechanism
  at all to ever promote that warning to an error, even when the
  caller explicitly opted in via `-Werror`. Fixed by hard-erroring on
  an unrecognized flag when bare `-Werror` is present, unless the
  flag looks like a warning name (`-W...`) — those keep the
  pre-existing, unchanged clang-style leniency (warn unless
  `-Werror=unknown-warning-option` is _also_ present), matching how
  meson/muon's own warning-flag-support probes expect a bare
  `-Werror` to NOT itself promote an unknown `-W` name to an error.
- **First fix attempt caused a real regression, caught by full
  `make check-all` before committing**: gating the new check on the
  existing `opt_Werror` boolean (shared with `-pedantic-errors`,
  which legitimately promotes pedantic _diagnostics_ to errors)
  broke 12 GCC torture tests and 3 dg-error tests whose own
  `dg-options` intentionally combine `-pedantic-errors` with real GCC
  flags rcc doesn't implement (`-fsigned-char`, `-ffreestanding`,
  `-fno-asm`, ...) and rely on those being tolerated. Root cause:
  "should compiler _diagnostics_ be promoted to errors" and "should
  an unrecognized _command-line flag_ be promoted to an error" are
  distinct questions that happened to share one boolean. Fixed by
  introducing a separate `opt_werror_bare` local, set only by the
  literal `-Werror` token (never by `-pedantic-errors`), and gating
  the new unknown-flag check on that instead — leaving every existing
  `-pedantic-errors` diagnostic-promotion behavior, and the whole
  corpus's tolerance for real-but-unimplemented GCC flags under it,
  untouched.

New regression test: `test/test_werror_unknown_opt.c` — five cases:
an unrecognized non-`W` flag without `-Werror` must still just warn
and succeed; the same flag with bare `-Werror` must now fail; an
unrecognized `-W` flag with bare `-Werror` must still only warn
(clang-style leniency preserved); the same `-W` flag with
`-Werror=unknown-warning-option` must fail (pre-existing, unchanged);
a genuinely supported flag (`-O2`) combined with `-Werror` must still
compile cleanly. Verified this test fails without the fix and passes
with it (A/B). The regression itself was caught and fixed within this
same session via full `make check-all` before any push. Full suite
verified: Unit tests 223/223, Compliance 15/15, C-testsuite 220/220,
Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Link tests
7/7 — 0 failed overall (native Linux x86-64); the new test and the
previously-regressed torture tests (`c23-constexpr-4`, `c11-stdint-1`,
etc.) both confirmed passing standalone on the mingw and arm64 cross
targets.

With this fix, `test_muon`'s `common/104 has arg` row flips from
failed to passing (339/387 muon self-tests now pass, up from
338/387). The remaining 30 failures still include the
previously-confirmed-NOT-an-rcc-bug `common/273 both libraries`
cluster (8 of the 30); a related linker-arg probe,
`common/180 has link arg`, was investigated but not fixed this
session — it needs rcc's own native ELF linker (or its `-Wl,`
sub-flag accumulator in main.c) to genuinely validate and reject
unrecognized linker sub-flags the way real `ld` does, which would
require enumerating and matching `ld`'s real accepted-flag surface; a
wrong rejection risks breaking some other currently-tolerated `-Wl,`
flag elsewhere in the corpus, so this is left as a separate,
larger, more carefully-scoped undertaking for a future session rather
than a quick win. ~22 of the remaining 30 failures are still not
individually triaged.

### Fixed (2026-08-11, continued — \_\_declspec native-target / K&R EOF crash session)

Continued investigating `test_muon`'s remaining failures.
`common/197 function attributes` — muon's own "does this GNU/MSVC
function attribute compile" capability probe — checks
`__declspec(dllexport) int foo(void) { return 0; }` specifically
expecting it to be rejected on `posix` (only genuinely supported on
`_WIN32`).

- **`__declspec(...)` (MSVC's declaration-attribute syntax) was
  unconditionally recognized and silently swallowed (a complete
  no-op — rcc implements no actual dllexport/dllimport semantics
  anywhere) on every target, including native Linux** (parser.c,
  `is_typename()`/`read_type_attrs()`) — but real GCC on native Linux
  doesn't recognize `__declspec` at all (verified directly: `gcc -c`
  reports a genuine syntax error); only MinGW-targeted GCC accepts it,
  as a real, long-standing extension for Windows DLL export/import
  entirely unrelated to MSVC compatibility mode. Checked the existing
  third-party corpus for any reliance on this cross-platform leniency
  first — every use found (bash's bundled gettext `lib/intl/export.h`
  etc.) is already guarded by `#if defined _MSC_VER`, a macro rcc
  never defines on any target, so `__declspec` is dead code under rcc
  regardless of the fix. Fixed by gating `__declspec` keyword
  recognition to `_WIN32` builds only (`#ifdef _WIN32` around both the
  `is_typename()` lookahead and `read_type_attrs()`'s consumption
  site), matching real GCC's own target-specific behavior exactly.
- **Second, more serious bug found while verifying the first fix**:
  once `__declspec` stopped being consumed as a recognized attribute
  on native Linux, `__declspec(dllimport) int foo(void);` (a bodyless
  prototype, no following K&R declaration-list ever reaching a `{`)
  crashed rcc internally (SIGSEGV) instead of producing a clean
  diagnostic. Root cause (parser.c, `parse_kr_param_list()`): the
  unrecognized `__declspec` identifier got misparsed as an
  implicit-int, K&R-style (old-style) function head named
  `__declspec` taking one old-style parameter `dllimport`; the K&R
  declaration-list loop (`while (!equalc(tok, "{"))`, meant to consume
  each old-style parameter's `type name;` declaration before the
  function body) had no EOF check at all, so once the real remaining
  tokens (`int foo(void);`) were consumed without ever producing a
  `{`, the loop kept calling `declspec()`/`declarator()` on the
  trailing EOF sentinel token forever — `declspec()` on EOF silently
  fails to consume it (EOF isn't a valid type-specifier token), so
  `tok` stayed pinned there, and `declarator()` unconditionally reads
  `tok->next` a few lines in, which is NULL for the lexer's genuine
  end-of-list EOF token, segfaulting several calls deeper inside
  `skip_attributes()`/`read_type_attrs()`. Reduced to a minimal repro
  independent of `__declspec` entirely (any unrecognized identifier
  followed by a K&R-shaped parameter list and a declaration that never
  reaches `{` triggers it) — a real, previously-unreachable parser
  robustness gap, not specific to this fix. Fixed by diagnosing a
  clean "expected '{' before end of input" error the moment the
  declaration-list loop reaches `TK_EOF`, instead of looping into it
  (mirroring the identical "diagnose, don't crash" pattern an earlier
  session already applied to `declarator()`'s own unclosed-`(`
  recursion nearby).

New regression tests: `test/test_declspec_native_reject.c`
(`__declspec(dllexport)` must be a clean compile error on native Linux
and must still compile cleanly on `_WIN32`; ordinary `__attribute__`
usage must stay unaffected on every target) and
`test/test_kr_param_list_eof.c` (the minimal `__declspec`-independent
K&R/EOF crash repro must be a clean diagnostic, not a crash — verified
via the `WIFEXITED`/`WEXITSTATUS` convention that distinguishes an
actual signal death from a clean nonzero exit, since a crashed
child's exit status surfaces through `system()`'s intermediate shell
as an ordinary 128+signal exit code, indistinguishable from a genuine
diagnostic exit without decoding it this way; a genuine, well-formed
K&R function definition must still compile, link, and run correctly).
Both verified failing without their respective fix and passing with
it (A/B). Full suite verified: Unit tests 225/225, Compliance 15/15,
C-testsuite 220/220, Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link tests 7/7 — 0 failed overall (native Linux x86-64); both
new tests confirmed passing standalone on the mingw and arm64 cross
targets (including `test_declspec_native_reject.c`'s `_WIN32` branch,
confirming `__declspec(dllexport)` still compiles cleanly under the
mingw cross-compiler).

With this fix, `test_muon`'s `common/197 function attributes` row
flips from failed to passing (340/387 muon self-tests now pass, up
from 339/387).

### Fixed (2026-08-11, continued — angle-bracket #include cwd-search session)

Continued investigating `test_muon`'s remaining failures.
`common/189 check header` — muon's own `check_header()` compiler
method probe — deliberately copies a same-named decoy file
(`ouagadougou.h`, a placeholder name chosen to be obviously
nonexistent) into the build directory next to the compiled test file,
then checks `#include <ouagadougou.h>` (angle brackets) still fails to
find it, confirming the compiler's "system" search never accidentally
includes the current working directory.

- **Angle-bracket `#include <name.h>` could silently resolve to an
  unrelated same-named file sitting in the compiler process's current
  working directory, even when no actual search directory (`-I` or
  the built-in system list) provided it** (preprocess.c,
  `resolve_include()`/`resolve_include_raw()`) — both functions ended
  with an unconditional `if (file_exists(spec)) return
canonical_path(spec);` fallback, applied regardless of angle vs
  quote form, after the real search-directory loop found nothing.
  Since `spec` is the bare include name and `file_exists()` opens it
  via a plain relative `fopen()`, this silently searched the
  compiler's cwd — which real GCC never does for angle brackets
  (verified directly: `#include <name.h>` from a file in a
  subdirectory, with `name.h` sitting only in the invoking process's
  cwd and no matching `-I`, is a clean "No such file or directory" on
  real gcc). Reproduced exactly: muon compiles each probe's `test.c`
  from a relative path (`.muon/test.c`) with the compiler's cwd set to
  the build directory one level up — precisely where `configure_file()`
  had copied the decoy. Fixed by only reaching that trailing
  cwd-relative fallback for quote includes (whose own C search rules
  already permit a directory-independent fallback once the
  including-file's own directory search misses).
- **Collateral consideration**: `#embed <file>` (C23) reuses the same
  `resolve_include()` for its own angle-bracket form, but real GCC
  gives `#embed` an entirely separate, dedicated search-path mechanism
  (`--embed-dir=`, with "no default directories for #embed" per GCC's
  own docs) that rcc doesn't implement at all; rcc's own
  `test/test_embed.c` (from an earlier session) already relies on
  `#embed <file>` resolving via this same cwd-relative fallback as a
  deliberate, pragmatic substitute. Applying the `#include` fix
  unconditionally would have regressed that existing, working
  behavior (caught by `make check-all` before committing — a real
  `test_embed` `EXEC FAIL`). Fixed by threading a new
  `allow_cwd_fallback` parameter through `resolve_include()`, set
  `false` at every `#include`/`#include_next`/`__has_include` call
  site and `true` only at `#embed`'s own, so `#embed`'s pre-existing
  (already-decided, unrelated to this session) leniency is preserved
  exactly while `#include`'s matches real GCC.

New regression test: `test/test_include_angle_no_cwd.c` — a decoy
header sitting next to the compiled source must NOT be found via
`#include <decoy.h>` (angle brackets), but the identical decoy MUST
still be found via `#include "decoy.h"` (quote form, unaffected by the
fix). Verified failing without the fix and passing with it (A/B).
Full suite verified: Unit tests 226/226, Compliance 15/15,
C-testsuite 220/220, Torture 3605/3609 (100% of non-skipped), Dg-error
34/34, Link tests 7/7 — 0 failed overall (native Linux x86-64); both
the new test and `test_embed.c` (confirming its own #embed
angle-bracket leniency stayed intact) verified passing standalone on
the mingw and arm64 cross targets.

With this fix, `test_muon`'s `common/189 check header` row flips from
failed to passing (341/387 muon self-tests now pass, up from
340/387).

### Fixed (2026-08-11, continued — #warning -Werror promotion session)

Continued investigating `test_muon`'s remaining failures.
`common/28 try compile` — muon's own `compiler.compiles(werror:
true/false)` capability probe — checks that a `#warning` directive
compiles cleanly with `werror: false` but fails to compile with
`werror: true`.

- **`#warning` never promoted to a compile error under `-Werror`**
  (preprocess.c, `do_directive()`'s `dn_warning` case) — unconditionally
  printed the warning message and continued, with no mechanism at all
  to promote it to an error even when the caller explicitly opted in
  via `-Werror`. Real GCC promotes `#warning` to a hard error under
  bare `-Werror` (`error: #warning ... [-Werror=cpp]`, verified
  directly) — but, also verified directly, NOT under `-pedantic-errors`
  alone (`-pedantic-errors` promotes pedantic _diagnostics_, not
  `-Wcpp` ones). Fixed by promoting `#warning` to a clean compile error
  (matching the existing `#error` handler's `exit(1)` convention) when
  the caller passed bare `-Werror`.
- Reused the `opt_werror_bare`-vs-`opt_Werror` distinction from the
  earlier "Fixed (2026-08-11, continued — -Werror unknown-flag
  session)" above rather than re-deriving it: promoted that
  previously-`main()`-local boolean to a proper global
  (`opt_werror_flag`, declared in rcc.h) so preprocess.c's directive
  handler can see it too, since the earlier session's local-only
  version was scoped for a single main.c-internal use case that no
  longer covers this one.

New regression test: `test/test_warning_werror_promote.c` — three
cases: `#warning` without `-Werror` must still compile cleanly;
`#warning` with bare `-Werror` must fail to compile; `#warning` with
`-pedantic-errors` (no bare `-Werror`) must still compile cleanly,
matching real GCC exactly in every case. Verified failing without the
fix and passing with it (A/B), and that the earlier
`test_werror_unknown_opt.c` test is unaffected by the
`opt_werror_bare` → `opt_werror_flag` promotion-to-global refactor.
Full suite verified: Unit tests 227/227, Compliance 15/15, C-testsuite
220/220, Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Link
tests 7/7 — 0 failed overall (native Linux x86-64); the new test
confirmed passing standalone on the mingw and arm64 cross targets.

With this fix, `test_muon`'s `common/28 try compile` row flips from
failed to passing (342/387 muon self-tests now pass, up from
341/387).

### Fixed (2026-08-12, continued — path_join() absolute-path session)

While A/B-verifying the angle-bracket cwd-search fix above, an
unrelated, pre-existing regression test (`test/test_pp_tokens_file_boundary.c`,
which embeds absolute header paths in quote-form `#include` directives)
started failing once the angle-bracket fix's tightened quote-include cwd
fallback stopped silently masking it.

- **`path_join(dir, file)` (preprocess.c) never checked whether `file`
  was already an absolute path** — it unconditionally concatenated
  `dir + separator + file`, so `#include "/abs/path/foo.h"` from a
  source file in a different directory produced the literal, garbage
  candidate path `"<source's own dir>/abs/path/foo.h"` instead of using
  the already-fully-qualified absolute path as-is. Every standard
  path-joining utility (POSIX/Python `os.path.join`, C++
  `std::filesystem::path::append`, ...) treats joining an absolute path
  onto any base as returning that absolute path unchanged; real GCC
  matches this too (verified directly: `#include "/abs/path"` resolves
  regardless of the including file's own directory). This bug had been
  present and previously invisible: quote-form `#include`'s old,
  overly-broad trailing `file_exists(spec)` cwd fallback (removed by
  the angle-bracket fix above) happened to independently re-resolve
  most absolute-path quote-includes whenever the compiler's cwd
  happened to be `/` or the path started from cwd — masking the join
  bug rather than exercising the intended directory-relative path.
  Fixed by adding `is_absolute_path()` (POSIX: leading `/`; Windows:
  also a drive letter + `:` or a leading `\`) and having `path_join()`
  return `file` unchanged whenever it's already absolute, before ever
  touching `dir`. This is the single, general fix point: every
  `#include`/`#include_next`/`__has_include`/`#embed` call site already
  routes through `path_join()`, so no call site needed updating.

New regression test: `test/test_include_abs_path_quote.c` — a header
and its including source file placed in two different, unrelated
subdirectories, `#include`d by the source's absolute path; must
compile cleanly. Verified failing without the fix (isolated: only
`path_join()`'s absolute-path check reverted, angle-bracket fix left
in place) and passing with it (A/B), and that the pre-existing
`test_pp_tokens_file_boundary.c` failure this session started from is
fixed by the same change. Full suite verified: Unit tests 228/228,
Compliance 15/15, C-testsuite 220/220, Torture 3605/3609 (100% of
non-skipped), Dg-error 34/34, Link tests 7/7 — 0 failed overall (native
Linux x86-64); both new/fixed tests confirmed passing standalone on
the mingw and arm64 cross targets.

### Fixed (2026-08-12, ksh93 arith / IEEE truthiness / 64-bit pointer-arithmetic session)

`test_ksh93`'s own `shtests` suite ran for the first time with a fully
built binary (the arith.sh `cos*cos + sin*sin > 1.01` failure that had
been "left for a future session" was reproduced: **every math function
hung**). Four stacked rcc bugs were found and fixed; ksh93's suite went
from the build blocking on `arith.sh` to only the single documented
long-double-precision rounding check below failing.

- **Long-double assignment-expression value returned 0** (codegen.c,
  `ND_ASSIGN`) — `(d = f * 10.)` is an assignment expression whose value
  is the stored (truncated) value; the TY_LDOUBLE x86 path stored the
  double bits but returned a **zeroed dummy register** instead of the
  assigned value, so `(long)(d = f*10.)` was always 0 and sfio's
  `_sfcvt` normalization loop `while ((long)(d = f*10.) == 0)` spun
  forever (the `atan(1.)`/`sin(1.)`/`cos(1.)` hangs). Fixed by returning
  the stored value register like the plain-double path.
  → found via ksh93's `arith.sh` (every `$(( atan(1.) ))` hung).
- **IEEE flonum truthiness used a bitwise GP test** (codegen.c, 7 sites)
  — `if(-0.0)`, `-0.0 && x`, `-0.0 ? a : b`, `while(-0.0)`, `do{}while
(computed -0.0)` and the `&&`/`||` value paths tested the double bit
  pattern with `cmp $0`, so `-0.0` (bits 0x8000000000000000) was
  **truthy**. Fixed with three helpers
  (`gen_flonum_branch_if_zero` / `gen_flonum_branch_if_nonzero` /
  `gen_flonum_truthiness`) emitting a real `ucomisd`-against-0.0
  (ARM64: `fcmp` against xzr) with the NaN-is-truthy unordered handling,
  applied at every condition site (gen_cond_branch_inv's generic
  fallthrough and ND_LOGOR lhs, ND_COND in gen/gen_addr/gen_int128,
  ND_LOGAND/ND_LOGOR value paths, ND_DO) plus a latent ARM64 `(bool)NaN`
  bug in gen_cast_reg. → found via ksh93's `arith.sh` `-0` failures
  (`$(( -1.0*0))` printed "0" instead of "-0"; `printf "%g" $((x))` for
  x=-0 lost the sign).
- **Pointer-offset multiply hardcoded `int`** (type.c `new_scale_mul`)
  — the scale multiply `offset * elemsize` inserted for pointer
  arithmetic was typed `ty_int` unconditionally, so `char *p + long n`
  with `n >= 2^31` became `p + (long)(n * 1)` with `n*1` computed in 32
  bits — every offset >= 2^31 was truncated (a 4 GB `printf -v` buffer
  came back as 32-bit-wrapped garbage). Fixed by typing the multiply as
  the actual usual-arithmetic-conversion result.
  → found via ksh93's `printf -v v "%4000000000d"` (sfio padding).
- **Pointer subtraction typed `int` instead of `ptrdiff_t`** (type.c,
  `ptr - ptr` `ND_SUB`) — `f->next - f->data` (sfio string-stream
  position arithmetic in `_sfexcept`'s buffer growth) was computed in 32
  bits, so once a stream's buffer passed 2 GB the growth wrote garbage
  `next`/`endb`/`data` pointers and the next byte-store segfaulted
  (ksh93 crashed at exactly 2^31 = 2147483648). Fixed by typing the
  result `ty_llong` (ptrdiff_t width).

New regression tests: `test/test_ld_assign_expr.c` (assignment
expression value + the sfcvt-style normalization guard),
`test/test_float_truthiness.c` (if/&&/||/ternary/while/do-while/\_Bool
cast with -0.0 and NaN, cross-checked against gcc),
`test/test_ptr_arith_64bit.c` (`p + n` with n >= 2^31, `p - q` >= 2^31,
scaled `long*` offset — proven to fail on the unfixed compiler with
`q-p = -294967296`). Full suite verified: Unit tests 228/228, Compliance
15/15, C-testsuite 220/220, Torture 3605/3609 (100% of non-skipped),
Dg-error 34/34, Link tests 7/7 — 0 failed overall (native Linux x86-64).
ksh93: clean rebuild with fixed rcc; `bin/shtests` runs the entire suite
(alias/append/.../variables/vartree\*, 1000s of tests) with the **only**
remaining failure `arith.sh[327]` — `typeset -lE20 val=123.01234567890`
expects 20-significant-digit extended-precision rounding
(`123.0123456789`) but rcc prints the double expansion
(`123.01234567890000449`), because rcc computes `long double` internally
in double precision by design (see the `TY_LDOUBLE` comments in
codegen.c/type.c); true 80-bit extended support is a multi-session
feature, not a quick win. The 5 GB `printf -v` (`basic.sh[983]`) now
passes too (takes ~2 min of byte-at-a-time padding; the 420 s harness
timeout at full-suite scale is a throughput, not correctness, issue).

### Fixed (2026-08-12, external-link fallback / muon toolchain detection session)

`test_muon` was at 342/387 with ~22 untriaged failures, all in
link-command generation: sub-projects built with `CC=rcc` got bare
link commands (`rcc -o prog ... liblib1.so`) instead of the full
`-Wl,--as-needed -Wl,--no-undefined -Wl,-rpath,<dir> -Wl,--start-group
... -Wl,--end-group -Wl,-soname,...` set, so shared-library chains
failed to link (transitive DT_NEEDED resolution) and shared libs lost
their DT_SONAME/DT_RUNPATH. Two independent causes, both fixed:

- **rcc silently dropped linker commands its internal linker can't
  honor** (src/main.c) — the native ELF/PE/Mach-O linker only
  understands `-l`/`-L`/`.a` (plus bare `.so` positionals and the
  `-pie/-pic/-shared/-static/-export-dynamic` mode flags); every
  `-Wl,` option (rpath, soname, `--start-group`/`--end-group`,
  `--as-needed`, `--no-undefined`, `-v`, `-z`, ...) was silently
  skipped, so the link "succeeded" with wrong semantics. rcc now scans
  the link command up front and, on any `-Wl,` option or
  `-nodefaultlibs`, skips the internal linker and goes straight to the
  external (gcc) link — the fallback that previously only ran after the
  internal linker _failed_. Also, a bare `-Wl,<opt>`/`-l<name>`
  invocation with no source/object inputs is now a legitimate link-only
  probe (matching real gcc) instead of dying at "no input files", so
  `rcc -Wl,-v` runs the real linker and prints its "GNU ld version"
  banner — the probe build tools like muon use to detect the linker
  type.
  → found via muon's toolchain detection: with `-Wl,-v` producing no
  output, muon could never identify rcc's linker as GNU ld and fell
  back to the minimal `ld-posix` (no handler args at all).
- **muon doesn't recognize rcc as a gcc-compatible compiler** — its
  `gcc` detect requires "Free Software Foundation" in `--version`
  output; rcc's version line lacks it, so rcc was detected as the
  generic `posix` compiler whose `linker:` is `ld-posix` (by name, no
  GNU handlers). The harness's `shared_muon()` patch (test/
  linux_thirdparty.bash) now registers `rcc` as a muon compiler
  toolchain (`inherit: 'posix', linker: 'ld', detect: 'rcc' in out`),
  exactly like the existing `slimcc` patch, so rcc resolves to GNU ld
  and muon emits the full linker argument set.

New regression test: `test/test_link_wl_fallback.c` — a bare `rcc
-Wl,-v` must print the linker version banner (not "no input files"),
and a `-Wl,-rpath,<dir>` link of a program against a shared library in
a non-standard directory must produce a binary that actually finds its
library at runtime (proven to fail with the internal linker silently
dropping `-Wl,-rpath`: no DT_RUNPATH → runtime "cannot find library").
Full suite verified: Unit tests 231/231, Compliance 15/15, C-testsuite
220/220, Torture 3605/3609 (100% of non-skipped), Dg-error 34/34, Link
tests 7/7 — 0 failed overall (native Linux x86-64). muon: full
`build/muon -C build test` now 384/387 — the 3 remaining failures
(`muon/wayland`, `common/183 partial dependency`, `common/251
add_project_dependencies`) reproduce identically with a fully gcc-built
muon and are environment/muon issues (wayland-scanner tool missing;
zlib link path), not rcc bugs.

### Fixed (2026-08-12, AVX/AVX2 intrinsics session)

Implemented AVX (256-bit) intrinsic support end to end — the VEX
encoding infrastructure and the full AVX/AVX2 `__builtin_ia32_*256`
surface — unblocking test_brotli's `-mavx2` compile (test_blake3's
AVX-512 remains, see below):

- **VEX encoders in `src/x86_enc.c`** — 2- and 3-byte VEX prefix
  emission (`vex2`/`vex3`/`vex_rr`, vvvv inverted, L=1) plus ~200
  `VEX256_*` 2-op/3-op/imm/mem/GP-dst encoders: float ALU
  (vaddps..vxorps, hadd/hsub/addsub, min/max, sqrt/rcp/rsqrt, cmpps),
  int ALU (vpaddb..vpsubq, pand/por/pxor/pandn, pcmpeq/pcmpgt,
  min/max, mul, pack, punpck, pavg, saturating add/sub, psadbw,
  pmaddwd, pmaddubsw, pmulhrsw), 0F38 (pabs/psign/phadd/phsub/pshufb/
  pmulld/pmuldq/pcmpgtq/packusdw/vpsllvd/vpsrlvd/vpsravd/pmovsx/pmovzx/
  vptest/vtest/maskmov), 0F3A imm (vblendps/pd, vpblendb/w/d,
  vblendv\* with the is4 mask nibble, vroundps/pd, vdpps, vmpsadbw,
  vpalignr, vperm2f128/i128, vpermq/vpermpd, vpermilps/pd imm,
  vextractf128/i128, vinsertf128/i128), shifts (imm 71/72/73 group +
  XMM-count var forms + pslldq/psrldq), broadcasts, maskload/store,
  movmsk, non-temporal stores.
- **32-byte vector ops** (`gen_vector32_x86`) — element-wise
  ADD/SUB/MUL/DIV/AND/OR/XOR/compare/neg for `vector_size(32)` types
  via the fixed-YMM path, with scalar operands broadcast through
  vpbroadcastb/w/d/q / vbroadcastss/sd. Integer MUL via vpmulld/vpmullw.
  Result slots are 32-byte (two int128 slots), and `alloc_int128_slot`
  now refuses to hand out struct-ret-buf slots that overlap the local
  var pool (32-byte params / the hidden `__retbuf` var extend past
  `fn->stack_size`, which used to clobber the saved struct-return
  pointer and segfault).
- **`__builtin_ia32_*256` classification** (`ia32_builtin_ret`) — the
  trailing `256`/`128` suffix selects the vector width; the float
  (ps/pd/ss/sd) suffix is checked on the base name _before_ the size
  suffix (mulps256), the int rule scans the base for the element
  letter, and root-based specials (palignr, permvarsi/permvarsf,
  vpermilps/vpermilpd, movddup/movshdup/movsldup, vextract/insert,
  vbroadcast, movmsk/pvtest int results) fill the gaps.
- **Encoding gotchas found by A/B against gcc -mavx2** (the print
  probe is byte-identical to gcc at -O0..-O3): gcc sets VEX.W=1 on
  ALL 256-bit 0F38/0F3A ops (the W0 encodings SIGILL on this CPU),
  vextracti128/vinserti128 are W1, vpermilps imm = 0F3A 04 (pd=05),
  vpshuflw = F2 / vpshufhw = F3 (they were swapped), vpermd/vpermps
  take (indices in vvvv, table in rm), vextract encodes dst in
  ModRM.rm, palignr's builtin imm counts BITS (header passes \_\_N\*8),
  cvttps2dq = F3 not F2, and the runtime-int shift counts on the "i"
  builtins fall back to the XMM-count form.
- **`__AVX__`/`__AVX2__`/`__FMA__`/AVX-512 macros** in
  `gcc_predefined.h` so the real headers' `#error` guards pass, and
  the header chain now compiles: `__bf16` is a 2-byte type (keyword,
  not the float fallback), `__mmask8` resolves, the bf16-mask convert
  builtin is typed as a 16-byte vector, and the declarator keeps
  function attributes that appear after a pointer star
  (`extern __inline void * __attribute__((__gnu_inline__)) fn()` —
  gcc's lwpintrin.h) so those wrappers stay eliminated instead of
  being codegen'd and erroring.
- New regression test `test/test_avx2_intrinsics.c` (x86-only,
  guarded like the ia32 intrinsics test) covering the families above,
  gcc-verified; PASS at -O0..-O3, PASS on the mingw and arm64
  cross-compile targets. Full suite: TCC 118/118, Unit tests
  234/234, Compliance 15/15, C-testsuite 220/220, Torture
  3605/3609 (0 failed, 4 todo).

### Fixed (2026-08-12, AVX-512 / EVEX intrinsics session)

test_blake3 now builds and passes (rc=0) end to end — the whole
`blake3_avx512.c`/`blake3_avx2.c`/`blake3_sse41.c`/`blake3_sse2.c`/
`blake3_dispatch.c` set compiles with rcc against the real glibc
`<immintrin.h>` chain:

- **EVEX infrastructure in `src/x86_enc.c`** — the 4-byte 0x62 prefix
  (`evex4`/`evex_rr`, R/X/B/R'/V' extension bits, L'L width field,
  aaa k-mask field; EVEX W is NOT inverted unlike VEX) plus encoders
  for the blake3 set: vpsrld/vpsrlq/vprord imm-group shifts (incl. the
  128/256-bit VL forms), vpunpckldq/hdq/lqdq/hqdq, vshufi32x4,
  vpmovqd (zmm to ymm and ymm to xmm), vpcmpud (result in a k register
  plus kmovw to GP), vmovdqu32 masked stores, vmovups zmm moves,
  vpbroadcastd/q, vpandnd/vpxord/vpaddd/vpsubd 3-op ALU, and
  vextractf64x4 (dst in ModRM.rm, W1).
- **`*512_mask` builtin dispatch** in codegen.c — the masked forms
  blake3's `_mm512_*` wrappers lower to; the (\_\_v16si)-1 mask blake3
  passes encodes as aaa=0 (unmasked), equivalent to gcc's real k-mask.
- **64-byte vectors**: int-element ops go through the parser's
  `vector_lower` element loop (correct, gcc-verified); float 64-byte
  vectors route to a new `gen_vector64_x86` (EVEX, fixed ZMM0-3,
  64-byte result slots).
- **Classifier**: the 512/512_mask size suffix, result-width
  exceptions (pmovqd512_mask narrows to 32 bytes, the ucmpd\* compares
  return a \_\_mmask16), and name-shape outliers (extractf64x4_mask,
  shuf_i32x4_mask, storedqusi256_mask, prord128/256_mask carry no
  "512" in the name).
- **Bundled header gaps** the real chain needs: `__m128[u|d|i]_u` /
  `__m256[u|d|i]_u` unaligned aliases in include/emmintrin.h and the
  32 `_CMP_*` compare predicates in include/xmmintrin.h.
- New regression test `test/test_avx512_intrinsics.c`: compiles the
  full 512 surface; the runtime checks are guarded by
  `__builtin_cpu_supports("avx512f")` so the test PASSes on machines
  without AVX-512 (the local dev CPU is a Zen+ with no AVX-512; the
  encodings were verified against gcc -mavx512f objdump output).
  PASS at -O0..-O3, PASS on the mingw and arm64 cross targets.

Full suite: TCC 118/118, Unit 235/235, Compliance 15/15, C-testsuite
220/220, Torture 3605/3609 (0 failed, 4 todo); test_blake3 PASS.

### Fixed (2026-08-12, SIMD intrinsics / real glibc headers session)

Investigated why rcc cannot include the real glibc `<immintrin.h>`
chain (the diagnosis that became this session's fixes). With rcc's
bundled headers renamed away, `#include <immintrin.h>` resolved to
`/usr/lib/gcc/.../include/` and failed on four independent gaps:

- **`__builtin_ia32_*` calls typed as implicit int** — every real
  header intrinsic is a thin `extern __inline __always_inline__`
  wrapper over one of these calls; rcc neither typed them (they fell
  through `declare_builtin_on_demand` to implicit int, so the headers'
  `(__m128)__builtin_ia32_addps(...)` casts became scalar-int→vector
  casts) nor codegen'd them (only sqrtps/sqrtss/rsqrtps existed).
  Fixed by (a) type.c's `ia32_builtin_ret()` name-suffix classifier
  (ps/pd/ss/sd → float vectors, trailing b/w/d/q + optional "128" →
  integer vectors, exact matches for converts/moves/compares/string
  ops), and (b) codegen.c's `gen_ia32_builtin()` dispatcher plus
  ~150 new x86 encoders covering SSE1 (xmmintrin.h), SSE2
  (emmintrin.h), SSE3 (pmmintrin.h), SSSE3 (tmmintrin.h), SSE4.1
  (smmintrin.h), AES-NI (wmmintrin.h) and the MMX 8-byte ops
  (mmintrin.h) — ALU, compares (cmp* imm predicates incl. operand
  swap for gt/ge and runtime-predicate dispatch), shuffles
  (shufps/pshufd/pshuflw/pshufhw/unpck*/pack*/palignr), loads/stores
  (movdqa/movdqu/movnt*/lddqu/movq/movd), converts (packed cvtps2dq
  family + scalar cvtsi2ss/cvtss2si), masks (movmskps/pmovmskb/ptest),
  shifts (imm + register-count forms), MMX vec_init/vec_ext, and the
  fence/cache/mxcsr/monitor/mwait controls.
- **GNU `extern __inline __gnu_inline__` emitted nothing** — GCC
  relies on mandatory inlining; rcc has no guaranteed inliner, so at
  -O0 the header wrappers never inlined and no symbol existed → every
  call was an undefined reference. Fixed two ways: (a) `try_inline()`
  now inlines `__attribute__((always_inline))` callees at every -O
  level (matching GCC) and accepts vector (is_vector) returns/params,
  and (b) when a call can't be inlined, the wrapper body is compiled
  as a per-TU LOCAL copy (`fn_emitting_local_copy`) instead of
  nothing — no global symbol is exported (GCC's actual promise) and
  multi-TU links never collide. Unreferenced copies are dropped by
  `eliminate_unused_static_inline` (now also treating gnu-extern-
  inline as omittable). EXCEPTION: a fortify-style wrapper whose body
  calls its OWN name (glibc's `... ? abort() : memcpy(...)` fallback)
  skips the local copy — a copy would shadow the fallback's libc
  binding and recurse until stack overflow; those call sites bind to
  libc exactly as before.
- **scalar→vector casts ICE'd** — `(__v8qi)0LL` (and the real
  headers' `(__m128i)0` idiom) hit "Invalid register -1": the cast was
  never materialized. `gen_vector_splat()` now broadcasts any scalar
  (constant or runtime, int or float/double) to every lane into a
  16-byte slot, matching GCC's semantics. Also fixed the companion
  8-byte (MMX) vector-assignment path (stored the slot address
  instead of copying the value) and vector→scalar bitcasts
  (`(long long)__m64`).
- **`__bf16` type missing** — avx512bf16vlintrin.h's `typedef __bf16
__v16bf ...` failed with "expected ';' or ','". `__bf16` and
  `_Float16` are now builtin typedefs (16-bit storage; rcc has no
  16-bit-float or AVX512-BF16 codegen, so the typedefs parse and any
  real bf16 arithmetic falls through integer paths).
- **bundled-header gaps** — rcc's own xmmintrin.h/emmintrin.h lacked
  the unaligned `__m128_u`/`__m128i_u`/`__m128d_u`
  (`__aligned__(1)`) typedefs that real programs use with
  `_mm_loadu_si128`'s pointer type; added them.

Also fixed while iterating: haddps/haddpd prefix (F2, not none),
addsubps matching the "add" prefix first (now checked before plain
add/sub), unary 0F38 ops (pabs*/pmovsx/zx*) loading a second
argument, roundps/roundpd (unary 0F3A) reading the imm from the wrong
argument, cvtpi2pd arity (1 arg), vec_init_v4hi storing ints instead
of the vector's element width, blendps-family width (non-128-suffixed
SSE4.1 names are 16 bytes, not MMX 8), cvtss2si/cvtsd2si prefix order
(F3/F2 must precede REX), and the 5 legacy sqrt\* builtins declared as
real functions in rcc's bundled headers (calls carried their target
in lhs, not funcname).

Verified: with the bundled headers renamed away, a program covering
SSE1/SSE2/SSE3/SSSE3/SSE4.1/MMX (`real_broad.c`: addps/addsubps/
haddps/pmaddwd/pshufb/pabsd/pmin/pmovsxbd/blendps/roundps/ptest/
extract/cvt/mmx-add) compiles against the REAL glibc headers and its
output is byte-identical to gcc -O2 -mssse3 -msse4.1 at both rcc -O0
and rcc -O2. blake3/brotli still need AVX2/AVX-512 (256/512-bit)
intrinsics + mask registers — unchanged, still item 1 below.

New regression test: `test/test_ia32_intrinsics.c` — direct
`__builtin_ia32_*` calls (packed+scalar float ALU incl. lane-0
semantics, integer ALU, compares with operand swap, shuffles,
converts, move masks, pshufb128, unary pabsd128, blendps/roundps/
ptestz128, MMX paddw via vec_init), the splat casts, and a GNU
extern-inline wrapper exercising the local-copy link path; passes at
-O0/-O1/-O2/-O3 and matches gcc byte-for-byte. Full suite verified:
Unit tests 233/233, TCC 118/118, Compliance 15/15, C-testsuite
220/220, Torture 3605/3609 (100% of non-skipped), Dg-error 34/34,
Link 7/7 — 0 failed (native Linux x86-64). The 8-byte vector and
vector→scalar codegen paths are additionally covered on arm64 by the
same test (guard skipped there).
