Skip to content

Repository files navigation

badc

CI Release License: MIT OS Arch

badc is a small cross-platform optimizing C compiler, and a compiler-as-library, that emits native binaries for five targets from any host. It carries its own linker, DWARF emitter, inline-asm encoder, in-process JIT, and SSA interpreter.

What it can do

Compile the Linux kernel

Every C translation unit of a Linux 7.1.6 defconfig kernel on x86_64 and aarch64 is built by badc in the CI, this is 2921 and 4434 units for the kernel image, 2953 and 10489 with the modules. Both kernels boot, checked by boot markers plus in-kernel procfs/sysfs self-checks. All defconfig modules build and load, with per-module verdicts identical to a gcc-built kernel. badc links the kernel, too, and those kernels boot too as well. The kernel packages as a .deb and an .rpm, installs into stock Debian 13 and Fedora 44 images, and reaches systemd multi-user with modules autoloading, /proc/version names badc.

badc -c foo.S -o foo.o assembles too, and the kernel build uses it: of the defconfig assembly units badc takes 60 of 71 on x86_64 -- the real-mode boot units among them, written out as ELFCLASS32 / EM_386 objects under -m16 / -m32 -- and 71 of 77 on aarch64, gas the rest. ld links nothing: badc makes every link, the 32-bit i386 ones (boot setup, realmode blob, 32-bit vDSO) included, and all three vDSOs are badc-linked, dynamic metadata and symbol versions included. See for more here.

Target five platforms from any host

badc emits Mach-O, ELF, or PE32+ directly:

  • macOS (ARM64),
  • Linux (ARM64, x86_64),
  • Windows ({ARM64, x86_64} x {console, GUI, NT, driver}).

EFI images are supported as well. --freestanding drops the startup runtime.

Ship as one binary

Headers and runtime are embedded; --install writes them to a path to override. There is no ld / lld / link.exe dependency as badc's linker also can stand in for LD= in an existing build. Assembly is supported in standalone files and inline.

Optimize

-O runs SSA passes over a graph-coloring register allocator and produces code faster than clang -O0, especially on ARM64. CI compares against tcc and clang/MSVC on every push.

Emit debug info

-g writes DWARF for lldb / gdb / rr and the profilers. You can set breakpoints, watchpoints, dump the structure layout, all the usual debugging repertoire.

Run C without writing a binary

--jit lowers in-process and calls main directly; --interp runs the SSA IR under a VM that keeps code, stack and data apart and can track every allocation. A .c file with a shebang is directly executable.

Be a library

cargo add badc builds or runs C from your project, on std or on alloc alone.

Demos

demos/ wires each project below as a smoke test; demos/README.md says what each exercises.

  • Language interpreters: Python 3.14 on all five targets, Lua, quickjs, TCL.
  • Systems software: sqlite3, curl, qemu: over a thousand units per target, self-linked, and both qemu-system-aarch64 and qemu-system-x86_64 boot Linux through UEFI firmware to a shell under TCG.
  • Toolchains: chibicc, tinycc, and the nasm / yasm assemblers, each run against its own test suite.
  • Firmware and kernels: edk2: badc compiles the full UEFI firmware from edk2 source into a bootable OVMF / AAVMF image, and the CI boots run under it. kernel is a freestanding preemptive multitasking kernel on both architectures, timer interrupts and context switches included.
  • Cryptography and compression: TweetNaCl, Monocypher, BearSSL, miniz, bzip2.
  • Graphics, math, and the rest: stb, raylib with a Lode Runner game, kissfft, the GUI demos, the Windows driver and NT native binaries, and the cooperative-concurrency libraries (libmill, libdill, coroutines), whose context switches run through inline asm.

Lineage

It started as a Rust port of Robert Swierczek's C compiler in four functions, c4, and diverged enough to call the dialect c5. Hence the c5 module and the C5Error type. c4.c ships as a test fixture and self-hosts:

badc -O -o c4 tests/fixtures/c/c4.c   # compile c4 to a native binary
./c4 hello.c                          # which then runs hello.c

And you can really crank the fun up with something like

badc -O --jit tests/fixtures/c/c4.c tests/fixtures/c/c4.c tests/fixtures/c/c4.c tests/fixtures/c/c4.c

to run it quadro-nested :)

It has since grown from a stack IR through a 3-operand IR to SSA with an optimizing backend, without taking on the pass count of a titan toolchain.

badc used to be bad when the project started out, and the name stuck. There is some compiler-building jargon in this document here and there. You can safely skip it, and jump to the usage section right away. For the true compiler heads there is the --dump-ssa option which prints each function's SSA IR plus the register allocator's per-value placement to stderr before lowering.

Documentation

  • Getting started -- install, first run, flags, debugging, C as a script.
  • Native compilation -- targets, multiple translation units, the linker, headers and bindings, #pragma-driven build flags, the JIT, optimizations.
  • The Linux kernel -- what badc compiles, links and boots, and what it does not.
  • Standard conformance -- implementation-defined choices, divergences from C99, and the C11 / C23 / POSIX / GCC / MSVC extensions implemented.
  • The interpreter -- --interp and the pointer-tracking safety net.
  • Library and no_std -- using badc from Rust.
  • Testing -- the suites, the fixtures, the snapshots, CI.
  • Tools -- the core walker and the assembler-surface probe.

Disclaimer for those legally enlightened

This is a personal educational/research project, it has not been sponsored or suggested by anyone, i.e. it is a product of my own volition. That said, in no event I'll be responsible for how you use this project or what happens due to that. See LICENSE for the exact terms.