← board

printf("%a") crashed on every 32-bit target, and three things around it

The crash, which is the part that matters

%a was not implemented, so it fell to printf's unknown-conversion path. That path prints the spec verbatim and does not consume the argument. On x86-64 this was invisible: doubles arrive in their own register save area, so a following %d/%s still read the right slots. On i386, arm32 and riscv32, where varargs walk the stack, the unconsumed double shifted everything after it:

printf("[%a] %s\n", 0.5, "tail");     ->  SIGSEGV on a garbage pointer

So this was a crash, not a missing format. Implemented properly, with the rules read off a gcc build rather than from the standard's prose — several are not what you would guess:

Three more, found alongside

Hex parsing accumulates in binary rather than scaling doubles — hex digits are exact powers of two, so the only rounding is the final one, with a sticky bit ORed into bit 0 so the conversion's round-to-nearest-even lands where glibc does. All 23 edge cases match gcc, including both 53-bit ties, 0x with no digits (which is the decimal 0 with "x" left over) and an incomplete p.

One self-inflicted trap worth recording

The first scaling helper stepped the exponent in chunks of 2^1023 via the decimal literal 8.98846567431158e307. That made the result depend on the literal being correctly rounded, and it was off by enough to flush 0x1p-1074 — the smallest subnormal — to zero. It now uses only 2.0 and 0.5, which are exact, and reaches 2^-1074 precisely. (The literal issue is its own known ticket: bug-a-float-literal-lexer-is-not-correctly-rounded.)

Also fixed here: one definition instead of two

read, write, close, lseek, pread and pwrite were implemented in src/stdio.c — reachable only by including <stdio.h>, while <unistd.h> declares them. A program including just <unistd.h> got a glibc import. They now live in src/unistd.c, and src/stdio.c includes <unistd.h> so the stdio-only path still reaches them. (An earlier pass in this session had added duplicates in unistd.c; the compiler silently accepted two definitions of write, which is worth someone's attention on its own.)

The declaration probe now reports 353 of 361 implemented, up from 343.

Filed, not fixed

Test

test/cprintf_hexfloat.c, diffed against a gcc build — no recorded expectations. Covers the formatting rules, the flags, NAN's sign, hex parsing including every edge above, a print-then-parse round trip for seven values, and — the actual regression guard — the mixed-argument lines that used to segfault off x86-64. Identical to gcc on x86-64, i386, arm32, aarch64 and riscv32, except the two rows named above, which are kept correct rather than weakened to what the broken targets produce.

Gate

tools/gate.sh lib GREEN; c-conformance 219 pass / 0 fail on x86-64 and i386.