Fil's Unbelievable Garbage Collector
Algorithm & Design
- Fil-C compiles C into a memory-safe dialect using “InvisiCaps” capabilities and a concurrent, non‑moving, Dijkstra‑barrier GC (FUGC).
- The collector is precise/accurate: LLVM is instrumented to emit stack maps and metadata so the GC knows exactly where pointers are, rather than scanning conservatively.
- Pointer–integer casts are allowed but “laundered”: if a pointer goes through an integer and is stored, it loses its capability; later dereference traps instead of becoming a hidden root.
- GC uses a grey-stack, concurrent marking and a sweeping phase based on bitvector SIMD; dead objects can be reclaimed via bitmaps without touching their contents.
- Stack scanning is bounded by stack depth; in practice, participants argue typical stacks are small enough that scans are rarely the main bottleneck.
Performance, Overheads & Latency
- Reported slowdowns range widely:
- Some programs near 1×, many in the ~2–4× band.
- Pathological cases observed: ~9× and up to ~49× for STL/SIMD‑heavy or QuickJS workloads (a recent bug made unions particularly bad; fixing it improved one QuickJS case ~6×).
- Author’s rough targets: worst‑case ~2×, average ~1.5×, with SIMD/array‑heavy code close to 1× and pointer‑chasey tree/graph code >2×.
- Memory overhead is estimated around 2× for GC alone, with additional overhead from capabilities; code size is currently very bloated (guessed ~5×) due to unoptimized metadata and ELF constraints.
- Debate on “perf‑sensitive” C/C++:
- One side: most user‑facing C/C++ could be 2–4× slower without noticeable UX impact, especially IO‑bound tools.
- Other side: many domains (compilers, browsers, games, DAWs, scientific computing, embedded) would notice even 2×.
- Latency: FUGC is concurrent; pauses are limited to safepoint callbacks (poll checks) and stack scans. For latency‑critical workloads (games, audio, hard real‑time), this may still be unacceptable; ideas like end‑of‑frame safepoints are mentioned but not implemented.
Use Cases, Compatibility & Adoption
- Fil-C has been used to run substantial existing C code (e.g., shells, interpreters, build tools, editors). It can “just work” on many real programs, sometimes uncovering bugs.
- Strong fit: large legacy C where rewrites are unlikely and absolute peak performance isn’t critical; poor fit: embedded/hard real‑time, JITs (no PROT_EXEC), current 32‑bit targets.
- Some see it as evidence that “memory-safe C” for existing code is practically achievable, providing an existence proof against claims that such systems can’t work.
Safety Model vs Alternatives
- Capability system is designed to be thread‑safe without pervasive atomics/locks.
- Undefined‑behavior‑driven optimizations are disabled via LLVM flags and patches; the compiler runs a controlled opt pipeline before instrumentation, aiming for “garbage in, memory safety out”.
- Compared with:
- Lock‑and‑key temporal checking: would miss Fil‑C’s thread‑safety properties.
- RLBox: provides sandboxing but not memory safety inside the sandbox; some argue performance comparisons are still relevant, others say they solve different threat models.
- Rust: can still rely on
unsafe; Fil‑C is positioned as slower/heavier but with no escape hatches.
GC vs Ownership Philosophy
- One camp calls GC an “evolutionary dead end” and prefers compile‑time resource management with no runtime cost.
- Others counter:
- All memory management has costs (runtime, compile‑time and cognitive).
- Tracing GC is often faster than malloc/free or refcounting under some workloads, at the cost of more memory.
- For complex, cyclic, graph‑like structures, tracing GC is very natural; simulating it with ownership+RC can be more complex and sometimes slower.
- Broad agreement that this is all about tradeoffs, not absolutes.