M4f: real C corpus
Reproduce
nix build .#checks.x86_64-linux.nanofil
# For a retained survey (after building the tools in AGENTS.md):
nix build --impure --no-link --print-out-paths --expr '
let pkgs = import (builtins.getFlake (toString ./.)).inputs.nixpkgs {
system = "x86_64-linux";
}; corpus = import ./nanofil/nix/corpus.nix { inherit pkgs; };
in [ corpus.cproc corpus.c-testsuite ]'
export SWIPL=/tmp/swipl/bin/swipl FILCC=/tmp/filcc/bin/clang
python3 nanofil/tests/corpus.py CPROC_STORE_PATH C_TESTSUITE_STORE_PATH \
--work /tmp/corpus
# Narrow a rerun with --test c-testsuite/00205.c (repeatable).
# Review results.tsv and per-test log/output/diffs before using --record.The sources are fixed-output Nix fetches in corpus.nix:
- cproc at
d1c53ddf56571573a7025324c8dd5c6d547a4d1f:sha256-u9zlMI0NOLUXCXtEWw/c8gOgqeH6yAq+ZIqIoywPitU=. License correction: cproc itself is ISC, but this revision's LICENSE explicitly dedicatestest/*to the public domain under the Unlicense. - c-testsuite at
5c7275656d751de0e68b2d340a95b5681858ed07:sha256-bKfdr7veh+UJvvlumm7vxRyP5uO9uelZziBsp30HbgU=. Its runner is MIT; individual tests have their own.otagprovenance, as explained bytests/LICENSE. The fetched trees retain all licenses.
corpus.py inventories every .c recursively
in cproc's test/ and c-testsuite's
tests/single-exec/. Programs with main(
(allowing whitespace, ignoring comments) go through the existing
progtest.sh: filcc -E → nanofil → GNU
as → filcc link; the reference is filcc -O1. Both links
include -lm, needed by c-testsuite 00174. No source
rewrites, private headers, synthesized main functions, or
architecture-name skips. In particular, a test named
+aarch64 is compared against the x86-64 oracle, not checked
against cproc's architecture-specific .qbe expectation.
Since M4e, nanofil preprocesses .c files itself
(c_pp.pl); filcc only builds the reference.
corpus.py runs programs in parallel (--jobs,
default the CPU count). stdout, stderr and exit status are compared
separately; stderr with a Fil-C safety panic and its exit status
normalize to trap. For single-exec tests the reference must
additionally exit 0 and match .c.expected (stdout followed
by stderr; these pinned tests do not interleave the two). Compilation is
bounded to 20 seconds and each execution to 10 seconds; timeouts never
count as passes, even if both executables time out. Each executable gets
a fresh working directory, so file-I/O tests neither write into the
checkout nor share files with the other executable.
The complete expected-results
list is checked in Nix, not just a whitelist of passes. An
added/removed test, a regression, or a newly passing
test fails with a baseline diff and diagnostics.
no-main entries explicitly inventory
function-only/preprocessor/constraint fixtures, which are not executable
programs. Reference compilation failures are distinct from nanofil
rejections. A matching expected failure means the survey is stable,
not that the compiler implements that feature.
Current counts (October 2026)
The checked baseline has 262 passes, no rejections, 7
reference errors and 147 without main. Variable length arrays
and variably modified types (m4.md, "Variable length arrays", 4822471)
turn eight rejections into passes: cproc builtin-vaarg-vm,
cast-vm, func-vla, sizeof-vla,
vla-deref, vla-nested; c-testsuite
00162 ([*] and qualified array parameters) and
00207. Two declarator fixes pass c-testsuite
00209 (int ([4]) as an abstract parameter
declarator) and 00210 (an attribute before *
in an abstract declarator). c-testsuite 00216 passes with
three GNU initializer extensions: range designators
[a ... b], constant compound literals in static
initializers, and designators that name a member of an anonymous member
(C11 6.7.2.1p13 makes those members of the enclosing struct; bbee6cc).
cproc struct-packed passes with GNU packed layout (m4.md,
"Packed structs and unions"), including the [[gnu::packed]]
spelling (59581cd).
c-testsuite 00140 passes with byval struct arguments
(abi-findings.md, Byval arguments), including one past a
prototype's ... (1d1b22c).
The last rejection, c-testsuite 00204, passes with small
float-containing structs by value (SSE eightbytes), aggregate
va_arg, and x87 long double (float.md, x87
long double), whose HFAs it passes by value, returns and reads
through va_arg. Its myprintf calls of more
than 16 words to one variadic callee also needed one weak thunk per word
list (abi-findings.md, Distinct thunks for distinct word lists;
b6c1cc6,
8bf1564,
ab05a5b).
The survey compiles every test in each compiler's default language
mode. nanofil's C23 mode (NANOFIL_STD=c23, which nanofil-cc
sets for -std=c23; 36039eb)
is exercised by tests/c/prog/c23.c and the sed check, not
here.
cproc constraint/c23-6.7.7.3p2-vm-declaration-2 stays a
pass: under C11 a block-scope static pointer to a VLA is
allowed (only a VLA itself may not be static), and nanofil and filcc
both evaluate its size.
Since this baseline, the real-program checks (PLAN.md, M4: xz, make,
coreutils, CPython, bash) drove constructors, thread-local variables,
weak symbols, _Atomic, object alignment, pointer laundering
through integers and atomic read-modify-write (cas(T)).
None of these appears in the two corpora's programs with a
main, so the counts above are unchanged; their tests are
whole programs in tests/c/prog (atomic-rmw.c,
atomic-misaligned.c, c11-atomics.c,
launder.c, object-alignment.c,
constructor.c, thread-local.c,
weak-symbols.c) and refusals in
tests/c/reject.
Floating-point counts (M9)
The baseline then had 243 passes, 19 rejections, 7 reference
errors and 147 without main. Floating-point support adds eleven
passes: cproc conditional, convert-char-float,
logical-and, logical-or,
while-condition; c-testsuite 00113,
00119, 00123, 00174,
00175, 00195. They cover conversions, truth
and comparisons, gradual underflow, variadic float promotion, libm calls
and float fields accessed by pointer. Every newly passing test matches
clang through progtest.sh; the full survey still checks all
other outcomes unchanged.
Historical M4 counts and triage
The remaining sections retain the earlier survey and its diagnostics; the floating-point rejections below are resolved by M9.
Baseline after M4d (struct values) and the array-length fix. The M4c
baseline had pass 19 / 198 / 217, reject 22 / 21 / 43 and wrong result 1
/ 1 / 2; conditional-struct and struct-copy
(M4d) and 00205 (array length) now pass.
| Source | .c files |
No main | With main | Pass | Reject | Wrong result | Reference error |
|---|---|---|---|---|---|---|---|
| cproc | 196 | 147 | 49 | 28 | 14 | 0 | 7 |
| c-testsuite | 220 | 0 | 220 | 203 | 17 | 0 | 0 |
| Total | 416 | 147 | 269 | 231 | 31 | 0 | 7 |
There were no preprocessing, assembler, nanofil-link,
reference-output or timeout failures. Of 38 non-passes, 27 are planned
exclusions, 4 are other gaps, and 7 cannot be compiled by the reference
in its default mode. Each program appears in exactly one primary-cause
group below; secondary features may be hidden behind the first error.
All IDs below are .c basenames, under the named source.
Every C block is a standalone minimal reproduction; diagnostics omit
only the path/line prefix.
Planned exclusions — 27
Floating-point values — 11 rejections
Planned: M4 is integers only, including when real
headers declare floats. cproc: conditional,
convert-char-float, logical-and,
logical-or, while-condition. c-testsuite:
00113, 00119, 00123,
00174, 00175, 00195.
int main(void) { double x = 1; return x; }nanofil: cannot assign int to double. Other members
report loading a float/double value is not supported yet,
cannot convert uchar to float, or
floating point is not supported.
VLAs / variably modified types — 7 rejections (resolved October 2026)
Now passing, with 00162 and
builtin-vaarg-vm; the history below is how it stood before.
PLAN decision 6 rejected VLAs. cproc: func-vla,
sizeof-vla, vla-deref,
vla-nested, cast-vm.
c-testsuite: 00162, 00207.
int main(void) {
int n = 0;
(int (*)[++n])0;
return n;
}All reject. cast-vm was accepted with its size side
effect silently dropped (nanofil exited 0, filcc 1); every variably
modified type name, in casts, sizeof, declarations and
parameters, now fails with
variably modified types (non-constant array size) are not supported
(tests/c/reject/vm_cast.c, vm_pointer.c).
Bitfields — 4 rejections (resolved October 2026)
Now passing: cproc
bitfield-assignment-sign-extend,
bitfield-integer-promotion, bitfield-short;
c-testsuite 00218. The baseline has 248 passes and 14
rejections. The history below is how it stood before.
struct S { unsigned x : 1; };
int main(void) { struct S s; s.x = 1; return s.x; }nanofil:
s has a type nanofil does not support (bitfields). In the
full tests the unsupported bitfield contaminates the enclosing layout
and the diagnostics instead say no member named x/a/common.
Those should name bitfields rather than a missing member. No support
added.
Variadic definitions / va builtins — 3 rejections
Planned: PLAN decision 6; variadic calls
remain supported. cproc: builtin-vaarg-vm; c-testsuite:
00140, 00204.
int f(int n, ...) { return n; }
int main(void) { return f(0); }nanofil: defining variadic function f is not supported.
The other two stop earlier at
__builtin_va_start is not supported, at (.
Fixed since the M4f survey
GNU statement expressions: c-testsuite
00214now passes;00213still rejects its floating-point values. The survey at that stage was 231 passes, 31 rejections, 7 reference errors and 147 without main.Wide literals (cproc
wchar-sign+*×3, c-testsuite00098,00220):L,u,Ucharacter and string literals,u8strings.Compound literals (cproc
conditional-compound-literal, c-testsuite00149,00150): block scope as an unnamed local, file scope as unnamed static data.GNU
c ?: b(cprocconditional-omit) and__builtin_expect(cprocbuiltin-expect).Struct values (M4d): cproc
conditional-struct,struct-copy(wereloading a struct(0) value is not supported yet). This cproc snapshot has no executable struct-argument/return tests; itsstruct-passing*andstruct-return*fixtures have no main.Unsized array length with brace-elided struct initializers: c-testsuite
00205.struct S { int a, b; } s[] = {1, 2, 3, 4};had 4 elements instead of 2; the frontend now counts the entries each element takes, as initialization does. Regression cases are in tests/c/structs.c.
Other gaps — 4
Abstract array parameter declarator — 1 rejection (resolved)
Gap, now passing: c-testsuite 00209.
The earlier complex function declarators parse, but an unnamed
parenthesized array parameter does not.
int f(int ([4]), int);
int main(void) { return 0; }nanofil: expected a parameter declaration, at [.
Attribute inside a function-pointer declarator — 1 rejection (resolved)
Gap, now passing: c-testsuite 00210.
Its earlier packed unions are not used; the first actual failure is a
GNU attribute in a pointer cast, not aggregate ABI lowering.
int f(void) { return 42; }
int main(void) {
void *p = &f;
return ((int(__attribute__((noinline)) *)(void))p)();
}nanofil misbuilds the declarator and rejects a pointer conversion
(the full test:
cannot convert ptr(void) to func(func(int,[],fixed),[ptr(int)],fixed)).
_Generic — resolved
C11 selection now passes c-testsuite 00219, including
qualified pointer targets, lvalue conversion, compatible typedefs,
function/array decay, struct identity and default. cproc
generic.c also compiles, but stays no-main in
the executable survey because it only defines a global.
GNU range designators — 1 rejection (resolved)
Extension gap, now passing: c-testsuite
00216.
int main(void) { int a[2] = {[0 ... 1] = 3}; return a[1]; }nanofil:
GNU range designators are not supported, at int(1,none-dec).
C23 attribute spelling — 1 rejection
Outside the C11 contract, now passing: cproc
struct-packed uses [[gnu::packed]]. Packed
structs themselves are already an intentional rejection in M4's tests;
this particular failure is earlier, at the new attribute syntax.
struct [[gnu::packed]] S { char x; int y; };
int main(void) { return 0; }nanofil: expected a declaration, at struct.
Reference limitations — 7
C23 keywords in filcc's default language mode — 6 reference errors
Not measured nanofil failures. cproc:
alignas-local-strict, alignas-local,
alignas-vla-strict, cast-bool-char,
thread-local,
constraint/c23-6.7.7.3p2-vm-declaration-3. No C23 flags or
compatibility macro rewrites are injected into either compiler.
int main(void) { alignas(16) char x; return 0; }filcc:
call to undeclared function 'alignas'; ISO C99 and later do not support implicit function declarations.
The others use bool without stdbool.h
(use of undeclared identifier 'bool') or
thread_local
(unknown type name 'thread_local'). They stay reference
errors: the survey runs both compilers in their default modes, and
nanofil's C23 mode, added for gnulib, does not change that.
filcc LLVM assertion on typeof of a VM expression — 1 reference error
Reference compiler bug, not investigated in the
toolchain. cproc: typeof-vm.
int main(void) {
int n = 1;
int (*p)[n] = 0;
typeof(typeof(n++, p)) q;
return n;
}filcc -O1:
CodeGenFunction::EmitDeclRefLValue assertion
E->isNonOdrUse() != NOUR_Unevaluated && "should not emit an unevaluated operand"
in clang/lib/CodeGen/CGExpr.cpp:3038. No compiler pin or
runtime changes belong in this milestone.
Follow-ups
- None in the corpus:
00204passes since x87long double(float.md).
Harness validation
All 18 minimal reproductions above were executed through
progtest.sh (including the reduced filcc assertion). A
deliberately wrong baseline (hello.c marked
reject) failed with a reject →
pass diff; restoring it passed the focused survey. A
scratch infinite-loop program produced run-timeout, not
pass, with both executions exiting 124; a 1 ms compile
budget produced compile-timeout. Test 00187
passed with its two fred.txt files isolated in separate
temporary directories. The existing whole-program tests still cover
stderr differences and normalized safety panics, including under GC
scribbling/verification.