Skip to content

Notes by djb on using Fil-C (2025)

News.Ycombinator November 2, 2025

I'm impressed with the level of compatibility of the new memory-safe C/C++ compiler Fil-C (filcc, fil++). Many libraries and applications that I've tried work under Fil-C without changes, and the exceptions haven't been hard to get working.

I've started accumulating miscellaneous notes on this page regarding usage of Fil-C. My selfish objective here is to protect various machines that I manage by switching them over to code compiled with Fil-C, but maybe you'll find something useful here too.

Timings below are from a mini-PC named phoenix except where otherwise mentioned. This mini-PC has a 6-core (12-thread) AMD Ryzen 5 7640HS (Zen 4) CPU, 12GB RAM, and 36GB swap. The OS is Debian 13. (I normally run LTS software, periodically upgrading from software that's 4–5 years old such as Debian 11 today to software that's 2–3 years old such as Debian 12 today; but some of the packages included in Fil-C expect newer utilities to be available.)

Another way to run Fil-C is via Filnix from Mikael Brockman. For example, an unprivileged user under Debian 12 with 10GB of free disk space can download, compile, and install Fil-C, and run a Fil-C-compiled Nethack, as follows:

Current recommendations for things to do at the beginning as root :

I created an unprivileged filc user. Everything else is as that user.

I downloaded the Fil-C source package:

This isn't just the compiler; there's also glibc and quite a few higher-level libraries and applications. There are also binary Fil-C packages, but I've worked primarily with the source package at this point.

I compiled Fil-C and glibc:

There are also options to use musl instead of glibc, but musl is incompatible with some of the packages shipped with Fil-C: attr needs basename , elfutils needs argp_parse , sed 's test suite needs the glibc variant of calloc , and vim 's build needs iconv to be able to convert from CP932 to UTF-8.

I had originally configured the server phoenix with only 12GB swap. I then had to restart ./build_all_fast_glibc.sh a few times because the Fil-C compilation ran out of memory. Switching to 36GB swap made everything work with no restarts; monitoring showed that almost 19GB swap (plus 12GB RAM) was used at one point. A larger server, 128 cores with 512GB RAM, took 8 minutes for Fil-C plus 6 minutes for musl, with no restarts needed.

Fil-C includes a ./build_all_slow.sh that builds many more libraries and applications (sometimes with patches from the Fil-C author). I wrote a replacement script with the following differences:

On phoenix , running time PATH="$ /bin:$ /fil-c/build/bin:$ /fil-c/pizfix/bin:$PATH" ./build-parallel.py went through 61 targets in 101 minutes real time (467 minutes user time, 55 minutes system time), successfully compiling 60 of them.

libcap. This is the one that didn't compile: / /filc/fil-c/pizfix/bin/ld: /usr/libexec/gcc/x86_64-linux-gnu/14/liblto_plugin.so: error loading plugin: libc.so.6: cannot open shared object file: No such file or directory

util-linux. I skipped this one. It does compile, but the compiled taskset utility needs to be patched to use sched_getaffinity and sched_setaffinity as library functions rather than via syscall , or Fil-C needs to be patched for those syscalls. This is an issue for build-parallel since build-parallel relies on taskset ; maybe build-parallel should instead use Python's affinity functions.

attr, bash, benchmarks, binutils, bison, brotli, bzip2, bzip3, check, cmake, coreutils, cpython, curl, dash, diffutils, elfutils, emacs, expat, ffi, gettext, git, gmp, grep, icu, jpeg-6b, libarchive, libcap, libedit, libevent, libpipeline, libuev, libuv, lua, lz4, m4, make, mg, ncurses, nghttp2, openssh, openssl, pcre2, pcre, perl, pkgconf, procps, quickjs, sed, shadow, simdutf, sqlite, tcl, tmux, toybox, vim, wg14_signals, xml_parser, xz, zlib, zsh, zstd. No problems encountered so far (given whatever patches were already applied from the Fil-C author!). The benchmarks package is supplied with Fil-C and does a few miscellaneous measurements.

