Filip Pizlo recently released a (solo!) project called Fil-C that adds a memory-safety instrumentation pass to clang (for spatial safety – out-of-bounds accesses), along with a runtime support library and a concurrent GC (for temporal safety – use after free). It is, by the standards of such tools, highly compatible with existing code – so much so that building a full linux distro userspace seems likely within reach with only modest patching effort. The stated performance overheads (measured by Dan Bernstein at “about 1-4x cycles”) are by modern standards “probably tolerable” for many workloads, especially stuff that’s IO bound or not otherwise straining f…
Filip Pizlo recently released a (solo!) project called Fil-C that adds a memory-safety instrumentation pass to clang (for spatial safety – out-of-bounds accesses), along with a runtime support library and a concurrent GC (for temporal safety – use after free). It is, by the standards of such tools, highly compatible with existing code – so much so that building a full linux distro userspace seems likely within reach with only modest patching effort. The stated performance overheads (measured by Dan Bernstein at “about 1-4x cycles”) are by modern standards “probably tolerable” for many workloads, especially stuff that’s IO bound or not otherwise straining for maximum performance.
I’m happy to see this work exist. It builds on a long line of academic and industrial work in this space (that Pizlo happily cites), including his own many years of iteration on the subject while at Apple. If I understand correctly, some of those earlier iterations are already in production in security sensitive code. I recall talking with Pizlo about these prototypes when I was at Apple too, and I’m pleased to see the work maturing to its current state.
(He also makes an interesting point that the bounds checking Fil-C inserts can make pointer-twiddling C code safer than pointer-twiddling unsafe Rust. This seems likely true! And it would be interesting to know if there’s a way to have the best of both worlds, eg. if his instrumentation pass could be adapted to compile otherwise-full-speed optimized unsafe Rust blocks with a little bit of systematic compiler-injected bounds checking, perhaps derived from Rust’s strict pointer provenance? Obviously this wouldn’t be appealing for folks who use unsafe blocks for speed, but I think a lot are for other reasons and might enjoy an extra layer of checks. This is well beyond anything I know anymore, sadly I’ve long since lost track of what rustc can or can’t do. Just speculating, but it seems to me that most unsafe Rust code doesn’t allocate or free or interact with an allocator at all, so you’d want to drive it from something other than allocator, could probably still omit the GC.)
Naturally Fil-C has some caveats (if we’re comparing to Rust, say, or other PLs with restrictions on mutable aliasing):
-
It’s not going to statically prevent any of the errors it prevents; it’s strictly dynamic. So your programs will still crash on memory errors. But almost all programs have paths that crash, and perhaps the density of crashes will be tolerable.
-
In addition to the stated performance overheads there will be a space overhead, as deferring frees until the GC is sure they’re garbage (unreachable) will retain that garbage for a while. On most GCs the amount of memory spent on garbage is tunable: make the GC work more often and there’s less retained garbage, but typical GC tuning will put the overhead at 1.5x-2x the memory. I haven’t measured Fil-C-compiled code at all to see what its overheads are here. Also computers have a lot of memory these days.
-
Related: it’s not going to do anything for memory leaks. You can also leak memory in Rust by tying Rc-and-RefCell in a knot, but you kinda have to go out of your way to do it. A missed free() in C is fairly common. Less so in C++ that’s using shared_ptr everywhere. YMMV.
-
It’s not going to do anything much to solve data races or help with local reasoning for correctness. Preventing mutable aliasing has additional correctness advantages beyond being a tool for memory safety. Fearless concurrency remains out of reach. But perhaps it’s less fearful since you’ll only crash or get data corruption.
-
There’ll be a big obvious switch you can flip – compile without Fil-C – to turn the safety back off everywhere to make the program faster and use less memory. There will be a lot of temptation from bosses who like to see better numbers to flip that switch. But perhaps bosses in 2025 are safety conscious enough to leave it on.
In any event, I only mention those caveats because they’re the sort of thing that motivated languages like Rust in the first place. There have been memory-safe, bounds-checked and GC’ed AOT-compiled languages for a long time! And I like them! I’m happy to code in Haskell or OCaml or SBCL or Modula-3 or Java or C# or whatever. The main problem motivating Rust was that there was an audience of developers who wouldn’t accept those PLs for their use cases. People were very very attached to their C/C++ performance and memory-usage envelopes. It’s weird! Like the gap between C/C++ and the next-fastest safe PL has never been especially huge, it’s never anything like the performance gaps between different generations of hardware. But it persists across time, and it’s been enough for decades to sustain the “we have to be unsafe” argument.
If times have changed and people are now mostly ok with the caveats and will throw the switch to turn safety on, I’m super happy for that to be true! Code that fails more-safely on memory errors is a great thing for human civilization. For people who have huge legacy C/C++ codebases with no ability or desire to rewrite, or even are writing anew but feel constrained to avoid (or just don’t like) safer PLs, I hope Fil-C meets their needs. If at some point (say) there’s an easy-to-install Debian distro built with this, I’ll probably use it.