← board

C differential fuzzing (csmith vs gcc) — campaign, PAUSED with the harness live

Resume in one line

make fuzz-csmith FUZZ_ITERS=200          # or: tools/csmith_fuzz.py --iters 200

Prereqs are already satisfied on this box: csmith (apt) and the runtime headers, which tools/install_lib_candidates.sh csmith vendors WITHOUT root (apt-get download + dpkg-deb -x into library_candidates/csmith/include, gitignored).

Why it works — the oracle needs no judgement

csmith generates C that is free of undefined behaviour BY CONSTRUCTION, and every program ends by printing a checksum of all its globals. So: build the same program with gcc and with pxx, run both, compare the checksum. A difference is a real miscompile in one of the two — and it is not gcc. The harness also builds at several pxx -O levels and compares them against each other, which catches our own optimiser without needing gcc at all.

Findings are bucketed (MISCOMPILE_VS_GCC, MISCOMPILE_OPT, PXX_CRASH, PXX_COMPILE_FAIL, PXX_TIMEOUT) and DEDUPLICATED — csmith throws thousands of programs at the same few gaps, and 500 copies of one bug is not 500 bugs. Each distinct hit is saved with its seed and a REPRO.md.

Scoreboard when paused

Nine bugs found and fixed in the first sitting (2026-07-13). Every one was SILENT, and not one was reachable by the real-world corpora we already run (lua, sqlite, tcc, zlib, c-testsuite) — those are written by humans who avoid dark corners:

# bug why the corpora missed it
b306 signed bitfields never sign-extended (signed f:7 = -5 read as 123) corpora use UNSIGNED bitfields
signed bitfield FILLING its unit (signed f:8 -7 → 249) the storage load is always unsigned
enum bitfields must stay UNSIGNED while plain int x:8 is SIGNED both map to tyInt32
b307 struct-valued comma passed by value → SEGV; also on the RHS of = a comma is not an lvalue
b308 a discarded expression statement did not RUN (f() ^ 3; never called f()) values were right, only side effects vanished
b309 multidim array of POINTERS ignored its brace initializer (local AND global) 1-D and multidim-int both worked
b310 anonymous bit-fields (unsigned : 0;) made the whole aggregate OPAQUE — sizeof 0 rejected outright, silently
b311 multidim LOCAL array of STRUCTS initialised only its first element nDims hard-coded to 1
b312 global pointer to a multidim array element lost its initializer → null only one [...] was consumed
C99 hex float (0x1.0p-28f) + leading-dot (.5f) literals blocked csmith from running at all

After these, MISCOMPILE_VS_GCC is at zero across a 40-program sweep; the residual failures are crashes (still one dominant class — see below).

What is still open

Reduction recipe (no creduce on this box — these work without it)

  1. Name the guilty variable in seconds. csmith programs take an argv flag that prints a checksum after EVERY global:
    ./t_gcc 1 > g.txt ; ./t_pxx 1 > p.txt ; diff g.txt p.txt | head
    
    The first divergent line names the variable. This is how the bitfield bug went from 2474 lines to a 6-line repro.
  2. For a crash, find the last function entered: inject printf("TR func_N\n"); fflush(stdout); at the top of each function. Match the brace on the line AFTER the signature — csmith writes { /* block id: 0 */, not a bare {. Then bisect inside that function the same way.
  3. Shrink the search space, not the program:
    tools/csmith_fuzz.py --iters 30 "--csmith-args=--max-funcs 2 --max-block-depth 2 --max-expr-complexity 2"
    
    gives ~300-line crashers instead of 1700. NOTE the = — argparse eats a bare --csmith-args --no-x.
  4. sudo apt install creduce would make all of this much faster and is worth it.

Traps (paid for in wasted time)

RESUMED 2026-07-18 — ~300 iters (seed 5000+), 2 finding buckets (both pre-existing)

Ran tools/csmith_fuzz.py --iters 300 --seed-start 5000. ~95% agreed with the gcc oracle (rest skipped = gcc-side build/run fails). Two deduped finding buckets, BOTH confirmed pre-existing (the pinned stable compiler reproduces them — not from the 2026-07-18 C multi-dim / float work):

Blocker for fixing: both need reduction from ~2.5k-line generators; creduce/ cvise are not installed here (apt/pip need root/PEP-668) and a homemade line-delta reducer floors ~800 lines (csmith's nested exprs need a C-aware reducer). Install creduce to reduce + fix. Reproducers (this box's csmith) preserved in the session scratchpad; seeds reproduce exactly via tools/csmith_fuzz.py --seed N.

2026-07-18 — TWO miscompiles found AND FIXED via small-program fuzzing

Small-program mode (--csmith-args "--max-funcs 1 --max-block-size 3 --max-block-depth 2 --max-expr-complexity 4 --max-array-dim 2 --max-array-len-per-dim 3 --max-pointer-depth 2") makes findings born ~130-160 lines → a homemade line-delta reducer (interestingness: gcc runs & pxx runs & checksums differ) got them to 20-40 lines, directly diagnosable WITHOUT creduce.

Both were pre-existing (pinned reproduced). After both fixes, all four reduced repros match gcc. The remaining PXX_COMPILE_FAIL (seed 5004, kind-5 AN_BINOP) is still open ([[bug-a-csmith-o0-miscompile-seed5038]] history) — a lowering gap, lower severity (clean error).

Post-fix verification (2026-07-18)

After both miscompile fixes: 650+ fresh iters clean — small-mode seeds 9000-9250 & 12000-12250 (243/250 agree) and higher-complexity (--max-funcs 2 --max-expr-complexity 6) seeds 20000-20200 (188/200 agree), 0 findings, and the harness's pxx -O-level cross-check reported no MISCOMPILE_OPT (validates the -O3 float xmm-fusion against csmith too). The reducible-complexity miscompile space is clean. Deeper hunting needs full-complexity csmith → ~2.5k-line repros that require creduce/cvise (not installed; root/PEP-668) to reduce. Only the open PXX_COMPILE_FAIL (seed 5004, kind-5 AN_BINOP) remains — didn't recur in 650+ small/mid iters, so it needs a specific full-complexity shape.

2026-07-18 — 3rd miscompile (seed 31039), creduce wall reached

A further small/mid-complexity batch (--max-funcs 2 --max-array-dim 3 --max-pointer-depth 3, seeds 31000+) found another pre-existing -O0 miscompile (g_22 checksum), filed [[bug-a-csmith-o0-miscompile-seed31039]]. Unlike 5038/8020, it does NOT reduce below ~90 lines with the homemade line-reducer (nested functions + pointer chains + safe_math), so it is CREDUCE-GATED like 5004. Summary: 2 miscompiles reduced-and-FIXED (signed/unsigned, struct-array-ptr stride); 2 open findings (31039 miscompile, 5004 compile-fail) both need creduce/cvise to reduce to a diagnosable core. NEXT: install creduce (root) to unblock the remaining findings, or a C-aware reducer in Track T tooling.

2026-07-18 — seed 31039 FIXED (4de51285), not creduce-gated after all

Localized WITHOUT creduce by instrumenting the diverging global g_22 with a printf at its mutation site (both compilers, p_3 identical) → pinpointed (int8)g_15 >= (uint16)g_74 compared unsigned instead of signed (C integer promotion to int). Fixed 4de51285 (sub-int compare promotion). Now 3 miscompiles found+fixed (574fcac1 signed/unsigned-64, 4f4aceb3 struct-array-ptr, 4de51285 sub-int-promote); only the PXX_COMPILE_FAIL (seed 5004, kind-5 AN_BINOP) remains open.