C differential fuzzing (csmith vs gcc) — campaign, PAUSED with the harness live
- Type: feature / ongoing campaign — Track C (with Track A fixes as they fall out)
- Status: done Resume by running the one command below; nothing needs rebuilding or rediscovering.
- Origin: step 4 of [[feature-c-corpus-expansion]] ("csmith differential fuzzing").
Read this before you touch the ownership — the shape of this ticket is unusual
It is a standing campaign log, not an open task. status: done, parked in
backlog/, resumed by the one command below across six sittings now. Do not
"fix" it into working/ and do not close it: working/ is a live lock and this
holds no lock between sittings, and there is nothing to close because the
campaign has no end state — it has a ratio.
It is a Track C ticket whose remaining moves fall on both sides of the
file-lane boundary, and that is not a mis-filing. Running the harness,
triaging what it finds, and fixing cfront is C's; each bug it finds is filed
into whichever lane owns it (that is the ordinary rule, not a special case).
But axis 2 below needs a --target pass-through added to
tools/csmith_fuzz.py, which is Track T's file lane. A campaign ticket like
this does not want SPLITTING — it wants each move routed as it comes up, when
it comes up. Report which side a move falls on and let the coordinator route
it; do not re-litigate the ownership of the whole ticket from scratch. (Axis 2
was routed to T on 2026-08-19, bundled with
[[bug-t-csmith-harness-reports-slow-as-a-timeout]] so T visits the file once
rather than twice.)
Standing note — how to read a batch's numbers
Hit count is not severity. Dedup count is closer. Neither substitutes for reading the bucket. Batch B below scored 56 hits and they were ONE missing table-row family; batch A scored 1 and it was the more serious finding. A report line is a prompt to open the bucket, never a verdict on its own.
And a null batch is a result, not a non-event — report a dry run as a dry run, with its seed count and its flag set, so the next sitting does not spend those seeds again.
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
- A residual crash class.
/tmpfindings are gone by now, but it reproduces in minutes: run the harness and reduce (recipe below). Last known unreduced crashers were csmith seeds 901 and 1502 (generated with the DEFAULT csmith flags). Both of those seeds PASS at HEAD as of 2026-08-29 (frankC,--seed 901and--seed 1502, each1/1 agreed with the gcc oracle, no findings), so this bullet's named repros are stale — something between 2026-07-13 and now fixed them and nobody re-checked. Whether the crash CLASS is gone or just these two representatives is not established: a fresh crasher has to be found before the bullet can be closed, which is what a batch is for. - Bitfield LAYOUT —
sizeofof a packed bitfield struct is 12 where gcc gives 8. Values are right, so the checksum oracle CANNOT see it; it breaks ABI/interop instead.bug-c-bitfield-packing-sizeof-vs-gccwas never filed (de-linked 2026-08-30). Do not mistakebugfix-cfront-bitfield-packing-gcc-compatfor it: that one is inrejected/, and it was rejected as a false diagnosis of the sqliteVdbeCursorcrash, having measured PXX and gcc as matching on that struct — a different subject, closed for a reason that says nothing about this. This bullet is the whole record of a finding the oracle structurally cannot rediscover, and the link is what made it look filed. - Brace elision over rows ([[bug-c-multidim-brace-elision-flattens-rows]]) —
int q[2][3] = {{1},{2}}gives q[0][1]=2 instead of 0. Pre-existing. --opts 0,2,3— the harness only ran-O0,-O2in anger. Adding-O3would point the same oracle at Track O's newer passes for free.- Cross targets. Everything so far is x86-64. The same programs under qemu would
exercise aarch64/arm32/i386/riscv32 codegen against the same oracle. High value, cheap:
the harness only needs a
--targetpass-through andtools/run_target.sh.
Two "still open" bullets are DONE, and one prereq note is stale — frankC, 2026-08-30
Checked before running, because the list above is what a resuming sitting plans from and three of my dispatches this week turned out already-fixed.
1. --target is IMPLEMENTED. The bullet says "the harness only needs a
--target pass-through and tools/run_target.sh". Track T built it in
764e98048 (csmith --target, and never report a comparison it did not make),
and then went further than the bullet asked:
174186b5dkeys the oracle on the data model, not the ISA, with a layout classifier — because a csmith checksum depends on whetherlongis 64-bit, so a native x86-64 gcc against an ILP32 target disagrees for reasons that are not miscompiles. The harness now looks for a cross gcc whose data model matches, falls back to a native one, and if it finds neither it drops the vs-gcc comparison and says so rather than filingMISCOMPILE_VS_GCCon alongwidth.21117f415marks riscv64 as a target with no backend.- The pxx-vs-pxx
-Ocomparison is unaffected by any of that — it compares one target against itself, and is the finding we own outright.
bug-t-csmith-harness-reports-slow-as-a-timeout, which this axis was bundled
with, is in done/. So the cross-target axis is not blocked on T; it is ready
to run. Nobody updated the bullet.
Why it sat idle — and this is a finding about the BOARD, not the fuzzer
The ticket calls this axis "High value, cheap" in its own words. It then sat
untouched for months. Not because anyone judged it low priority, but because
everyone believed it needed a --target pass-through that Track T had already
built.
That is worse than a stale park condition, and the difference is worth naming: a
stale blocked-by: edge or a prose park is an artefact — it sits in a file,
check can grep it, and someone eventually re-reads it. A belief held by
everyone has no owner and leaves no artefact to go stale. Nothing points at it,
so nothing re-checks it, and each new reader inherits it from the last summary
rather than from the code. The bullet describing the axis as needing work that
was already done is the closest thing to a record, and it reads as a plan rather
than as a claim someone should verify.
Recorded here so the next reader does not conclude the axis was simply deprioritised. It was ready, and cheap, and idle, for the whole time it was described as blocked.
2. The --opts 0,2,3 bullet is actionable right now and I am running it this
sitting. Nothing was needed: --opts has always been a flag.
3. The prereq note is stale in a harmless direction. It says
tools/install_lib_candidates.sh csmith vendors the headers into
library_candidates/csmith/include. That directory does not exist on this box
and nothing needed it: find_csmith_include() checks the vendored path, then
/usr/include/csmith, then /usr/local/include/csmith, and the apt install
supplies /usr/include/csmith/csmith.h. The generator is at /usr/bin/csmith.
Recorded because "prereqs are already satisfied on this box" plus a named install command reads as run this first, and running it would have been a no-op download for a resuming sitting that had no other reason to doubt the line.
Reduction recipe (no creduce on this box — these work without it)
- Name the guilty variable in seconds. csmith programs take an argv flag that prints a
checksum after EVERY global:
The first divergent line names the variable. This is how the bitfield bug went from 2474 lines to a 6-line repro../t_gcc 1 > g.txt ; ./t_pxx 1 > p.txt ; diff g.txt p.txt | head
- 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. - Shrink the search space, not the program:
gives ~300-line crashers instead of 1700. NOTE thetools/csmith_fuzz.py --iters 30 "--csmith-args=--max-funcs 2 --max-block-depth 2 --max-expr-complexity 2"
=— argparse eats a bare--csmith-args --no-x. sudo apt install creducewould make all of this much faster and is worth it.
Traps (paid for in wasted time)
- Call ORDER differing from gcc is NOT a bug. C leaves argument evaluation order unspecified; csmith only guarantees the OUTPUT is order-independent. Do not chase it.
- Replaying a seed without the same
--csmith-argsgenerates a DIFFERENT program. Use the savedt.cin the findings directory, not just the seed. - A
printfyou inject can move or hide the crash — that means memory corruption, and it is a signal, not an annoyance.
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):
- MISCOMPILE_VS_GCC (seeds 5038, 5194, …) — RECURRING, SERIOUS. pxx prints a wrong global checksum at -O0 (all pxx -O levels agree, differ from gcc). Filed [[bug-a-csmith-o0-miscompile-seed5038]] (prio 55). Multiple seeds hit the same bucket → a common codegen/lowering bug.
- PXX_COMPILE_FAIL (seed 5004) — kind-5 AN_BINOP.
IR_UNSUPPORTED: could not lower AST node (kind 5). Pre-existing: the pinned compiler fails it EARLIER at "wrong number of array subscripts" (the partial-multi-dim-index bug fixed 2026-07-18 in de649c39) — so recent work fixed the first gap and exposed this deeper AN_BINOP-lowering one. Same kind-5 family seen in [[bug-c-ptr-to-array-parameter]] history.
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.
- seed 5038/5194/8020 → signed/unsigned 64-bit comparison (
int64 > 0ULcompared signed). FIXED 574fcac1. [[project_c_signed_unsigned_compare64]]. - seed 9048 → global pointer to struct-array element wrong stride
(
static T *p = &g[1]used TypeSize(tyRecord)=8 not RecSize;*p=..corrupted the wrong slot). FIXED 4f4aceb3.
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.
RESUMED 2026-08-13 — one new bug, found and fixed
First run since the 2026-07-13 pause. 60 seeds warm-up: nothing. 250 seeds:
one MISCOMPILE_VS_GCC (seed 79), reduced and fixed as
[[bug-c-csmith-seed-79-miscompile-vs-gcc]] — an integer literal's l/L suffix
was widening the rung the unsuffixed ladder picked instead of RE-RUNNING the
ladder, so 0x9745DC78L was an unsigned long and 0x9745DC78L > <negative int32> silently answered 0.
Confirmation run after the fix: 80 seeds, seed 79 among them, all ok, no findings.
Scoreboard: 390 seeds this session, 1 finding, 1 fixed. That ratio is the point of the campaign — the previous nine came in one sitting because the low-hanging gaps were still there; the tail is one bug per few hundred programs, and none of them is reachable from the human-written corpora.
The reduction guards are the real cost, and they are now written down
Three reductions were discarded before a valid one: two read uninitialised
locals (gcc's -Wuninitialized does not fire at -O0; UBSan cannot see them —
valgrind can) and one compared an int with a pointer, making the checksum depend
on where the globals land (caught by requiring gcc PIE and -no-pie to agree).
The five-layer interestingness test is in that ticket; copy it rather than
re-deriving it.
SESSION 2026-08-15/16 — 550 seeds, one bug fixed, one open
Two sweeps run in the background while other tickets were worked: 250 seeds from 90000, 300 seeds from 91000.
- seed 90202 — fixed. Reduced to a one-page repro and closed as
[[bug-c-a-struct-assignment-used-as-a-value-runs-its-rhs-twice]]:
y = (x = f())walked the innercopy_rectwice — once as a top-level statement, once as the outer copy's source — sof()ran twice. Every VALUE was right and only the side effects doubled, which is exactly why the whole human-written corpus (lua, sqlite, tcc, zlib, c-testsuite) missed it and a checksum found it. Pinned bytest/cstruct_assign_value_side_effects.c. - seed 90044 — not a bug. Filed as
PXX_TIMEOUT, but both binaries finished and agreed; pxx took 18.2s to gcc -O0's 6.9s. The harness's fixed wall-clock limit sits between the two, and the bucket name sends the reader hunting an infinite loop. Tool fix filed as [[bug-t-csmith-harness-reports-slow-as-a-timeout]]. - seed 91110 — OPEN, unreduced.
MISCOMPILE_VS_GCC; the divergence is in the elements ofg_42. The per-global checksum trick (./t_gcc 1prints a checksum after EVERY global) is the lever — it names the first global that differs and cuts the reduction to that one. Start here next session.
Scoreboard for the session: 550 seeds, 2 findings, 1 fixed, 1 unreduced.
Parked to backlog/ rather than left in working/ — the campaign is a standing
one and working/ is a live lock.
seed 91110 — reduced and fixed, 2026-08-16
The open one above is closed. Reduction path, for the next reader: the
per-global checksum (./t_gcc 1) named g_42[2] and a direct print narrowed it
to one value (1 vs 0); a gdb watchpoint on g_42[2] named the two writes that
reach it; a printf at the second showed both operands already differing, which
moved the hunt back to the first, in func_27 — whose struct argument was
((*l_1698) = l_1697). Eleven lines out of 1723.
The bug: ResolveNodeRec had no AN_ASSIGN arm, so the argument resolved to
REC_NONE and the by-value record temp took the 8-byte fallback size —
[[bug-c-a-struct-assignment-passed-by-value-copies-only-eight-bytes]]. The full
1723-line program now agrees with gcc on every checksum.
Note that BOTH bugs this campaign found in two sessions are the same construct from two angles: a struct assignment used as a value, once for how many times it RUNS and once for what its VALUE is. That is worth reading as a hint about where to look next — the C expression forms that hand-written code has no reason to write are where the frontend's coverage actually thins out.
2026-08-16 — a CLEAN sweep, which is also data
300 seeds from 92000, run against the fix for seed 91110: 258 agreed with the gcc oracle, 42 skipped (gcc itself could not build or run them), no findings.
Worth recording rather than dropping, because the campaign's value is the RATIO and a clean run is half of it. Ten findings came out of the first sitting, then one per few hundred programs, and now zero in three hundred. That is the shape of a tail, not of a fuzzer that stopped working — the two hits before it were both the same construct (a struct assignment used as a value) seen from two angles, which is what a thinning frontier looks like.
2026-08-16 — a second clean sweep (400 seeds), and the axis that is left
400 seeds from 94000: 354 agreed with the gcc oracle, 46 skipped, no findings. Seven hundred programs in a row now with nothing, against the same default csmith flags that produced ten bugs in the first sitting and one per few hundred after that. Read that as the default-shape space being worked out, not as the fuzzer being finished — a fuzzer only ever finds what its generator's flags can express.
So the next findings have to come from a NEW AXIS, and the ticket has been listing three of them since July:
--opts 0,2,3— the harness has only ever run-O0,-O2in anger, so Track O's-O3passes have never been pointed at this oracle. Free: the cross-check between our own -O levels needs no gcc at all, and a disagreement there is a miscompile we own outright. Running now, seeds 95000+.- Cross targets under qemu — the same programs against aarch64 / arm32 /
i386 / riscv32 codegen. Needs a
--targetpass-through andtools/run_target.sh. - csmith flags the defaults leave off — the two bugs this campaign found in August were both "a struct assignment used as a value", a form hand-written code has no reason to write. That is the tell: coverage thins where the GENERATOR is shy, not where the corpus is.
2026-08-16 — axis 1 done: -O3 in the mix, still clean
300 seeds from 95000 with --opts 0,2,3: 266 agreed with the gcc oracle, 34
skipped, no findings — and no MISCOMPILE_OPT, which is the part that is new.
That bucket compares our own -O levels against each other and needs no oracle at
all, so this is the first time Track O's -O3 passes have been differentially
tested against -O0/-O2 on random programs rather than on the suite.
Running total for the campaign's default-shape space: 1000 seeds since the last
finding. Axis 1 of the three is now spent; the next findings have to come from
axis 2 (cross targets under qemu — needs a --target pass-through and
tools/run_target.sh) or axis 3 (csmith flags the defaults leave off), not
from more seeds at the same settings. Worth doing axis 2 next: it points the
same oracle at four backends that have never seen a random program.
Log
- 2026-08-16 — resolved, commit e9262d5db.
2026-08-19 — axis 3 opened, worked and CLOSED: three flag sets, two findings, both loud
Axis 3 is "csmith flags the defaults leave off", picked on the ticket's own
principle that coverage thins where the GENERATOR is shy, not where the corpus
is. Three batches, 150 seeds each; A and B against a HEAD fixedpoint at
cc20f7101, C at e6a14039a. The verdict is at the end of this section.
Batch A — --paranoid --max-pointer-depth 4 --max-struct-fields 15 --max-union-fields 8 --max-array-dim 3
Seeds 200000-200149. 125 agreed with the gcc oracle, 24 skipped, 1 finding, no miscompiles.
- seed 200056 —
PXX_COMPILE_FAIL, a capacity ceiling. "string table overflow" on a 14125-line program with 9426 distinct string literals againstMAX_STRS = 8192. Filed [[bug-a-string-table-cap-refuses-a-14k-line-c-program]] (Track A —defs.inc/emit.incare core, so C found it and A owns it). Measured there: raising the cap makes the program compile in 2.7s and agree with gcc, butVisCacheVisis sized byMAX_STRSwhile being indexed by unit, and its full-array clear sits on the name-lookup path — so the naive one-line raise buys a quiet slowdown. The ticket says fix that coupling first. - What the flag bought:
--paranoidemits a pointer assertion with its own message text at every pointer op, so literal count scales with program size instead of staying flat the way hand-written C's does. That is the whole reason this ceiling had never been hit — no corpus program is literal-dense.
Batch B — --builtins --builtin-function-prob 40 --max-funcs 3
Seeds 210000-210149. 48 agreed, 46 skipped, 56 hits deduping to 11 distinct — and all 11 were one gap, not eleven.
Every one was call to undeclared function: __builtin_<X>. cfront renamed six
gcc bit builtins onto crtl helpers and was missing the rest:
- the
lrow of families it already had —__builtin_clzl,ctzl,popcountl.clz/clzllwere both there andclzlwas not, on all three families. This is the double-case shapenormalise-dont-special-case.mdwarns about, caught by the generator rather than by a reader. __builtin_ffs/ffsl/ffsll,__builtin_parity/parityl/parityll,__builtin_bswap16/32/64— absent entirely.
Fixed under Track C (cfront + lib/crtl, both C's own files, no core change).
The l row resolves through CLongWidthSuffix, which reads TARGET_PTR_SIZE
— C long is machine-word-sized, so __builtin_clzl is the 64-bit helper on
LP64 and the 32-bit one on ILP32, exactly the rule ParseCDeclType already
applies to long itself. A hard-coded 64 would have been silently wrong on
i386/arm32/riscv32; verified under qemu on all four cross targets, where the
l rows correctly read 24/32/31 against x86-64's 56/64/63. ffs and parity
are DEFINED at zero in gcc, so they are written to answer it rather than routed
through ctz/popcount.
All 11 seeds now compile AND agree with gcc's checksum — the builtins are
not merely accepted, they are right on real generated programs.
test/c_builtin_bits.c pins the family against gcc.
Reading batches A and B
Batch B's ratio looks alarming (56 hits in 150 seeds) and is the opposite of alarming: it is ONE missing table row family, found instantly because the generator was finally allowed to emit the construct. Batch A's ratio looks clean (1 in 150) and found the more serious thing. Hit count is not severity — dedup count is closer, and neither is a substitute for reading the bucket.
Neither batch produced a MISCOMPILE_VS_GCC. The default-shape space has now
gone 1000 seeds without one and these 300 did not change that; what axis 3
bought was a capacity ceiling and a coverage gap, both invisible to a checksum
oracle because both fail LOUDLY. Worth saying plainly: axis 3's yield so far
is compile-fails, not miscompiles. If the next flag set is also loud-only,
that is evidence the remaining silent bugs need axis 2 (cross targets) rather
than more generator flags.
Flag sets tried and spent, so nobody repeats them: --paranoid + deep
aggregates (1 finding), --builtins (1 finding, 11 buckets). Not yet tried:
--float — deliberately deferred, because csmith checksums floats by hashing
the bits, so any ulp difference reads as a full divergence and the bucket cannot
distinguish a codegen bug from an fp-contraction difference against gcc. That
needs -ffp-contract=off on the gcc side before it is worth a sweep, and the
findings would need the float-scope rules applied by hand.
Batch C — the prediction, tested: --inline-function --inline-function-prob 80 --max-funcs 6 --max-expr-complexity 8 --max-block-depth 4, --opts 0,2,3
Seeds 220000-220149, HEAD fixedpoint at e6a14039a. 131 agreed with the gcc
oracle, 19 skipped, ZERO findings — no miscompile, no compile-fail, nothing.
Run deliberately as the test of the prediction written above ("if the next flag
set is also loud-only, that is evidence the remaining silent bugs need axis 2"),
not as another sweep. The set was picked to give the SILENT buckets their best
shot: --inline-function at 80% drives the inliner, and --opts 0,2,3 makes
MISCOMPILE_OPT live — that bucket compares our own -O levels against each
other and needs no gcc oracle at all, so it can catch an optimiser bug on a
program gcc refuses to build.
Verdict on the prediction: confirmed, and more strongly than it was stated.
The prediction expected another loud-only batch; what came back was quieter than
that — not even a compile-fail. Axis 3 (generator flags) is spent as a source of
new signal on x86-64: three flag sets, 450 seeds, two findings, both loud, and
the batch aimed squarely at silence returned nothing. 1450 seeds total on this
axis without a single MISCOMPILE_VS_GCC.
So: stop here, and the priority moves to axis 2. Track T's --target
pass-through is now the thing standing between this harness and the untested
surface, and it inherits axis 3's priority — running the same generator against
i386/arm32/aarch64/riscv32 tests four backends' worth of codegen that no seed
has ever reached, whereas a fourth flag set would re-test x86-64.
Flag sets tried and spent (do not repeat): --paranoid + deep aggregates,
--builtins, --inline-function + deep expressions with multi--O. Still not
tried: --float, for the reason above.
2026-08-20 — axis 2 opened (cross targets): 370 seeds, THREE comparison classes, zero findings
Binary: self-hosted fixedpoint at 272e347bb — make compiler/pascal26
rebuilt deliberately at that sha before the first batch (the tree had an edit on
top of the previous build), tree clean, no pin taken.
Axis 3 closed with the prediction "the remaining silent bugs need axis 2". This
is axis 2's first sitting, and the first thing it turned up was not a compiler
bug but the shape of the axis: on this box there is no cross gcc of any kind
(aarch64-linux-gnu-gcc, arm-linux-gnueabihf-gcc, riscv64-linux-gnu-gcc —
none installed; gcc -m32 accepts the flag and then cannot link, no Scrt1.o).
qemu-user IS present, so we can RUN every target and cannot independently
JUDGE any of them. That splits axis 2 into three classes with different
evidentiary strength, and they must be reported separately or the numbers lie.
D1 — aarch64 through the harness, no oracle: 150 seeds, 0 findings
tools/csmith_fuzz.py --target aarch64 --iters 150 --seed-start 300100.
136 ran clean across pxx -O levels, 14 skipped, no findings. The harness
said so itself, in the header: NO ORACLE for aarch64 (LP64) -- aarch64-linux-gnu-gcc: not installed, so MISCOMPILE_VS_GCC and PXX_SLOW
were NOT CHECKED. What this batch does cover is real and worth having:
MISCOMPILE_OPT (our own -O levels against each other), PXX_CRASH,
PXX_COMPILE_FAIL, PXX_TIMEOUT — 136 csmith programs compiled and ran on the
aarch64 backend without one of them. That is a first for this campaign; every
prior seed in 1450 hit x86-64 only.
But a batch whose strongest bucket is switched off is a weak batch, and saying "150 seeds on aarch64, clean" without the qualifier would overstate it by exactly the bucket that has found most of this campaign's bugs.
D2 — the LP64 differential, oracle restored: 120 seeds, 0 findings
Then the harness's own doctrine (tools/csmith_fuzz.py, ~line 120: "The data
model decides whether a checksum is comparable at all; the ISA does not")
answered the missing-oracle problem. aarch64 is LP64; so is native x86-64 gcc;
both little-endian. A native gcc checksum IS comparable with an aarch64 pxx
checksum, because a csmith checksum is arithmetic over integers of a given
width, not machine code.
Seeds 310100-310219, native gcc -O0 vs pascal26 --target=aarch64 run under
tools/run_target.sh: 105 agreed, 15 skipped, 0 divergences.
That restores MISCOMPILE_VS_GCC for aarch64 with no cross toolchain
installed, and it is the batch with real teeth in this sitting.
It also surfaced a Track T defect, filed through the coordinator as
bug-t-csmith-oracle-list-is-keyed-on-isa-when-its-own-doctrine-says-data-model
(T, p60): ORACLE_CC keys its candidate compiler list on the ISA, so
--target aarch64 looks only for an aarch64 gcc and reports NO ORACLE, while
the doctrine three screens above says the data model is what decides. The
fix is nearly free — fuzz_one already builds and runs a native gcc on every
seed as a validity filter and already has its checksum in gcc_out, so for any
LP64 target the oracle is already computed and then discarded.
Trap recorded for whoever implements it: reuse the CHECKSUM, never the
TIMING. oracle_sec scales pxx's time budget; feeding a native gcc's seconds
into an emulated target's budget would manufacture PXX_TIMEOUT findings —
bug-t-csmith-harness-reports-slow-as-a-timeout in costume. Comparability of
checksums is governed by the data model; comparability of timings by the
execution environment, and those are different questions. So: set oracle_sum,
leave oracle_sec None for emulated targets, keep timing NOT CHECKED.
D3 — the ILP32 cross-backend class: 100 seeds, 0 findings, 0 layout-suspect
The three ILP32 backends against each other on the same program — i386, arm32, riscv32, seeds 320000-320099. Not an independent oracle (both sides are ours); a different question: not "is pxx right?" but "is pxx self-consistent?". 84 agreed, 16 skipped, 0 layout-suspect, 0 findings.
Two design points in this class are worth keeping, because both were nearly got wrong:
- A native-gcc validity filter still belongs here, even though its checksum is useless across the data-model boundary. It is answering only "is this program buildable and runnable at all" — without it, a csmith hiccup gets filed as a pxx compile failure it is not. On this run the filter is doing only that original job.
- Same data model does NOT mean same ABI. SysV i386, AAPCS32 and the
riscv32 psABI have genuinely different bitfield and struct-packing rules, and
a csmith checksum reaches layout through unions and bitfields. "We own both
sides" means any difference is ours; it does not mean any difference is a
defect — ownership of the code is not ownership of the specification it
implements. So a divergence here is triaged at the moment of the hit, not at
reduction time: re-run the same seed with
--no-bitfields --no-packed-struct --no-unions, and if the three then agree it isLAYOUT-SUSPECT(possibly a legitimate ABI difference) rather than a backend bug. Deliberately NOT disabling those constructs sweep-wide — bitfields produced 3 of this campaign's first 9 bugs.
The triage did not fire this sitting (0 layout-suspect), so it is untested machinery; it is written into the script so the next hit is classified rather than argued about.
Reading the sitting
370 seeds, zero findings — a dry sitting, reported as one. That is a result,
not a non-event, but it is a weaker result than the raw number suggests and the
weighting matters: 120 of the 370 (D2) had the strongest oracle this campaign
has; 150 (D1) had no MISCOMPILE_VS_GCC at all; 100 (D3) had a self-consistency
check instead of an oracle. So the honest one-liner is "120 seeds of real
cross-target differential, plus 250 of loud-bucket-only coverage, all clean" —
and D1/D2 should be read as a PAIR, since they are the same backend with the
strongest bucket off and then on.
The backends are in better shape than the absence of a cross toolchain made it
look. What axis 2 needs next is not more seeds at this strength but the Track T
oracle fix, which converts every LP64 cross batch into a D2 automatically and
would make an i386/arm32/riscv32 differential possible against a -m32 gcc on a
box that has one.
Scoreboard delta: seeds 1450 → 1820; findings unchanged; first non-x86-64 seeds in the campaign's history (250 of them).
CORRECTION 2026-08-20 — the LAYOUT-SUSPECT discriminator above is INVALID in the direction it was written for
Track T measured the D3 triage and it does not do what it says. csmith's
option set is part of its RNG input, so re-running a seed with
--no-bitfields --no-packed-struct --no-unions does not yield the same program
minus those constructs — it yields a different program (T measured seed 90044
going 1879 → 3373 lines with a different checksum). Agreement from that rerun
therefore carries almost no power to clear the original divergence, which was
the classifier's entire job.
The other direction survives: if the layout-free run also diverges, that is a fresh finding on its own merits. But "agrees, therefore layout-dependent" was never valid, and the D3 script must not be used to make that call again.
What replaced it in tools/csmith_fuzz.py (T, 174186b5d) is better than
either of our versions: it reads csmith's own XXX statistics footer for the
two constructs that can actually reach a checksum — union variables, and structs
with bitfields — because csmith hashes named FIELDS through transparent_crc,
not raw bytes, so padding and alignment cannot reach the checksum at all. That
narrows the ABI caveat well below where D3 put it. A divergence in a program
with neither construct is a MISCOMPILE_VS_GCC with the caveat explicitly
retired; anything else is LAYOUT_SUSPECT, naming the construct and its count.
LAYOUT_SUSPECT ranks with the miscompiles and is deliberately kept out of
the exit code, so a red exit cannot become pressure to misfile an ABI difference
into Track A.
D3's numbers are unaffected (it recorded 0 layout-suspect, so the invalid arm never fired) and so is the sitting's reading. What is retracted is the claim that the triage is sound-but-untested machinery: it is unsound in one direction, and that was not visible until someone measured what the flags do to the generator rather than reasoning about what they mean.
Second lesson, T's, worth more than the fix: their first detector grepped the C
source for \bunion\b and reported unions in 12 of 12 programs — every hit
was the footer line XXX total union variables: 0. A true fact about the wrong
subject, inside the classifier written to prevent exactly that. The thing you
grep for appears in the metadata too.
2026-08-20 (later) — axis 2 with a REAL oracle: 160 aarch64 differential seeds, dry
Binary: self-hosted fixedpoint at 21f05c52b (make compiler/pascal26,
converged in 1 round). Tool at T's 174186b5d.
T's data-model oracle landed, and the header line is the whole difference. The morning's D1 run said:
NO ORACLE for aarch64 (LP64) -- aarch64-linux-gnu-gcc: not installed.
MISCOMPILE_VS_GCC and PXX_SLOW are NOT CHECKED this run
and this one says:
vs gcc -O0 oracle (datamodel)
oracle: gcc (LP64, matches the DATA MODEL, not the ISA) -- runs natively.
Checksums are compared; TIMING is not (PXX_SLOW is off this run)
So aarch64 moved from the weak class to the strong one with no change on our side. 129/150 agreed with the gcc oracle, 21 skipped, no findings (seeds 330000-330149), plus a partial second batch of 31 agreed / 2 skipped (seeds 330200-330232) before it was killed from outside this session — see below. 160 real cross-target checksum comparisons, zero divergences.
Weighting, stated as before
This is 160 seeds of the STRONGEST class this campaign has: a different backend
from the one that generated the checksum, compared against an external oracle,
on a target Track O actually invests in. It is not 160 seeds of loud-bucket
coverage. Against that, the sample is small — the x86-64 axis has 1450 — and a
dry 160 bounds only what it measured: it says the aarch64 backend does not
diverge on csmith's default construct mix at -O0/-O2, not that it is
clean. The morning's 250 loud-bucket-only seeds are not superseded by this;
they measured a different thing (crashes, compile failures, hangs) and remain
the only coverage riscv32 has.
Scoreboard: 1820 -> 1980 seeds.
What I checked in T's implementation before running
Three things differed from what was sketched, and all three are improvements:
- The oracle-reuse guard is
cfg.oracle_cc == VALIDITY_CC, not a data-model comparison. The data-model version is wrong and it is worth recording why, because it reads as the more principled test: the validity filter always runs plain nativegcc, so the moment the probe picksgcc -m32for an ILP32 target, a data-model guard would say "same model, reuse it" and compare a 32-bit target against 64-bitlongs — the exact wrong-width comparison the file exists to refuse, reached by way of an optimisation. - The LAYOUT-SUSPECT discriminator I proposed and retracted this morning is
replaced by one reading csmith's own
XXXstatistics footer for union variables and bitfield-carrying structs. The fact that settles the scope: csmith hashes named fields throughtransparent_crc, not raw struct bytes, so ordinary padding and alignment cannot reach a checksum at all. The caveat is narrower than either side of this morning's exchange assumed — it is bitfield ALLOCATION and union PUNNING only. LAYOUT_SUSPECTranks with the miscompiles in the report but is deliberately outside the exit code, so a maybe cannot fail a run.
riscv64 is listed but unreachable
HOST_ORACLE carries riscv64: LP64 and a riscv64-linux-gnu-gcc entry.
pxx has no riscv64 backend — there is no TARGET_RISCV64 in defs.inc,
only riscv32. The entry is correct-in-advance, not currently live, so this
oracle fix buys exactly ONE target today: aarch64. riscv32 stays ILP32 with no
oracle on this box (gcc -m32 accepts the flag and cannot link — no Scrt1.o).
Worth knowing before anyone reads "aarch64 and riscv64" as two targets' worth
of new coverage.
Two runs killed from outside this session
The second 150-seed batch was killed twice, at seed 4 and at seed 33, both
times mid-run. Nothing in this session issued a stop; no csmith or qemu process
survived either kill; ~8 GB was available and the kernel log is not readable
unprivileged, so OOM is unruled-out rather than ruled out. The first 150-seed
batch had completed normally minutes earlier, so it is not deterministic. Noted
rather than fought — restarting into a third kill would have added nothing.
The known repo hazard here is a peer's pkill -f <script> matching another
agent's run of the same tool, which has happened before in this checkout.
Sitting 2026-08-20 (frank1-ACP) — DRY, 100 seeds, x86-64, --opts 0,2,3
tools/csmith_fuzz.py --iters 100 --opts 0,2,3 --compiler ./compiler/pascal26
93/100 agreed with the gcc oracle, 7 skipped by the native validity filter,
no findings. Seeds 1..100, default csmith flags, compiler at 584c2703d
(a self-hosted fixedpoint build at that sha, not a stale binary).
Reported as a dry run so the next sitting does not spend these seeds again. Two things it does say:
-O3is now in the sweep. The "what is still open" list above names--opts 0,2,3as an untried axis; this batch ran it, and pxx's own -O0/-O2/-O3 agreed with each other on all 93. That is the first evidence for Track O's newer passes from this oracle.- The run was the regression check for the same day's two C assignment-lowering
fixes (
311864e46,ed49b3ef7), which is why it was worth spending a batch on a corner csmith has already been through.
A first batch, started before those fixes landed, was discarded rather than
reported: the compiler binary was rebuilt underneath it mid-run, so its
results describe no single compiler. Noted because the mistake is easy to repeat
— the harness takes --compiler <path> and reads that path per program, so a
make compiler/pascal26 during a run silently splits the batch in two.
Sitting 2 — 2026-08-20, seeds 20000..20249, --opts 0,2,3, x86-64
tools/csmith_fuzz.py --iters 250 --seed-start 20000 --opts 0,2,3 \
--compiler <a snapshot copy of compiler/pascal26 at fabe8e845>
217/250 agreed with the gcc oracle, 33 skipped by the native validity filter, no findings. A fresh seed range, so it does not re-walk sitting 1's ground.
The operational note from sitting 1 — that a make compiler/pascal26 during a
run silently splits the batch across two compilers — has a cheap fix worth
writing down, because the run is long and the session usually wants to keep
working: cp compiler/pascal26 <scratch>/pxx-<sha> and pass that path to
--compiler. The harness reads the path per program, so a snapshot pins the
whole batch to one binary and the tree stays free to rebuild. Name the copy
after the sha; the batch's provenance is then in the filename rather than in
someone's memory.
Caveat learned the same way: the snapshot cannot compile anything that uses
the RTL, because pxx resolves lib/ relative to its own location. Fine for
csmith (freestanding C), not a general-purpose trick.
Sitting 3 — 2026-08-20, seeds 30000..30299, --opts 0,1,2,3, x86-64
Compiler 55388136a, snapshot-pinned as sitting 2 recommends. 253 of 300
agreed with the gcc oracle, 47 skipped by the native validity filter, no
findings. --opts 0,1,2,3 adds -O1 to sitting 2's set, so the three
optimisation tiers plus -O0 all agreed on every program that ran.
Three dry sittings in a row over 850 fresh seeds is now the shape of this
campaign, and it is worth saying plainly: csmith has stopped being the
productive oracle for this compiler. The two bugs it found (seeds 5038 and
31039) were both integer-conversion rules, and the same afternoon a plain FPC
differential probe found four more of exactly that family (the div/mod and
comparison signedness pair, Str of an unsigned, Format('%u')) in under an
hour of hand-written probes. Csmith generates C, so it can only ever exercise
the C frontend's slice of the shared IR; a hand-written Pascal probe reaches
constructs csmith has no vocabulary for.
Recommendation for whoever picks this up: keep csmith running as a background
regression net (it is nearly free — nice -n 15 and a snapshot), but do not
budget session time on widening it. Spend that time on
tools/pasmith.py (random Object Pascal, FPC oracle) instead, which fuzzes the
frontend that four of today's six findings came from.
Sitting 4 — 2026-08-29 (frankC), seeds 2000..2199, default --opts 0,2, x86-64
tools/csmith_fuzz.py --iters 200 --seed-start 2000
183/200 agreed with the gcc oracle, 17 skipped by the native validity filter,
no findings. Compiler: a self-hosted compiler/pascal26 at a374f6b63's tree,
fixedpoint verified (converged after 2 round(s)) before the run and not rebuilt
during it — so the batch is pinned to one binary even though sitting 2's
--compiler snapshot trick was not used. Findings directory empty; all five
buckets zero.
Weaker than sittings 2-3 in one dimension, and it should be read that way:
this used the DEFAULT --opts 0,2, not their 0,1,2,3. So -O1 and -O3 were
not swept here and this sitting does not renew their evidence.
The skip rate is lower than the established baseline rather than a new
problem — 8.5% here against 13.2% (sitting 2) and 15.7% (sitting 3). Worth
recording because a skip is a program that told us nothing, and the filter is
purely gcc-side (gcc won't build it or its own binary hung), so no pxx-side
coverage is being silently discarded by a pxx defect.
The "residual crash class" bullet's named repros are gone
The What is still open section named csmith seeds 901 and 1502 as the last
known unreduced crashers, from 2026-07-13. Both now report 1/1 agreed with the gcc oracle, no findings at HEAD, and this 200-program batch produced no crasher
of any kind.
The bullet is left OPEN deliberately. Two named seeds passing means those two
representatives are fixed, not that the class is; and "no crasher in 200
default-flag programs" is evidence against a class the ticket said "reproduces
in minutes", which is a genuine update but still not a proof of absence. What
would close it is a deliberate attempt that fails — a wider flag set, or the
--opts 0,1,2,3 sweep this sitting skipped — not another dry default run.
This makes four dry sittings over 1050 fresh seeds, which strengthens rather
than changes sitting 3's conclusion above: keep csmith as a cheap background
regression net, spend session time on tools/pasmith.py instead.
SITTING 2026-08-30 (frankC) — 443 comparisons, DRY, and the -O3 axis run for the first time
A null batch is a result, so it is reported as one, with its seed range and its flag set.
| batch | seeds | csmith args | opts | agreed | skipped | findings |
|---|---|---|---|---|---|---|
| A | 1-200 | default | 0,2,3 | 175 | 25 | 0 |
| B | 40000-40299 | default (full complexity) | 0,2,3 | 268 | 32 | 0 |
Compiler f2bfbb3c94a5 — a self-host fixedpoint at HEAD f278ddaca, converged
in 1 round, sha distinct from pinned, so the binary is the one these sources
define and not a copied-in seed.
Seed space 40000+ was unused by any prior sitting (previous runs took 5000+, 9000+, 12000+, 20000+, 31000+), so batch B is new ground rather than a re-walk.
The -O3 axis, previously listed as open, is now run — and it is clean
The bullet said the harness "only ran -O0,-O2 in anger" and that adding -O3
"would point the same oracle at Track O's newer passes for free". Both batches
ran --opts 0,2,3. Zero MISCOMPILE_OPT across 443 comparisons.
That is the finding we own outright — a disagreement between our own -O0 and
-O3 needs no oracle and admits no question of who is right — and it is worth
recording as a negative: Track O's -O3-gated passes have no differential
coverage anywhere else, and now they have 443 programs of it.
The 25/32 skips are legitimate, checked rather than assumed
skip (the native validity filter could not build/run it) is 12.5% of batch A.
Measured on seed 191: gcc builds it fine and the gcc-built binary does not
terminate — still running at 120s against a 15s cap. So these are
non-terminating csmith programs and skipping them is correct; nothing about pxx
is being hidden.
Worth stating because the harness gets this right in a way other rungs in this
repo do not: it reports 175 agreed ... (25 skipped), keeping the skip count
out of the pass count. 200 pass would have been the lie.
All three named open findings now pass — and NONE of them can be closed
901, 1502 (2026-07-13) and 5004 (2026-07-18, the PXX_COMPILE_FAIL kind-5
AN_BINOP gap) all pass at HEAD.
Do not read that as three fixes. A seed reproduces the same program only
against an identical generator and identical --csmith-args; this box runs
csmith 2.3.0/30dccd7 and nothing records what produced the July findings, whose
saved t.c files lived in a /tmp scratchpad this ticket already notes is gone.
So each "passes" is equally consistent with fixed and with today's csmith
emitting a different program for that seed. The error class still exists as a
general fallback at ir.inc:617.
Filed as bug-t-a-fuzz-finding-cited-by-seed-alone-cannot-prove-a-fix [T p45] —
three independent instances, so a format defect rather than bad luck. The three
findings above should be marked unverifiable (cause: citation format), not
closed, and rediscovered by a future batch if the classes survive.
Still open after this sitting
-
The residual crash class — still unestablished. Batch B was full complexity, which is where the ticket says a crasher must come from, and found none in 268 comparisons. That is evidence, not proof: it narrows the class without closing it.
-
Cross targets — first ever cross batch RUN this sitting, and clean: 100 programs at
--target aarch64, seeds 50000-50099, throughtools/run_target.sh— 90 agreed, 10 skipped, 0 findings. An i386 (ILP32) batch followed at seeds 60000+.The harness reported one thing more than a verdict, which is worth copying:
NOT CHECKED: the slow ratio — the oracle matched aarch64's data model, not its ISA, and ran natively.It names the comparison it did not make rather than letting a green stand for it. That is the same discipline as reporting skips separately from passes, and the exact opposite of a rung that self-skipsexit 0.
Cross-target coverage is NOT uniform, and the reason is the oracle — 2026-08-30
The two cross batches this sitting returned different kinds of result, and the difference is not visible unless the report's own "NOT CHECKED" lines are read:
| batch | oracle | what was actually compared | result |
|---|---|---|---|
| aarch64, seeds 50000+ | yes — native gcc, LP64 matches | pxx vs gcc, plus pxx -O vs -O |
90 agreed, 10 skipped, 0 findings |
| i386, seeds 60000+ | NONE | pxx -O0 vs -O2 only |
88 clean, 12 skipped, 0 findings |
NO ORACLE for i386 (ILP32) -- gcc: does not build; i686-linux-gnu-gcc: not installed. MISCOMPILE_VS_GCC and PXX_SLOW are NOT CHECKED this run; pxx-vs-pxx -O comparison still is.
aarch64 got a real differential only because it is LP64 — the same data model
as the native compiler, which is exactly what 174186b5d built the
data-model-not-ISA oracle selection for. It is working as designed.
Every ILP32 target has no oracle on this box. Measured, not assumed:
i686-linux-gnu-gcc arm-linux-gnueabihf-gcc arm-linux-gnueabi-gcc
riscv32-linux-gnu-gcc riscv64-linux-gnu-gcc aarch64-linux-gnu-gcc -- all MISSING
gcc -m32 -> cannot find Scrt1.o / crti.o (no multilib)
So an i386 / arm32 / riscv32 batch is a self-differential only: it can find a
disagreement between our own -O levels — which is a real finding we own
outright — and it cannot find "we disagree with gcc" at all. A clean ILP32 batch
means considerably less than a clean aarch64 one, and the report says so on its
own line rather than leaving the reader to infer it.
The remedy needs root and is therefore not an agent's to apply:
gcc-multilib would give -m32 for i386; the gcc-*-linux-gnu cross packages
would cover arm32/riscv32 (and give aarch64 a same-ISA oracle instead of a
same-data-model one). Raised for the owner rather than worked around, and noted
here so the next sitting does not read a clean ILP32 batch as a vs-gcc result.
-
Bitfield LAYOUT — NOW FILED, and it is worse than this bullet said. Filed as
bug-c-a-long-long-bitfield-after-a-smaller-one-puts-later-members-at-the-wrong-offset[C p40]. The recorded shape (sizeof12 vs 8) does not reproduce; the real one isunsigned a:1; unsigned long long b:33;, where pxx starts a second allocation unit and gcc packs one. With a following member the SIZES MATCH (16 both) andoffsetofdiffers (12 vs 8) — so the size assertion this bullet implied is exactly the test that misses it, which is why the sharper half went unrecorded for seven weeks.This bullet's own reasoning was right and is now confirmed by measurement: values are always correct, so the checksum oracle cannot fire, and the item survived 443 clean comparisons this sitting exactly as predicted. A layout differential needs to compare OFFSETS, not sizes — comparing sizes reproduces this bullet's blind spot.
-
Brace elision over rows — pre-existing, unchanged.
AXIS: --builtins — first run, 2026-08-30 (frankC), and it found a hang
This ticket names "csmith flags the defaults leave off" as an untried axis.
--builtins is off by default, so every run this campaign has ever done left
our builtin lowering uncompared against gcc. First run of it:
tools/csmith_fuzz.py --iters 120 --seed-start 700000 "--csmith-args=--builtins"
40/120 agreed with the gcc oracle (77 skipped)
PXX_TIMEOUT 3 hit(s), 1 distinct
Compiler 883476f0abaf. The finding is filed as
[[bug-a-a-csmith-program-hangs-under-pxx-at-every-o-level-and-runs-under-gcc]] —
a program that compiles clean and then hangs at every -O level while gcc
runs it. Repro preserved verbatim at test/csmith/hang_builtins_700082.c.
The bug is not in a builtin. All 15 builtins the program uses agree with gcc individually. The axis earned its keep by generating an unusual program shape, not by exercising broken builtin lowering. Do not re-run that comparison.
The 64% skip rate is a property of this axis, not a fault
77 of 120 skipped, which looks alarming and is not. csmith's --builtins
emits x86-specific intrinsics — __builtin_ia32_crc32qi and friends — that
gcc itself refuses without the matching -msse4.2/-march flags:
error: implicit declaration of function '__builtin_ia32_crc32qi'
The harness skips rather than scoring those, which is its own doctrine working
correctly ("never report a comparison it did not make"). Feeding gcc
-march=native would "fix" the skip rate and make it worse: the programs would
then compare pxx against SSE4.2 intrinsics we have never claimed to implement,
turning every one into a PXX_COMPILE_FAIL that means nothing.
The real remedy is to restrict generation to the portable builtins
(clz/ctz/popcount/parity/ffs/bswap), which is what the surviving 43
exercise. --disable-builtin-kinds ia32 is not the spelling — it does not
filter — and the kind names are not in --help or in /usr/share/csmith.
Finding them (csmith's builtin spec tables) is the one piece of work that would
make this axis ~3x more efficient per iteration. Left undone deliberately: the
axis already paid for itself at 36% yield and the tuning is Track T's file.
--float, the other default-off axis, was deliberately NOT taken
Legitimate FP rounding differences would make every divergence ambiguous, and disambiguating them is exactly the spend Track F's charter exists to prevent ("float accuracy is LOW PRIO by definition"). A float miscompile is still an ordinary bug by the "rank the mechanism, never the datatype" rule — but this axis cannot tell the two apart without per-finding adjudication, so it is a poor buy at this prio. Recorded so the next person does not re-derive the decision.
The --builtins axis generates UB, and that changes what a divergence means (2026-08-30)
Found by frankA while diagnosing [[bug-a-a-csmith-program-hangs-under-pxx-at-every-o-level-and-runs-under-gcc]], which the axis produced.
__builtin_clz(0) is undefined in C, and gcc's own answers prove it rather
than merely permitting it: one expression yielded 64 folded, 36, and 63,
varying only with how the argument arrives. So on a program that calls it at
zero, gcc is not an oracle — it is a second implementation exercising its own
freedom.
Consequence for this campaign, and it is a scoping rule rather than a caveat:
A pxx-vs-gcc checksum divergence on a UB program is not by itself evidence of a defect.
The hang that came out of this run is a real defect — an unterminating loop in
our own lib/crtl on a plausible input is a bug whatever the standard permits —
but it was identified by its shape (a hang, diagnosed to one line), not by the
checksum comparison. Had it merely printed a different number, filing it would
have been wrong.
So before this axis is trusted at scale its predicate needs to reject UB
candidates, or every --builtins divergence needs manual UB triage before it
becomes a ticket. Untriaged, the axis manufactures tickets at whatever rate csmith
emits undefined constructs — and each one costs a real investigation to dismiss.
The harness (tools/csmith_fuzz.py and any Csmith runs) is Track T's by
CLAUDE.md; this ticket keeps its C letter because the findings land in the C
frontend and lib/crtl. Whoever changes the predicate should be T.
Unrelated and still true: the 64% skip rate is the harness being honest —
csmith emits x86 ia32 intrinsics gcc itself refuses without -msse4.2, and the
harness declines to score a comparison it could not make. Feeding gcc
-march=native would "fix" the rate and make every result meaningless. A metric
that improves when you weaken the oracle is not a metric.
And a verification caveat that outlives this ticket
frankA, guarding the four routines and getting the full program to print the oracle's checksum exactly:
Re-running with the guard returning 0, 7 and 63 gives the identical checksum every time: the program cannot see the value.
"Checksum matches gcc, done" would be citing a constant. The natural close for any csmith finding is exactly that sentence, and on a program whose observable does not depend on the value you changed, it is worth nothing. Vary the fix's return value and confirm the checksum moves before you accept a checksum match as evidence. If it does not move, the program is not the test — write one that is.
Same family as the campaign's other blind instruments: pxx↔pxx agreement, the nine-word probe where two formulas coincide, and byte-identity over a corpus that never reaches the case.
The --builtins axis generates UNDEFINED programs — needs a predicate
Carried back from the hang this axis found
(bug-a-a-csmith-program-hangs-under-pxx-at-every-o-level-and-runs-under-gcc,
fixed as 99b556c43). frankA's observation while diagnosing it, and it is a
property of the axis rather than of that one program:
__builtin_clz(0) and __builtin_ctz(0) are UNDEFINED in C, and gcc's own
answers show it — the same expression yielded 64 when folded from a literal and
other values when the argument arrived at runtime. csmith's --builtins mode
emits these calls without proving the argument is non-zero, so a generated
program can be UB in a way csmith's usual safe-math machinery does not cover.
Consequence for this campaign: a checksum divergence on such a program is not by itself a defect. Two conforming compilers may legitimately disagree. The axis is still worth running — it found a genuine hang, and a hang is never an acceptable response to UB — but its divergences need adjudication that the integer axis does not.
Before --builtins is trusted at scale it wants a predicate that rejects UB
candidates. I proposed comparing gcc against itself at two -O levels and
dropping programs where gcc disagrees with gcc — no source analysis, and the same
shape as the data-model oracle rule already in this ticket: do not score a
comparison whose oracle is not entitled to an opinion.
frankA measured it against the program that motivated it, and it returns
GREEN. hang_builtins_700082.c gives checksum = 5ABA20EA at -O0, -O1,
-O2 and -O3 alike, so the predicate would have kept it. Correcting my own
recommendation before it gets recorded as the fix.
The measurement splits UB into two classes rather than condemning the idea:
| UB class | operand | cross--O check |
|---|---|---|
| optimizer-resolved | compile-time constant | caught — const column moved from -1558077993 at -O0 to 64 at -O1+ |
| runtime-operand | value computed at run time | invisible — volatile column is 63 at every level: same instruction, same deterministic garbage |
The hang was the second class: __builtin_clzl(safe_mul_func_uint8_t_u_u(255UL, ...)), a runtime value that happened to be 0, not a folded constant.
So the predicate is sound but not complete — it rejects only genuinely-UB
programs, which is the direction you want, and it is blind over exactly the class
this finding came from. Note the shape: a check that looks like it covers the
case and cannot see it, which is the same failure as the if (u[i]) guard
recorded above. Keep it — it is nearly free and stops a class that would
otherwise manufacture tickets — but it must not carry the claim that the axis
is UB-clean.
Closing the runtime class costs more, and nothing here is measured yet:
-fsanitize=undefined on the oracle build only is the closest fit to the
"is this oracle entitled to an opinion?" shape — it answers per-program, catches
bit-scan-at-zero directly, and needs no source analysis, at real runtime cost.
The alternative is generating with csmith options that avoid the undefined
builtin domains up front, if --builtins has such a knob. Worth a measurement
before committing to either.
Tuning the harness is Track T's file, so this is a note for whoever picks that up rather than work claimed here.