tinycc

Fabrice Bellard’s TCC vendored as a cross-platform exerciser for badc: multi-TU compile + link of the tcc-the-binary sources against badc’s supported targets (x86_64 + aarch64 across ELF / Mach-O / PE), followed by a self-host fixed point once every TU compiles.

Source is pinned at the upstream mob branch HEAD (2026-05-13, 757507e). The last release tag (release_0_9_27, 2017) predates years of PE / AArch64 / Mach-O fixes that the cross-platform self-host requires, so the bringup follows mob the same way distros do. Pulled through the badc vendor-deps mirror – see setup.py.

Vendored surface

A multi-TU build of tcc-the-binary against the targets badc supports. Backends and output formats outside that scope are dropped:

Dropped: i386-gen.c / i386-link.c (32-bit x86 – not a badc target), arm-*.c (32-bit ARM – not a badc target), riscv64-*.c, c67-*.c / tcccoff.c (TI C67), il-*.c (orphaned IL backend). ONE_SOURCE is not defined, so each .c is its own TU, which is the shape that exercises badc’s cross-TU linker.

include/ carries tinycc’s shipped system headers (float.h, stdarg.h, stdatomic.h, tccdefs.h, …). They are not needed to compile tcc-the-binary; they are the headers tcc exposes to programs it later compiles, and the self-host stage points the produced tcc at them.

Bringup status

Every TU in the vendored set compiles cleanly through badc on every host profile (Linux x86_64 / aarch64, macOS aarch64, Windows x86_64 / arm64). The smoke harness is gated; a TU regression fails CI.

The self-host fixed point is exercised by self_host.py, which runs five tiers in sequence on every lane:

Tier What it asserts
samples per-TU -c parity (badc-built tcc vs host-cc-built tcc) on 25 small fixtures
corpus per-TU -c parity on tinycc’s own 11-12 TUs
bootstrap tcc-gen2 (host-linked from stage1-compiled TUs) compiles every TU byte-identical
gen2-self tcc-gen2-self (stage1-self-linked) compiles every TU byte-identical to gen2’s output
functional hello-world compile + link + run round trip through stage1 and gen2

Current per-lane state:

Lane samples corpus bootstrap gen2-self functional notes
macOS aarch64 25/25 12/12 12/12 12/12 2/2 full fixed point
Linux x86_64 25/25 11/11 11/11 11/11 2/2 full fixed point
Linux aarch64 25/25 11/11 11/11 11/11 2/2 full fixed point
Windows x86_64 25/25 12/12 12/12 12/12 2/2 full fixed point
Windows arm64 25/25 12/12 12/12 12/12 2/2 full fixed point

All five tiers are strict-gated on every lane.

Already-closed gaps that the bringup surfaced:

config.h

Upstream’s ./configure writes a config.h that picks the target, output format, and search paths. The smoke harness synthesizes a minimal config.h per host instead – it does not invoke configure – selecting macros from this table:

Host Target macros
Linux x86_64 TCC_TARGET_X86_64
Linux aarch64 TCC_TARGET_ARM64
macOS aarch64 TCC_TARGET_ARM64, TCC_TARGET_MACHO
Windows x86_64 TCC_TARGET_X86_64, TCC_TARGET_PE
Windows aarch64 TCC_TARGET_ARM64, TCC_TARGET_PE

CONFIG_TCC_SYSINCLUDEPATHS / CONFIG_TCC_LIBPATHS are baked in per host so the produced tcc resolves <stdio.h> etc. and -lkernel32 / -lmsvcrt etc. without a separate install step:

Host CONFIG_TCC_SYSINCLUDEPATHS (after {B} resolves to demos/tinycc) CONFIG_TCC_LIBPATHS
Linux {B}/include:/usr/include/<triplet>:/usr/include {B}:/usr/lib/<triplet>:/usr/lib
macOS {B}/include:<SDK>/usr/include:/usr/include {B}:<SDK>/usr/lib:/usr/lib
Windows {B}/win32/include;{B}/win32/include/winapi;{B}/include {B}/lib;{B}/win32/lib

CONFIG_TCC_ELFINTERP stays undefined; the self-host stage supplies it via CLI when it links an ELF binary that needs one.

The synthesized config.h also forces CONFIG_TCC_SEMLOCK 0: the upstream default pulls in <dispatch/dispatch.h> on macOS, <semaphore.h> on Linux, and CRITICAL_SECTION on Windows – none of which c5 ships today. tinycc uses the lock only to serialize libtcc state across threads; the bringup compile + self-host fixed point is single-threaded, so the lock is unused here.

Layout

^ To the top