Fil-C as a Nix C/C++ toolchain and cross platform
Python
118
293 commits
updated Sep 19, 2026
Lighttpd demo homepage |
Fil-C thwarting out-of-bounds access |
Figlet font gallery (streaming ASCII art) |
QEMU VM boot sequence |
This repository packages Fil-C (memory-safe C/C++) as Nix derivations. It contains:
Parts 1 and 2 are designed for eventual nixpkgs integration. Part 3 is where we explore what's possible.
The Prolog SaRCAsm prototype explores DCG assembly parsing, explicit memory accesses, and validated read grouping with Trealla. Its first x86-64 slice uses Fil-C for final lowering. See the development plan for staged extensions and the evidence required at each step.
nix run .#sarcasm-prolog -- experiments/sarcasm-prolog/examples/table-entry.s
nix build .#checks.x86_64-linux.sarcasm-prolog
The thread cancellation investigation compares glibc, musl, Cosmopolitan, and FreeBSD libthr, with a reproducible Linux probe, recorded observations, and implications for Fil-C's native syscall boundary.
Fil-C by Filip Pizlo is a memory-safe C/C++ compiler. It prevents use-after-free, buffer overflows, and type confusion through runtime bounds checking and garbage collection - no code changes, no unsafe escape hatches. See fil-c.org and the upstream repo for details on how it works.
See Updating Fil-C sources and ports for the separate core and application pins, source filtering, and update checks.
The filc Cachix cache has prebuilt binaries for the toolchain and many ported packages:
cachix use filc
Building from source on a fresh system means waiting for LLVM to compile, then glibc twice, coreutils, Perl, bash, and so on and on. This can be fascinating and inspiring but you may prefer to use the cache and see results immediately.
The curated cache baseline is defined in shells/baseline.nix. It covers shell utilities, build tools, compression and networking libraries, Bash, Python, Perl, Ruby, Lua, SQLite, Git, OpenSSH, and both OpenSSL variants. Packages outside this baseline may also be cached; missing binaries fall back to building from source.
nix build .#baseline
nix run .#baseline # interactive Fil-C Bash environment
nix run .#baseline -- -c 'python3 --version; sqlite3 --version'
The baseline retains every selected package output, including development headers and libraries, under result/packages/<name>/<output>. Its manifest.json records exact versions and paths. Building it also runs installed-program smoke tests, Python extension checks, and a C consumer of SQLite, libffi, and zlib. The alternative assembly-enabled OpenSSL is available as openssl-sarcasm in the shell.
To build and publish the entire baseline closure:
nix run .#push-baseline -- --max-jobs 4 --cores 8
This uses pinned Nix and Cachix clients, uploads to filc, then verifies every closure path and its content hash against the public Fil-C and official NixOS caches. Authentication comes from your private Cachix configuration or CACHIX_AUTH_TOKEN / CACHIX_SIGNING_KEY; credentials are never part of the derivation. Run from this checkout, or set FILNIX_FLAKE to another checkout's path. For a background build and upload, use swash start -- nix run .#push-baseline -- --max-jobs 4 --cores 8, followed by swash follow <id>.
Please note that the binary cache and the artifacts cached thereupon are provided by yours truly for entertainment purposes only, with absolutely no kind of implicit warranty and no actual security auditing or any kind of rigorous principled approach at all.
This repository packages Fil-C as reproducible Nix derivations. The upstream is a development repo with shell-script builds and 100+ vendored projects. This flake takes a different approach: it's modular (compiler separate from applications), reproducible (hermetic builds, binary caching), and integrates with the ecosystem (port any nixpkgs package via cross-compilation). Memory-safe and regular packages coexist peacefully via /nix/store paths, everything stays moisturized and flourishing.
By treating Fil-C as a cross-compilation target, nixpkgs dependency resolution works automatically. When you port one package, you accidentally port its entire dependency tree.
Transitive dependencies: Want Python with a web stack? Write this:
python3.withPackages (ps: with ps; [
uvicorn starlette msgspec pycairo
networkx pyvis ipython
])
You get 40+ Python packages plus their C dependencies built with Fil-C automatically. CPython itself, pycairo, msgspec, markupsafe - but also cairo, pixman, fontconfig, freetype, libpng, glib, sqlite, gdbm, bzip2, brotli, xz, and the entire X11 graphics stack (libX11, libXext, libXrender, libxcb). You didn't set out to port fontconfig or pixman or libXdmcp - they came along because pycairo needs cairo needs fontconfig needs freetype needs libpng. Check demo/python-web-demo-deps.graphml for the full transitive dependency graph (spoiler: it includes dejavu-fonts-minimal, mailcap, and tzdata).
Sure, to make pycairo work you need a ports.nix entry that disables some tests and adds cairo as a dependency. But once you do, everything that depends on it builds automatically. The demo/app.py web app with SQLite, JSON, graphics rendering, and more builds by listing 8 top-level packages - the transitive closure brings in dozens more.
Language ecosystems: Perl with XS modules works the same way. Port perl once (with tests disabled, some configure flags tweaked), and the demo/perl/ shows JSON::XS, XML::LibXML, DBD::SQLite, Compress::Zlib, and Inline::C all working. Each C extension needs its own ports.nix entry (pin version, apply patch, skip incompatible tests), but once defined, they compose freely.
System composition: Building a container with bash, coreutils, git, OpenSSL, lighttpd, SQLite, Lua, Perl, Python? List the top-level packages, let dependency resolution handle the rest. Everything links against the same memory-safe libc and runtime because it's cross-compilation - not ad-hoc wrappers.
Yes, most packages need some porting work (check ports.nix - it's full of skipTests, configure flags, dependency tweaks). But you do that work once per package, not once per composition. That's the win: porting effort is proportional to the number of unique packages, not the number of ways you combine them.
This repo has three distinct parts with different purposes:
1. Core Toolchain (compiler/, toolchain/, runtime/)
The foundation: packaging the Fil-C compiler (modified LLVM/Clang), binutils, sysroot, runtime libraries (libpas, FUGC), and build wrappers as Nix derivations. This is the "hard" part that transforms upstream fil-c's shell scripts into reproducible Nix builds.
2. Ports Overlay (ports/, ports.nix, pyports.nix)
A clean DSL and overlay machinery that makes nixpkgs packages build with Fil-C. The philosophy: apply the smallest possible changes - pin versions to match upstream fil-c patches, apply those patches, tweak build flags, skip incompatible tests. The DSL (for, pin, patch, etc.) makes porting readable and maintainable.
The ports/patch/ directory contains auto-generated patches extracted from upstream fil-c's monorepo. A script diffs each project subdirectory in upstream's projects/ tree (with filtering logic) and generates patch files. This bridges the gap: upstream vendors entire source trees, we apply patches to nixpkgs packages. As coverage expands, the goal is to need fewer and fewer manual interventions.
3. Playground & Demos (httpd/, virt/, demo/, emacs/, ttyd-demo/)
Integration tests, experiments, and explorations of what memory-safe systems can look like. Web servers with CGI scripts, QEMU VMs, Docker containers, development environments, terminal demos. This is where we test if the toolchain actually works for realistic use cases and where interesting/fun/valuable applications emerge.
Path to Nixpkgs: Parts 1 and 2 are designed for upstream integration. Hacking in this dedicated repo is more convenient and intelligible than working in the massive nixpkgs tree, but the architecture (cross-compilation, minimal patches, overlay structure) is built to merge. If the community finds this valuable, it could mean first-class platform support, Hydra CI, official binary caching, and more. Part 3 stays here as the laboratory.
Fil-C uses Nix's cross-compilation infrastructure (x86_64-unknown-linux-gnufilc0) with a lightly-patched nixpkgs fork that recognizes the gnufilc0 ABI tag. The flake imports nixpkgs with crossSystem.config set and uses replaceCrossStdenv to inject a custom stdenv wrapping the Fil-C compiler. The ports overlay applies patches and configuration to nixpkgs packages, allowing most packages to build with minimal changes. The fork's patches are minimal (gnu-config awareness, ABI tag recognition), making future nixpkgs integration straightforward.
The lighttpd demo runs a complete memory-safe web stack:
nix run .#lighttpd-demo
It's lighttpd compiled with Fil-C, serving CGI scripts in bash and C (both memory-safe), with WebDAV (digest auth), compression (brotli, zstd, bzip2, gzip — all Fil-C). The interactive demo invites you to trigger out-of-bounds access.
The demo.c CGI has an array of 5 fruits. Try ?index=999 — Fil-C traps it instead of crashing:
const char *fruits[] = {"apple", "banana", "cherry", "date", "elderberry"};
// This will trap with Fil-C:
const char *result = fruits[index]; // index=999
The figlet.sh script demonstrates memory-safe bash calling memory-safe figlet, processing results with memory-safe sed/awk, and escaping output with a memory-safe C program.
I'm experimenting with defining a minimal "Linux distribution"
with runit as PID1 and a /bin full of memory safe goodies.
It's not exactly NixOS, but it's fun and might somehow be useful for something.
nix run .#run-filc-docker # Docker container
nix run .#run-filc-sandbox # systemd-nspawn
nix run .#run-filc-qemu # QEMU VM (builds disk image)
Each boots into a curated environment with bash, coreutils, tmux, git, curl, OpenSSL, lighttpd web server, SQLite, Lua, Perl, Prolog, and more — all memory-safe.
Packages ported here that aren't in upstream fil-c:
| Package | Description |
|---|---|
| lighttpd | HTTP server with mod_cgi, WebDAV, compression |
| nethack | Nobody's ascended in Fil-C Nethack yet... |
| kitty-doom | Console DOOM |
| wasm3 | WebAssembly runtime (see CVE demos) |
| trealla | ISO Prolog with Fil-C FFI integration |
| figlet | ASCII art generator |
The ports/analysis.md document is a technical deep-dive generated by squads of Claude AI agents that analyzed every patch from upstream fil-c's 100+ ported projects. Each agent analyzed a subset of patches, then another agent synthesized the findings into a comprehensive guide. It covers systematic porting patterns (pointer tables, tagged pointers, alignment), case studies of major refactors (Python GC, Emacs memory manager, Perl XS), common mistakes and how to avoid them. The scale: 21,000 lines of analyzed changes across projects from dash to systemd. It's both a practical porting guide and a view into what real-world C code assumes about memory.
The ports.nix file uses a clean DSL for porting packages. It's now structured as a list that gets automatically converted to an overlay:
# Simple list-based port definitions
[
# Basic port with version pinning and patches
(for pkgs.zlib [
(pin "1.3" "sha256-/wukwpIBPbwnUws6geH5qBPNOd4Byl4Pi/NVcC76WT4=")
(patch ./ports/patch/zlib-1.3.patch)
])
# Port with explicit name and dependencies
{
lighttpd = for ./ports/lighttpd.nix [
(patch ./ports/patch/lighttpd-6a4880a.patch)
(addCFlag "-Wno-unused-but-set-variable")
];
}
# Port with tests disabled
(for pkgs.sqlite [
(patch ./ports/patch/sqlite.patch)
(skipCheck "TCL test harness needs adaptation")
])
]
The DSL includes helpers for version pinning (pin), patching (patch), configure flags (configure, addCFlag, addCMakeFlag), and test control (skipTests, skipCheck). The ports/default.nix file implements the overlay machinery that converts these declarations into working package overrides.
Python
47.9%
Nix
22.8%
Prolog
10.6%
C
9.9%
Shell
2.5%
JavaScript
2.5%
Emacs Lisp
1.5%
Fil-C as a Nix C/C++ toolchain and cross platform
Python
118
293 commits
updated Sep 19, 2026
Lighttpd demo homepage |
Fil-C thwarting out-of-bounds access |
Figlet font gallery (streaming ASCII art) |
QEMU VM boot sequence |
This repository packages Fil-C (memory-safe C/C++) as Nix derivations. It contains:
Parts 1 and 2 are designed for eventual nixpkgs integration. Part 3 is where we explore what's possible.
The Prolog SaRCAsm prototype explores DCG assembly parsing, explicit memory accesses, and validated read grouping with Trealla. Its first x86-64 slice uses Fil-C for final lowering. See the development plan for staged extensions and the evidence required at each step.
nix run .#sarcasm-prolog -- experiments/sarcasm-prolog/examples/table-entry.s
nix build .#checks.x86_64-linux.sarcasm-prolog
The thread cancellation investigation compares glibc, musl, Cosmopolitan, and FreeBSD libthr, with a reproducible Linux probe, recorded observations, and implications for Fil-C's native syscall boundary.
Fil-C by Filip Pizlo is a memory-safe C/C++ compiler. It prevents use-after-free, buffer overflows, and type confusion through runtime bounds checking and garbage collection - no code changes, no unsafe escape hatches. See fil-c.org and the upstream repo for details on how it works.
See Updating Fil-C sources and ports for the separate core and application pins, source filtering, and update checks.
The filc Cachix cache has prebuilt binaries for the toolchain and many ported packages:
cachix use filc
Building from source on a fresh system means waiting for LLVM to compile, then glibc twice, coreutils, Perl, bash, and so on and on. This can be fascinating and inspiring but you may prefer to use the cache and see results immediately.
The curated cache baseline is defined in shells/baseline.nix. It covers shell utilities, build tools, compression and networking libraries, Bash, Python, Perl, Ruby, Lua, SQLite, Git, OpenSSH, and both OpenSSL variants. Packages outside this baseline may also be cached; missing binaries fall back to building from source.
nix build .#baseline
nix run .#baseline # interactive Fil-C Bash environment
nix run .#baseline -- -c 'python3 --version; sqlite3 --version'
The baseline retains every selected package output, including development headers and libraries, under result/packages/<name>/<output>. Its manifest.json records exact versions and paths. Building it also runs installed-program smoke tests, Python extension checks, and a C consumer of SQLite, libffi, and zlib. The alternative assembly-enabled OpenSSL is available as openssl-sarcasm in the shell.
To build and publish the entire baseline closure:
nix run .#push-baseline -- --max-jobs 4 --cores 8
This uses pinned Nix and Cachix clients, uploads to filc, then verifies every closure path and its content hash against the public Fil-C and official NixOS caches. Authentication comes from your private Cachix configuration or CACHIX_AUTH_TOKEN / CACHIX_SIGNING_KEY; credentials are never part of the derivation. Run from this checkout, or set FILNIX_FLAKE to another checkout's path. For a background build and upload, use swash start -- nix run .#push-baseline -- --max-jobs 4 --cores 8, followed by swash follow <id>.
Please note that the binary cache and the artifacts cached thereupon are provided by yours truly for entertainment purposes only, with absolutely no kind of implicit warranty and no actual security auditing or any kind of rigorous principled approach at all.
This repository packages Fil-C as reproducible Nix derivations. The upstream is a development repo with shell-script builds and 100+ vendored projects. This flake takes a different approach: it's modular (compiler separate from applications), reproducible (hermetic builds, binary caching), and integrates with the ecosystem (port any nixpkgs package via cross-compilation). Memory-safe and regular packages coexist peacefully via /nix/store paths, everything stays moisturized and flourishing.
By treating Fil-C as a cross-compilation target, nixpkgs dependency resolution works automatically. When you port one package, you accidentally port its entire dependency tree.
Transitive dependencies: Want Python with a web stack? Write this:
python3.withPackages (ps: with ps; [
uvicorn starlette msgspec pycairo
networkx pyvis ipython
])
You get 40+ Python packages plus their C dependencies built with Fil-C automatically. CPython itself, pycairo, msgspec, markupsafe - but also cairo, pixman, fontconfig, freetype, libpng, glib, sqlite, gdbm, bzip2, brotli, xz, and the entire X11 graphics stack (libX11, libXext, libXrender, libxcb). You didn't set out to port fontconfig or pixman or libXdmcp - they came along because pycairo needs cairo needs fontconfig needs freetype needs libpng. Check demo/python-web-demo-deps.graphml for the full transitive dependency graph (spoiler: it includes dejavu-fonts-minimal, mailcap, and tzdata).
Sure, to make pycairo work you need a ports.nix entry that disables some tests and adds cairo as a dependency. But once you do, everything that depends on it builds automatically. The demo/app.py web app with SQLite, JSON, graphics rendering, and more builds by listing 8 top-level packages - the transitive closure brings in dozens more.
Language ecosystems: Perl with XS modules works the same way. Port perl once (with tests disabled, some configure flags tweaked), and the demo/perl/ shows JSON::XS, XML::LibXML, DBD::SQLite, Compress::Zlib, and Inline::C all working. Each C extension needs its own ports.nix entry (pin version, apply patch, skip incompatible tests), but once defined, they compose freely.
System composition: Building a container with bash, coreutils, git, OpenSSL, lighttpd, SQLite, Lua, Perl, Python? List the top-level packages, let dependency resolution handle the rest. Everything links against the same memory-safe libc and runtime because it's cross-compilation - not ad-hoc wrappers.
Yes, most packages need some porting work (check ports.nix - it's full of skipTests, configure flags, dependency tweaks). But you do that work once per package, not once per composition. That's the win: porting effort is proportional to the number of unique packages, not the number of ways you combine them.
This repo has three distinct parts with different purposes:
1. Core Toolchain (compiler/, toolchain/, runtime/)
The foundation: packaging the Fil-C compiler (modified LLVM/Clang), binutils, sysroot, runtime libraries (libpas, FUGC), and build wrappers as Nix derivations. This is the "hard" part that transforms upstream fil-c's shell scripts into reproducible Nix builds.
2. Ports Overlay (ports/, ports.nix, pyports.nix)
A clean DSL and overlay machinery that makes nixpkgs packages build with Fil-C. The philosophy: apply the smallest possible changes - pin versions to match upstream fil-c patches, apply those patches, tweak build flags, skip incompatible tests. The DSL (for, pin, patch, etc.) makes porting readable and maintainable.
The ports/patch/ directory contains auto-generated patches extracted from upstream fil-c's monorepo. A script diffs each project subdirectory in upstream's projects/ tree (with filtering logic) and generates patch files. This bridges the gap: upstream vendors entire source trees, we apply patches to nixpkgs packages. As coverage expands, the goal is to need fewer and fewer manual interventions.
3. Playground & Demos (httpd/, virt/, demo/, emacs/, ttyd-demo/)
Integration tests, experiments, and explorations of what memory-safe systems can look like. Web servers with CGI scripts, QEMU VMs, Docker containers, development environments, terminal demos. This is where we test if the toolchain actually works for realistic use cases and where interesting/fun/valuable applications emerge.
Path to Nixpkgs: Parts 1 and 2 are designed for upstream integration. Hacking in this dedicated repo is more convenient and intelligible than working in the massive nixpkgs tree, but the architecture (cross-compilation, minimal patches, overlay structure) is built to merge. If the community finds this valuable, it could mean first-class platform support, Hydra CI, official binary caching, and more. Part 3 stays here as the laboratory.
Fil-C uses Nix's cross-compilation infrastructure (x86_64-unknown-linux-gnufilc0) with a lightly-patched nixpkgs fork that recognizes the gnufilc0 ABI tag. The flake imports nixpkgs with crossSystem.config set and uses replaceCrossStdenv to inject a custom stdenv wrapping the Fil-C compiler. The ports overlay applies patches and configuration to nixpkgs packages, allowing most packages to build with minimal changes. The fork's patches are minimal (gnu-config awareness, ABI tag recognition), making future nixpkgs integration straightforward.
The lighttpd demo runs a complete memory-safe web stack:
nix run .#lighttpd-demo
It's lighttpd compiled with Fil-C, serving CGI scripts in bash and C (both memory-safe), with WebDAV (digest auth), compression (brotli, zstd, bzip2, gzip — all Fil-C). The interactive demo invites you to trigger out-of-bounds access.
The demo.c CGI has an array of 5 fruits. Try ?index=999 — Fil-C traps it instead of crashing:
const char *fruits[] = {"apple", "banana", "cherry", "date", "elderberry"};
// This will trap with Fil-C:
const char *result = fruits[index]; // index=999
The figlet.sh script demonstrates memory-safe bash calling memory-safe figlet, processing results with memory-safe sed/awk, and escaping output with a memory-safe C program.
I'm experimenting with defining a minimal "Linux distribution"
with runit as PID1 and a /bin full of memory safe goodies.
It's not exactly NixOS, but it's fun and might somehow be useful for something.
nix run .#run-filc-docker # Docker container
nix run .#run-filc-sandbox # systemd-nspawn
nix run .#run-filc-qemu # QEMU VM (builds disk image)
Each boots into a curated environment with bash, coreutils, tmux, git, curl, OpenSSL, lighttpd web server, SQLite, Lua, Perl, Prolog, and more — all memory-safe.
Packages ported here that aren't in upstream fil-c:
| Package | Description |
|---|---|
| lighttpd | HTTP server with mod_cgi, WebDAV, compression |
| nethack | Nobody's ascended in Fil-C Nethack yet... |
| kitty-doom | Console DOOM |
| wasm3 | WebAssembly runtime (see CVE demos) |
| trealla | ISO Prolog with Fil-C FFI integration |
| figlet | ASCII art generator |
The ports/analysis.md document is a technical deep-dive generated by squads of Claude AI agents that analyzed every patch from upstream fil-c's 100+ ported projects. Each agent analyzed a subset of patches, then another agent synthesized the findings into a comprehensive guide. It covers systematic porting patterns (pointer tables, tagged pointers, alignment), case studies of major refactors (Python GC, Emacs memory manager, Perl XS), common mistakes and how to avoid them. The scale: 21,000 lines of analyzed changes across projects from dash to systemd. It's both a practical porting guide and a view into what real-world C code assumes about memory.
The ports.nix file uses a clean DSL for porting packages. It's now structured as a list that gets automatically converted to an overlay:
# Simple list-based port definitions
[
# Basic port with version pinning and patches
(for pkgs.zlib [
(pin "1.3" "sha256-/wukwpIBPbwnUws6geH5qBPNOd4Byl4Pi/NVcC76WT4=")
(patch ./ports/patch/zlib-1.3.patch)
])
# Port with explicit name and dependencies
{
lighttpd = for ./ports/lighttpd.nix [
(patch ./ports/patch/lighttpd-6a4880a.patch)
(addCFlag "-Wno-unused-but-set-variable")
];
}
# Port with tests disabled
(for pkgs.sqlite [
(patch ./ports/patch/sqlite.patch)
(skipCheck "TCL test harness needs adaptation")
])
]
The DSL includes helpers for version pinning (pin), patching (patch), configure flags (configure, addCFlag, addCMakeFlag), and test control (skipTests, skipCheck). The ports/default.nix file implements the overlay machinery that converts these declarations into working package overrides.
Python
47.9%
Nix
22.8%
Prolog
10.6%
C
9.9%
Shell
2.5%
JavaScript
2.5%
Emacs Lisp
1.5%