Fil-C on Nix

Filnix wiki

nanofil/docs/corpus.md

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:

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

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

  1. None in the corpus: 00204 passes since x87 long 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.