WCC C Compiler for x86_64 Linux

April 6, 2026 ยท View on GitHub

This project is an implementation of the full C89/C90 C specification for x86-64 linux. It's mostly based on Engineering a Compiler 2nd Edition. The compiler is self hosting. It's just a hobby, it won't be big and professional like gcc. All of the C89/C90 specification has been implemented. All tests in SQLite's tcltest pass.

The was assembler and wld linker can be used to produce object files by either running configure with --as, --ld or setting the AS and LD environment variables.

This compiler is a hobby project and even close to be ready for production use.

Installation

$ ./configure
$ make install

Running wcc from source

$ ./configure
$ make wcc
$ ./wcc -I include test.c -o test

Implementation

The compiler goes through the following phases:

  • C Preprocessor
  • Hand rolled lexer
  • Precedence climbing parser
  • Simple arithmetic transformations
  • Transformation into SSA
  • Data flow analysis and replacement of variables with live ranges
  • Live range coalescing
  • Some more arithmetic optimizations while converting out of a linear IR into a tree IR
  • Instruction selection using tree pattern matching
  • Live range coalescing (again)
  • Register allocation using top-down graph coloring
  • Code generation using tree tiling

Running the tests

Run all tests

$ make test

Test 3 stage self compilation

$ make test-self-compilation

Run benchmarks

$ make run-benchmark

Compile and test sqlite3

$ CC=.../wcc ./configure
$ make tcltest

Running and testing with musl libc

To run wcc using musl, use --libc musl

To run the tests with musl libc:

GCC=musl-gcc WCC_OPTS="--libc musl" make test

GCC=musl-gcc is necessary so that wcc compiled with gcc and wcc self compiled continue to produce the same result.

Example compilation

#include <stdio.h>

typedef struct s1 {
    int i, j;
} S1;

typedef struct s2 {
    S1 *s1;
} S2;

void main() {
    S2 *s2;

    s2 = malloc(sizeof(S2));
    s2->s1 = malloc(sizeof(S1));
    s2->s1->j = 1;
    printf("%d\n", s2->s1->j);
}
main:
    push        %rbp            # Function prologue
    mov         %rsp, %rbp
    push        %rbx
    subq        \$8, %rsp
    movq        \$8, %rdi        # s2 = malloc(sizeof(S2));
    callq       malloc@PLT
    movq        %rax, %rbx      # rbx = s2
    movq        \$8, %rdi        # s2->s1 = malloc(sizeof(S1));
    callq       malloc@PLT
    movq        %rax, %rcx
    addq        \$8, %rsp
    movq        %rcx, %rax
    movq        %rax, (%rbx)
    movq        %rbx, %rax      # s2->s1->j = 1;
    movq        (%rax), %rax
    movl        \$1, 4(%rax)
    subq        \$8, %rsp        # printf("%d\n", s2->s1->j);
    movq        %rbx, %rax
    movq        (%rax), %rax
    movl        4(%rax), %esi
    leaq        .LS0(%rip), %rdi
    movb        \$0, %al         # printf is variadic, 0 is the number of floating point arguments
    callq       printf@PLT
    addq        \$8, %rsp
    movq        \$0, %rax        # Function exit code zero
    popq        %rbx            # Function epilogue
    leaveq
    retq

Testing

A unpublished hand rolled CI-style runner is used to run automated tests in the above linux distribtions. Fullblown github CI would be good, but considering the hobby state of the project it was deemed unnecessary for now.

To validate the compiler can compile something other than itself correctly, I fixed enough bugs to get sqlite3 to compile and pass the tcltest tests. The latest version of sqlite that compiles and passes its tests is 3.38.5.

Supported linux distributions

wcc was developed on Ubuntu 20.04 in between 2018 and 2021. An effort to support more recent linux distributions was started in March 2025.

Linux distributions where both wcc, wbinutils and sqlite tests pass:

  • Ubuntu 22.04
  • Ubuntu 24.04
  • Ubuntu 25.04
  • debian 13
  • fedora 43*
  • Sqlite tests for version 3.38.5 don't compile on fedora with gcc, so they are skipped.

History

The project started out as a clone of c4 to teach myself to write it from scratch. I then went down a route based on TCC where I wrote a code generator that outputted an object (.o) file. It then quickly became clear that generating object code without using an assembler is a waste of time, so I adapted it to produce .s files and compiled them with gcc. I then proceeded implemeting Sebastian Falbesoner's approach to register allocation. At this point I went academic and started reading Engineering a Compiler 2nd Edition. First SSA transformation, then graph coloring register allocation, then finally instruction selection using tree pattern matching.

After a hiatus I resumed work and fixed a ton of bugs in the instruction selection. I then decided to implement the full C89/C90 specification, starting with the non-preprocessor parts, then the preprocessor.