C corpus step 2: zlib v1.3.1 bring-up
- Type: feature (C frontend validation) — Track A/C. Sub-step of [[feature-c-corpus-expansion]].
- Status: in progress 2026-07-06 — vendored + runner landed, oracle green, 2 compiler blockers filed. Not yet passing.
- Workload class: bit-twiddling, huffman tables, CRC loops, unsigned saturation, fn-ptr dispatch — different muscle than lua/sqlite.
Done
- Vendored via
tools/install_lib_candidates.sh zlib(madler/zlib commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf = v1.3.1; gitignored, PROVENANCE.md). test/zlib/runner.c— unity build: crtl units + the 15 zlib TUs + zlib's owntest/example.c(self-checks compress/inflate/gzio round-trips, exit(1) on mismatch).- Oracle GREEN: gcc build of the same zlib sources + example.c runs clean, exit 0, 7 round-trip lines ("uncompress(): hello, hello!" … "inflate with dictionary: hello, hello!"). This is the byte-compare target.
make test-zlibtarget (skips if tree absent; NOT inmake test— 3rd-party).- Each zlib TU group compiles alone under pxx; the failures are specific interactions/constructs below.
Blockers
bug-c-typedef-name-as-uninitialized-local— FIXED 2026-07-06 (commit 0e2740ee): shadowed-typedef-name-as-local now parses;trees.cclears.- [[bug-c-gzgetc-fnlike-macro-call]] (open blocker) —
crtl + zlib.h + example.cfails:Expected: ), but got: (Kind: 74) near: gzgetc >>> file. example.c calls thegzgetc(file)macro that zlib.h defines to inline the fast path (expands to a comma/ternary expr referencing gzFile internals). Not yet reduced to a minimal repro — do that first in the fix session (candidates: function-like macro whose body is a parenthesized comma-expression; or the(gzgetc)(file)parenthesized-function-name call in the fallback arm). File as its own bug-c-* ticket once isolated.
Gate
Both blockers fixed → make test-zlib: pxx runner output byte-identical to the
gcc oracle, exit 0. Then graduate to a fuller vector set (minigzip round-trip)
and cross targets. Bonus: cross-check against the Pascal zlib in lib-test.
2026-07-07 progress — parse cleared, now a link-stage symbol gap
gzgetc function-like-macro parse blocker RESOLVED (bug-c-gzgetc-fnlike-macro-call:
(name)(args) paren-function-name call). zlib example.c now parses, compiles and
LINKS the runner. make test-zlib next blocker: runtime
undefined symbol: gz_error — a zlib-internal function not being compiled/linked
into the runner (a zlib .c TU missing from the build set, or an internal symbol
not emitted). Next: find which zlib TU defines gz_error (gzlib.c) and why the
runner build omits it; then re-diff vs the gcc oracle (8 lines expected).
2026-07-07 — 4/8 output lines now match (was parse-blocked)
Fixed a cascade of general string-literal-decay + linkage bugs, each advancing the runner (all landed on master with regressions b171-b174, self-host byte-identical, c-conformance 195/0):
(gzgetc)(g)paren-function-name call (bug-c-gzgetc-fnlike-macro-call)- gz_error dynamic-import (guardless gzguts.h re-externalized a defined fn)
return "literal"not char*-decayed (zlibVersion check)"literal"[i]0-based single-byte indexing (uncompress/inflateInit version) Runner now passes: version line, uncompress(), gzread(), gzgets(). NEXT BLOCKER:inflate error: -2(Z_STREAM_ERROR) at inflate()/large_inflate/ inflateSync/inflate-with-dictionary. Z_STREAM_ERROR from inflate = bad state/ stream params — deeper (inflate's windowBits/state machine, likely a struct layout or function-table issue, not another string decay). Isolate: minimal inflateInit2+inflate of the fixed 'hello' stream vs gcc; instrument inflate()'s early Z_STREAM_ERROR guards (strm/state NULL, window size).
inflate -2 sharpened (2026-07-07)
Isolated with a minimal deflate+inflate harness: inflateInit=0 (state non-null), then inflate returns Z_STREAM_ERROR (-2) MID-STREAM at total_in=7 under the byte-at-a-time buffers (avail_in=avail_out=1). uncompress() works because it inflates in ONE call and never suspends; test_inflate forces suspend/resume every byte. So the bug is in inflate's multi-call SUSPEND/RESUME path — the LOAD/RESTORE of state->{hold,bits,next,put,have,left} across calls and the NEEDBITS/PULLBYTE goto-driven resume. Likely a pxx codegen issue in inflate.c's large single function (local caching of state fields, or a goto/label save), not a frontend string bug. Next: instrument inflate()'s mode switch to log where mode goes invalid on the 2nd/3rd call; compare state->hold/bits save-restore vs gcc.
2026-07-07 — 6/8 lines; inflate WORKS (COPY macro leak fixed)
The inflate -2 was NOT a codegen bug: zutil.c includes gzguts.h, whose private
#define COPY 1 is never #undef'd, and in pxx's single-TU unity build it
macro-replaced inflate.h's COPY enum constant (16195) with 1 → inflate()
corrupted state->mode after a STORED block and returned Z_STREAM_ERROR under
byte-at-a-time buffers. Real zlib compiles each .c separately so never sees it.
Fixed in test/zlib/runner.c: #undef COPY before inflate.c (gzguts.h is
guardless so gz*.c re-define it). Diagnosis method: bisected mode via printf at
inf_leave → mode went 16193(STORED)→1 at state->mode = COPY; PROBE showed
#ifdef COPY true = a leaked macro; only COPY collides (gzguts vs inflate enum).
GENERAL LESSON: pxx's no-linker unity C build leaks every private macro across
all files; a porter must #undef colliding names (or pxx would need per-file macro
scoping — big preproc change). Now passes version/uncompress/gzread/gzgets/
inflate/large_inflate.
NEXT BLOCKER: inflateSync error: -3 (Z_DATA_ERROR). inflateSync returns -3 when
the 00 00 FF FF flush marker isn't found (state->have != 4). syncsearch is
correct; the marker comes from test_flush's deflate(Z_FULL_FLUSH) — so suspect
the DEFLATE Z_FULL_FLUSH output (deflate.c/trees.c flush path), not inflate.
Also re-check for further gzguts/other private-macro collisions in deflate.c
(none found for inflate enum, but deflate has its own constants). Then
inflate-with-dictionary (blocked behind sync).
inflateSync -3 isolated to deflate Z_FULL_FLUSH (2026-07-07)
Direct probe: our deflate(strm, Z_FULL_FLUSH) on "hello" emits 17 bytes with NO
00 00 FF FF sync marker, so inflateSync's syncsearch never finds have==4 →
Z_DATA_ERROR. syncsearch and inflateSync are correct; the bug is DEFLATE's
full-flush path not emitting the empty stored block / sync marker (deflate.c
deflate() flush switch + _tr_stored_block/_tr_align in trees.c). Not inflate, not
a COPY-style macro leak (checked: only COPY collided with the inflate enum).
Focused deflate-internals session: verify the flush-level switch recognizes
Z_FULL_FLUSH (=3; note Z_RLE strategy is also 3 — different namespace, shouldn't
matter) and that _tr_stored_block writes the 4-byte marker. inflate-with-
dictionary is blocked behind this. zlib now 6/8 lines (was parse-blocked).
Pinpointed to trees.c bit-buffer (2026-07-07)
Instrumented deflate: it DOES reach the FULL_FLUSH marker branch (bstate=block_done,
flush=3) and calls _tr_stored_block(s,0,0,0) which writes 5 bytes to pending —
but the bytes are wrong. deflate("hello",Z_FULL_FLUSH) emits:
78 9c 00 05 00 fa ff 05 00 00 00 00 00 00 00 00 00 (17 bytes). Expected a clean
stored "hello" block + a 00 00 00 ff ff empty-block marker; instead the data/
marker region is corrupt (no 00 00 ff ff anywhere). So the bug is in trees.c's
bit-buffer primitives used by _tr_stored_block: send_bits (3-bit block header),
bi_windup (byte align + flush bit buffer), put_short (LEN/NLEN). Suspect a
codegen issue in the bit-accumulator (s->bi_buf/s->bi_valid ush arithmetic) or a
macro (send_bits/put_short are macros). Focused trees.c session; verify each
primitive with a standalone bit-write test vs gcc. NOTE: Huffman blocks WORK
(uncompress/inflate pass), so the fault is specifically the STORED-block path —
_tr_stored_block's data copy AND the empty-block marker (deflate("hello") chose a
stored block: output shows the 00 05 00 fa ff header but the 'hello' data bytes
are missing/zeroed, then no clean 00 00 ff ff marker). Start at _tr_stored_block
- deflate_stored, not the Huffman coders.
RESOLVED 2026-07-07 (Track A+C, sole-A) — zlib byte-identical to gcc
make test-zlib PASSES: the pxx-built libc-free runner (all of zlib compiled as
one TU) produces output byte-identical to the gcc oracle for every example.c test
(version, uncompress, gzread, gzgets, inflate, large_inflate, inflateSync, inflate
-with-dictionary). Reached by a cascade of general cfront fixes this session (each
with a regression, all self-host byte-identical, c-conformance 194→198):
- (gzgetc)(g) paren-function-name call (b171)
- gz_error guardless-header re-externalize (b172)
- return "literal" char* decay (b173)
- "literal"[i] 0-based byte index (b174)
- runner #undef COPY (gzguts.h macro leak in the unity build) — test fix
- FINAL: sizeof(*s->field) = pointee size not 8 (b177) — CLEAR_HASH over-zeroed pending_buf and wiped the deflate flush marker → inflateSync -3. Diagnosis chain: test-zlib diff → each line one bug → minimal repro → isolate to a compiler primitive. zlib is the first real-world multi-file C project compiling + running byte-identical under pxx. Next corpus target: tcc.