I did export PATH="$ /bin:$ /fil-c/build/bin:$ /fil-c/pizfix/bin:$PATH" before these.

boost 1.89.0 : Seems to mostly work. Most of the package is header-only; a few simple tests worked fine.

I also looked a bit at the compiled parts. Running ./bootstrap.sh --with-toolset=clang --prefix=$ ran into vfork , which Fil-C doesn't support, but editing tools/build/src/engine/execunix.cpp to use defined(__APPLE__) || defined(__FILC__) for the no-fork test got past this.

Running ./b2 install --prefix=$ toolset=clang address-model=64 architecture=x86_64 binary-format=elf produced an error message since I should have said x86 instead of x86_64 ; Fil-C said it caught a safety issue in the b2 program after the error message: filc safety error: argument size mismatch (actual = 8, expected = 16) . I didn't compile with debugging so Fil-C didn't say where this is in b2 .

cdb-20251021 : Seems to work. One regression test, an artificial out-of-memory regression test, currently produces a different error message with Fil-C: filc panic: src/libpas/pas_compact_heap_reservation.c:65: pas_aligned_allocation_result pas_compact_heap_reservation_try_allocate(size_t, size_t): assertion page_result.result failed.

libcpucycles-20250925 : Seems to work. I commented out the first three lines of cpucycles/options .

libgc : I replaced this with a small gcshim package ( ) that simply calls malloc etc. So far this seems to be an adequate replacement. (Fil-C includes a garbage collector.)

libntruprime-20241021 : Seems to work after a few tweaks but I didn't collect full notes yet. chmod +t crypto_hashblocks/sha512/avx2 disables assembly and makes things compile; configured with --no-valgrind since Fil-C doesn't support valgrind; did a bit more tweaking to make cpuid work.

lpeg-1.1.0: Compiles, maybe works (depends on lua, dependency of neovim): cd PREFIX=$(dirname $(dirname $(which lua))) wget tar -xf lpeg-1.1.0.tar.gz cd lpeg-1.1.0 make CC=`which filcc` DLLFLAGS='-shared -fPIC' test cp lpeg.so $PREFIX/lib

luv-1.51.0: Compiles, maybe works (depends on lua, dependency of neovim): cd PREFIX=$(dirname $(dirname $(which lua))) wget tar -xf luv-1.51.0-1.tar.gz cd luv-1.51.0-1 mkdir build cd build LUA_DIR=$ /fil-c/projects/lua-5.4.7 # lua install should probably do this: cp $LUA_DIR/lua.h $PREFIX/include/ cp $LUA_DIR/lauxlib.h $PREFIX/include/ cp $LUA_DIR/luaconf.h $PREFIX/include/ cp $LUA_DIR/lualib.h $PREFIX/include/ # and then: cmake -DCMAKE_C_COMPILER=`which filcc` -DCMAKE_INSTALL_PREFIX=$PREFIX -DWITH_LUA_ENGINE=Lua -DLUA_DIR=$ /fil-c/projects/lua-5.4.7/ .. make test make install

mutt-2-2-15-rel (depends on ncurses): wget tar -xf mutt-2-2-15-rel.tar.gz cd mutt-mutt-2-2-15-rel CC=`which clang` ./prepare --prefix=$ /fil-c/pizfix --with-homespool make -j12 install Seems to work, at least for reading email.

tig (depends on ncurses and maybe more): wget tar -xf tig-2.6.0.tar.gz cd tig-2.6.0 CC=`which filcc` ./configure --prefix=$(dirname $(dirname $(which git))) make -j12 make test make -j12 install Seems to work, at least for viewing the Fil-C repo.

w3m (depends on gcshim and ncurses): Seems to work. I tried the Debian version: git clone . I used CFLAGS=-Wno-incompatible-function-pointer-types (which is probably needed for clang anyway even without Fil-C).

