I stumbled upon this talk the other day, Fil-C: Garbage in Memory Safety Out!, in which Filip Pizlo demonstrates his memory safe implementation of C and C++.
It's a really cool concept. The gist of Fil-C is that under the hood it adds two things: Invisible Capabilities (InvisiCaps) and Garbage Collection.
InvisiCaps are extra data that is created alongside pointers that tracks what the pointer is allowed to read and/or write in memory.
The Garbage Collector is Fil's Unbelievable Garbage Collector (FUGC). It does a lot of really cool stuff to keep the program performant while collecting garbage in the background. It makes it so that forgetting to free allocated objects or double freeing is safe, rather than undefined behavior.
Testing it out
I don't write too much C or C++ anymore at the moment. C++ was the very first language I started with, and I wrote tons of little stupid programs with it as a teenager. I don't have many of those around though. They're lost to the sands of time. But I do still have a few programs that I made around when I was a freshman and sophomore in college. At that time the courses mostly used C++. They are:
- AC-Grader
- 2600-Imager
- Pathfinder
AC-Grader
This is a program that takes a txt file, then grades it according to letter grading criteria in the game Animal Crossing for the Game Cube.
I attempted to make write this program according to the rules specified in this Polygon article, but I'm pretty sure I completely messed it up. I don't really care to try to fix it, it's almost a decade old and it doesn't matter. But! For me it makes a great candidate for testing Fil-C because it's so small.
So I compiled it with regular g++ just to test it first, and
$ ./ac-grader.out lorem.txt Individual Grades: Check A: 50 Check B: 99 Check C: 20 Check D: 0 Check E: -20 Check F: -150 Check G: 0 Total Grade: -1 Illegal instruction (core dumped)
That's weird. I don't think I would have just pushed code that did illegal instructions, even a decade ago — and I didn't actually. I booted up a VM of Ubuntu 16.04 just to make sure I wasn't going crazy, and yeah, it compiles just fine.
Turns out the g++ compiler just got stricter. While forgetting to return values has always been undefined behavior, now when your program is compiled with g++, it will crash by default if you forget to return something.
bool grader::close_file()
{
file.close();
}
Oops, should be
bool grader::close_file()
{
file.close();
return true;
}
But that's a good thing to test with Fil-C
$ ./ac-grader.out ./lorem.txt
Individual Grades:
Check A: 50
Check B: 99
Check C: 20
Check D: 0
Check E: -20
Check F: -150
Check G: 0
Total Grade: -1
filc safety error: llvm trap intrinsic
(ac-grader.out) grader.cpp:28:2: grader::close_file()
(ac-grader.out) main.cpp:30:9: main
(libc.so) src/env/__libc_start_main.c:79:7: __libc_start_main
(libpizlo.so) : start_program
[1143455] filc panic: thwarted a futile attempt to violate memory safety.
Trace/breakpoint trap (core dumped)
Pretty neat!
2600-Imager
This too is a pretty simple program. What this does, is it takes an Atari 2600 ROM, reads the individual bits within, and saves an image according to whether a bit is a 1 or a 0. So with this program, with the image that is saved afterwards, a white pixel is a 1 and a black pixel is a 0.
The problem with compiling against Fil-C is that the whole program and all of it's dependencies must be compiled against Fil-C. You cannot link against libraries that are compiled with regular C compilers, the ABI is incompatible because of the InvisiCaps.
But CImg is actually just one header file. So I just downloaded that, threw it into the project directory, and I was good to go. Below are some example images of some game ROM files. The memory reads from left to right, bottom to top (Had to flip the x loop in the code, another oops, oh well).
Dig Dug
Donkey Kong
Pac-Man
Pitfall!
Space Invaders
Pathfinder
Sadly, I'm not going to compile this one. While with the previous program, I could just drop a single header into the project and compile the whole thing. This program requires Xlib, and I'm not going to attempt to compile the whole Xlib library. I can't be bothered to do it. Maybe if someone starts distributing a Fil-C version of Xlib, or Fil-C allows calling regular C libraries, then I'll revisit this. But the maze navigating game I made in college will have to stay dormant.
Which is a real shame, because I'm pretty sure this game caused one of my project teammates to drop out out of college. We were put on a team, and the project for the whole semester was to make some sort of program that you would iterate on throughout the semester, and then present at the end. The one teammate said something like "Hey why don't we just make like Solitaire or something?" and I said "Fuck that, that's boring. Let's make a game with raycasting and pseudo 3D." I was thinking kinda like the old Windows screensaver. So we assigned him the task of making the maze algorithm. Then a couple weeks later he never showed up to class ever again.
Some Thoughts
Everything must be Fil-C
You cannot call into code written in other languages (except for a very very narrow exception made for crypto). If you control your code and all your dependencies, then great! You can just target Fil-C, and everything should compile with minimal or no modifications. If not, then tough shit. Go find the library and compile it yourself (if you even can), or stay out of the kingdom of perfection.
So if you want to compile your Unreal game against Fil-C, then you're out of luck.
And if you want to optimize a hot path in your code, too bad. No speed for you.
Targeting Fil-C is a one way street
I had a realization while Filip was talking about the garbage collector, that if you start using Fil-C as your main target for your C program, you're program will likely eventually only work with Fil-C.
So for example, it says at the end of the page on the garbage collector:
Failing to free an object means the object gets reclaimed for you.
So once you start forgetting to free your objects, your program will work just fine in Fil- C, but could leak memory like crazy compiled with a regular C compiler.
I wrote a small demo to show just that:
#include
#include
#include
#include
void eat_memory();
int main()
{
printf("Running up memory ;-)\n");
while (1) {
eat_memory();
}
return 0;
}
void eat_memory()
{
void *ptr = malloc(1024);
memset(ptr, 0xAA, 1024);
asm volatile("" : : "r"(ptr) : "memory");
}
Compiled against Fil-C, after that eat_memory function exits, the memory will be reclaimed. But in regular C.
Here's a video running that code.
Let me explain what's happening in this video.
There's a tmux session with 5 panes. The top two panes are where I run each compiled program. On the left, the program from above compiled with regular gcc, and on the right, the prorgam compiled with Fil-C.
Below that are the two panes in the middle. They're running the two following commands on the left and right respectively to monitor the memory usage:
watch -t -n 0.5 'ps aux | grep memory-reg-c | grep -v grep | awk "{print \"Regular C using up: \" \$6/1024 \" MB\"}"'
watch -t -n 0.5 'ps aux | grep memory-fil-c | grep -v grep | awk "{print \"Fil-C using up: \" \$6/1024 \" MB\"}"'
Then on the bottom I trigger the programs to run simultaneously in the top two panes with:
tmux send-keys -t 0.0 './memory-reg-c & sleep 1 && echo 500 > /proc/$!/oom_score_adj && fg' Enter & tmux send-keys -t 0.2 './memory-fil-c & sleep 1 && echo 500 > /proc/$!/oom_score_adj && fg' Enter
And I'll break this down too
- tmux send-keys -t 0.0 — Write the following text in the zeroth session's first pane
- './memory-reg-c & sleep 1 — Run the regular C compiled program, put it in the background, and sleep 1 second
- && echo 500 > /proc/$!/oom_score_adj — Put 500 into the memory killer score for the called program. The ID is gotten from $!, which is the program that was just put in the background
- && fg' — Bring the program into the foreground
- Enter & — Press enter on the pane
- tmux send-keys -t 0.2 — Write the following text in the zeroth session's second pane
- './memory-fil-c & sleep 1 — Run the Fil-C compiled program and put it in the background
- && echo 500 > /proc/$!/oom_score_adj — Adjust the memory killer score like before
- && fg' — Bring the program back into the foreground
- Enter — Press enter on the pane
The trick with the background foreground stuff was just to easily get the process ID, so that I could then adjust the memory killer score on it. The reason for adjusting the memory killer score was because I wanted to make sure that the memory hog between these two programs would get killed, and nothing else would get killed along with it.
So once all the runs, you can see in the middle left pane, that the memory shoots up like crazy because I "forgot" to free the memory before the function ends. On the middle right pane you can see the Fil-C compiled program running just fine, because the memory automatically gets reclaimed by the garbage collector.
Final Thoughts
Fil-C might be a good fit for you if:
- You have an existing large C or C++ application that has memory safety issues
- You don't mind a performance drop
- You can compile all the dependencies of your program
- You don't mind being backwards incompatible with regular C or C++ (which if you have abundant memory safety issues, this might not be a bad thing)
If you're starting fresh, you might as well pick a memory safe language that is best suited to your application. In the talk, Filip mentions that the performance ranges from a slightly faster (due to better SIMD support) to a median 4x slower, which tracks closely to the differences between Go and C++ according these computer language benchmarks from Debian. So if you're starting a new project, and you want a compiled garbage collected language, you should probably just use Go.
Thinking about Fil-C makes me also think of Carbon Lang — an experimental successor to C++ that is memory safe. The idea with Carbon is that you'll be able to compile your C++ project with the Carbon compiler with minimal changes, and slowly migrate your memory unsafe C++ code to memory safe Carbon. Either way I hope both succeed, it's nice to have more options out in the ecosystem.
Overall I think Fil-C is a really cool concept. But without having the ability to link against libraries that are compiled with regular C and C++, I think Fil-C is just going to be suitable for niche use cases. For me, it was a fun excuse to crack out some of my programs that I haven't thought about in almost a decade.