Skip to content

yzhe819/C-Compiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

37 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Compiling Itself: A C Language Subset Compiler

Which came first β€” the compiler or the source code? This project might just answer that.

English | δΈ­ζ–‡

A self-hosting C subset compiler written in C, inspired by c4. Small enough to compile itself. Complete enough to teach you everything about how a compiler turns characters into execution.


Features

  • Supports int, char, and pointer types
  • Function definitions, calls, and recursion
  • Control flow: if / else, while, return, enum
  • Full unary and binary operators (bitwise, logical, comparison, arithmetic)
  • Array access and type casting
  • Built-in virtual machine that directly interprets bytecode
  • Can compile itself (bootstrapping)

Architecture

Source code (.c file)
        β”‚
        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Lexer           β”‚  next()
β”‚                   β”‚  Tokenizes the character stream
β”‚  Handles:         β”‚
β”‚  identifiers      β”‚
β”‚  numbers          β”‚
β”‚  strings          β”‚
β”‚  keywords         β”‚
β”‚  operators        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Parser                         β”‚  Recursive descent
β”‚                                  β”‚
β”‚  program()                       β”‚  Entry β€” loops over global declarations
β”‚  β”œβ”€ global_declaration()         β”‚  Variables / functions / enums
β”‚  β”‚   β”œβ”€ enum_declaration()       β”‚  Enum constants
β”‚  β”‚   └─ function_declaration()   β”‚
β”‚  β”‚       β”œβ”€ function_parameter() β”‚  Parameter list
β”‚  β”‚       └─ function_body()      β”‚  Function body
β”‚  β”‚           └─ statement()      β”‚  Statements
β”‚  β”‚               └─ expression() β”‚  Expressions (precedence climbing)
β”‚  └─ ...                          β”‚
β”‚                                  β”‚
β”‚  Emits bytecode into text segment as it parses
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Virtual Machine β”‚  eval()
β”‚                   β”‚  Interprets bytecode from the text segment
β”‚  Registers:       β”‚
β”‚  pc / bp / sp / axβ”‚
β”‚                   β”‚
β”‚  Memory segments: β”‚
β”‚  text / data / stack
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Instruction Set

1. Load & Store

Instruction Description
IMM Load immediate value into ax
LEA Load local variable address into ax
LC Load char from address in ax
LI Load int from address in ax
SC Save ax as char to address on stack top
SI Save ax as int to address on stack top
PUSH Push ax onto the stack

2. Arithmetic & Bitwise

Instruction Description
ADD / SUB / MUL / DIV / MOD Arithmetic
OR / XOR / AND Bitwise operations
SHL / SHR Bit shifts
EQ / NE / LT / LE / GT / GE Comparisons

3. Control Flow

Instruction Description
JMP Unconditional jump
JZ Jump if ax == 0
JNZ Jump if ax != 0
CALL Call function (saves return address)
ENT Enter function (create stack frame)
ADJ Adjust stack pointer (clean up arguments)
LEV Leave function (restore frame and return)

4. Native Calls

Instruction Maps to
OPEN open()
CLOS close()
READ read()
PRTF printf()
MALC malloc()
FREE free()
MSET memset()
MCMP memcmp()
EXIT exit()

Symbol Table

During lexing, every identifier is stored in a flat integer array acting as a symbol table:

Each identifier occupies IdSize slots:
[ Token | Hash | Name | Type | Class | Value | BType | BClass | BValue ]

BType, BClass, and BValue temporarily shadow global variable metadata when entering a function scope, and are restored on exit. This is how the compiler handles local variable scoping without heap allocation per scope.


Build & Run

⚠️ This compiler runs in 32-bit mode and requires the -m32 flag. macOS users: Xcode 12+ dropped 32-bit support β€” use Linux or Docker instead.

# Compile the compiler (note the -m32 flag)
gcc -m32 cc.c -o cc

# Or simply use the Makefile
make compile

# Use it to compile a C source file
./cc hello.c

# Enable debug mode (prints each bytecode instruction as it executes)
./cc -d hello.c

Running the tests

# Run the built-in test suite
make test
# Equivalent to: ./cc test.c

Bootstrapping: How It Compiles Itself

The most interesting property of this compiler is that it can compile its own source code:

# One step β€” compile itself and run the tests
make bootstrap
# Equivalent to: ./cc cc.c test.c

# Or step by step:
# Step 1: use system gcc to produce the first binary
gcc -m32 cc.c -o cc

# Step 2: use cc to compile its own source
./cc cc.c

This is the engineering answer to the chicken-and-egg paradox: the very first compiler always has to be bootstrapped by an external tool. After that, it can sustain itself β€” compiling the next version of its own source, indefinitely.

It mirrors how production compilers like GCC and Clang work. The compiler you use today was compiled by yesterday's compiler.


Known Issues

test.c includes three test cases marked as expected failures, all related to how logical operators handle negative numbers:

test(1, c || c);    // failed β€” || gives wrong result when c is negative
test(TRUE, b && c); // failed β€” && mishandles negative operands
test(TRUE, c && c); // failed

The root cause: the VM's JNZ (jump if not zero) instruction does not treat negative integers as truthy, which is non-standard β€” C requires any non-zero value to be true.


Supported Language Subset

// Types
int x;
char c;
int *ptr;

// Control flow
if (x > 0) { ... } else { ... }
while (x > 0) { x = x - 1; }

// Functions
int add(int a, int b) {
    return a + b;
}

// Enums
enum { False, True };

// Pointers & arrays
int arr[10];
*(arr + 2) = 42;

// Sizeof
int size;
size = sizeof(int);

References & Further Reading

Source inspirations

Articles

Books

About

πŸ’­ A mini C compiler written in C β€” can compile itself.

Topics

Resources

License

Stars

3 stars

Watchers

1 watching

Forks

Contributors