I've built and installed some replacement Debian packages using Fil-C as the compiler on a Debian 13 machine, as explained below. Hopefully this can rapidly scale to many packages, taking advantage of the basic compile-install-test knowledge already built into Debian source packages, although some packages will take more work because they need extra patches to work with Fil-C.

Structure. Debian already understands how to have packages for multiple architectures (ABIs; Debian "ports") installed at once. For example, dpkg --add-architecture i386; apt update; apt install bash:i386 installs a 32-bit version of bash, replacing the usual 64-bit version; you can do apt install bash:amd64 to revert to the 64-bit version. Meanwhile the 32-bit libraries and 64-bit libraries are installed in separate locations, basically /lib/i386-linux-gnu or /usr/lib/i386-linux-gnu vs. /lib/x86_64-linux-gnu or /usr/lib/x86_64-linux-gnu . (On Debian 11 and newer, and on Ubuntu 22.04 and newer, /lib is symlinked to /usr/lib .)

I'm following this model for plugging Fil-C into Debian: the goal is for apt install bash:amd64fil0 to install a Fil-C-compiled ( amd64fil0 ) version of bash, replacing the usual ( amd64 ) version of bash, while the amd64 and amd64fil0 libraries are installed in separate locations.

The include-file complication. Debian expects library packages compiled for multiple ABIs to all provide the same include files: for example, /usr/include/ncurses.h is provided by libncurses-dev:i386 , libncurses-dev:amd64 , etc. This is safe because Debian forces libncurses-dev:i386 and libncurses-dev:amd64 and so on to all have the same version. An occasional package with ABI-dependent include files can still use /usr/include/x86_64-linux-gnu etc.

Fil-C instead omits /usr/include in favor of a Fil-C-specific directory (which will typically be different from /usr/include : even if Fil-C is compiled with glibc, probably the glibc version won't be the same as in /usr/include ). This difference is the top source of messiness below. I'm planning to tweak the Fil-C driver to use /usr/include on Debian. [This is done in the filian-install-compiler script.]

Something else I'm planning to tweak is Fil-C's glibc compilation, so that it uses the final system prefix. [This is also done in the filian-install-compiler script.] The approach described below instead requires / /filian/fil-c to stay in place for compiling and running programs. Building Debian packages. How does Debian package building work? First, more packages to install as root: apt install dpkg-dev devscripts docbook2x \ dh-exec dh-python python3-setuptools fakeroot \ sbuild mmdebstrap uidmap piuparts Debian has multiple options for building a package. The option that has the best isolation, and that Debian uses to continually build new packages for distribution, is sbuild, but for fast development I'll focus on directly using the lower-level dpkg-buildpackage. Baseline 1: using sbuild without Fil-C. In case you do want to try sbuild, here's the basic setup, and then an example of building a small package (tinycdb): mkdir -p ~/shared/sbuild time mmdebstrap --include=ca-certificates --skip=output/dev --variant=buildd unstable ~/shared/sbuild/unstable-amd64.tar.zst mkdir -p ~/.config/sbuild cat ~/.config/sbuild/config.pl $chroot_mode = 'unshare'; $external_commands = { "build-failed-commands" => [ [ '%SBUILD_SHELL' ] ] }; $build_arch_all = 1; $build_source = 1; $source_only_changes = 1; $run_lintian = 1; $lintian_opts = ['--display-info', '--verbose', '--fail-on', 'error,warning', '--info']; $run_autopkgtest = 1; $run_piuparts = 1; $piuparts_opts = ['--no-eatmydata', '--distribution=%r', '--fake-essential-packages=systemd-sysv']; EOF mkdir -p ~/shared/packages cd ~/shared/packages apt source tinycdb cd tinycdb-*/ time sbuild Baseline 2: using dpkg-buildpackage without Fil-C. Here's what it looks like compiling the same small package with dpkg-buildpackage: mkdir -p ~/shared/packages cd ~/shared/packages apt source tinycdb cd tinycdb-*/ time dpkg-buildpackage -us -uc -b The goal: Using dpkg-buildpackage with Fil-C. As root, teach dpkg basic features of the new architecture, imitating the current line amd64 x86_64 (amd64|x86_64) 64 little in the same file: echo amd64fil0 x86_64+fil0 amd64fil0 64 little >> /usr/ /dpkg/cputable

Building Debian packages. How does Debian package building work? First, more packages to install as root:

Debian has multiple options for building a package. The option that has the best isolation, and that Debian uses to continually build new packages for distribution, is sbuild, but for fast development I'll focus on directly using the lower-level dpkg-buildpackage.

Baseline 1: using sbuild without Fil-C. In case you do want to try sbuild, here's the basic setup, and then an example of building a small package (tinycdb): mkdir -p ~/shared/sbuild time mmdebstrap --include=ca-certificates --skip=output/dev --variant=buildd unstable ~/shared/sbuild/unstable-amd64.tar.zst mkdir -p ~/.config/sbuild cat ~/.config/sbuild/config.pl $chroot_mode = 'unshare'; $external_commands = { "build-failed-commands" => [ [ '%SBUILD_SHELL' ] ] }; $build_arch_all = 1; $build_source = 1; $source_only_changes = 1; $run_lintian = 1; $lintian_opts = ['--display-info', '--verbose', '--fail-on', 'error,warning', '--info']; $run_autopkgtest = 1; $run_piuparts = 1; $piuparts_opts = ['--no-eatmydata', '--distribution=%r', '--fake-essential-packages=systemd-sysv']; EOF mkdir -p ~/shared/packages cd ~/shared/packages apt source tinycdb cd tinycdb-*/ time sbuild

Baseline 2: using dpkg-buildpackage without Fil-C. Here's what it looks like compiling the same small package with dpkg-buildpackage: mkdir -p ~/shared/packages cd ~/shared/packages apt source tinycdb cd tinycdb-*/ time dpkg-buildpackage -us -uc -b

The goal: Using dpkg-buildpackage with Fil-C. As root, teach dpkg basic features of the new architecture, imitating the current line amd64 x86_64 (amd64|x86_64) 64 little in the same file:

Also, allow apt to install packages compiled for this architecture (beware that this will also later make apt update look for that architecture on servers, and whimper a bit for not finding it, but nothing breaks):

Also, teach autoconf to accept amd64fil0 (the third of these lines is what's critical for Debian builds):

[Not necessary if you've used filian-install-compiler :] As a filian user, compile Fil-C and its standard library:

[Not necessary if you've used filian-install-compiler :] As root, copy Fil-C and its standard library into system locations:

Now, as user filian (or whichever other user), let's make a little helper script to adjust a Debian source package:

Explanation of the differences from a normal build:

For me this worked and produced three ../*.deb packages. Installing them as root also worked:

Compiling a deliberately wrong test program with the newly installed library also works, and triggers Fil-C's run-time protection:

libc-dev. Some packages depend on libc-dev, so let's build a fake libc-dev package (probably there's an easier way to do this):

As root, install the above libraries:

libmd. Seems to work. At first this didn't install since the compiled version (for amd64fil0) was 1.1.0-2 while the installed version (for amd64) was 1.1.0-2+b1. Debian requires the same version number across architectures (see above regarding include-file compatibility), so apt said that 1.1.0-2+b1 breaks 1.1.0-2. I resolved this by compiling and installing 1.1.0-2 for both amd64 and amd64fil0. This is a downgrade since "+b" refers to a "binNMU", a "binary-only non-maintainer upload", a patch beyond the official source; I don't know what the patch is.

readline. Needs ln -s /usr/include/readline /usr/include/x86_64+fil0-linux-gnu/readline after installation. Could have tweaks in debian/rules (which seems to predate *.install ), but this is in any case an example of the messiness that I'm planning to get rid of.

lua5.4. Seems to work. Depends on readline.

Extracted Entities

Domains (2)