Skip to main content

Series

Build a Compiler From Scratch in C++ for x86-64

A step-by-step compiler tutorial in C++: design a language, build a lexer and parser, add semantic analysis, and generate native Linux x86-64 programs.

By Updated 3 min Intermediate

Compiler project written in C++ that translates the Si programming language into native x86-64 executables
On this page

Build a compiler in C++ for Si, a small programming language targeting Linux x86-64. This ten-part series takes you from language design and tokenisation through parsing, type checking, intermediate representation, assembly generation, and optimisation.

Each chapter extends the same project, with explanations, implementation code, and tests. Our backend generates NASM assembly directly; NASM and the system linker turn that output into a native executable.

Start with Chapter 1: How a Compiler Works
Already familiar with compiler stages? Begin the project in Chapter 2.

Chapters

Follow the chapters in order to build the complete project, or choose a topic below to study a particular compiler stage. Chapter-specific source code is linked from the implementation chapters.

  1. 01

    How a Compiler Works: From Source Code to Machine Code

    Learn how a compiler transforms source code into machine code through lexing, parsing, semantic analysis, IR, optimisation and x86-64 code generation.

  2. 02

    How to Design a Programming Language from Scratch

    Design a small statically typed C-style programming language from scratch, including its syntax, types and grammar, then build its compiler in C++ with CMake and Docker.

  3. 03

    How to Build a Lexer in C++: Lexical Analysis and Tokens

    Build a lexer in C++ that converts source code into tokens, recognises keywords and operators, applies maximum munch, and reports useful errors.

  4. 04

    How to Build a Recursive Descent Parser in C++ with an AST

    Build a recursive descent parser in C++ that converts lexer tokens into an AST, handles operator precedence, reports syntax errors, and runs CTest tests.

  5. 05

    Compiler Semantic Analysis: Symbols, Scopes and Types in C++

    Learn semantic analysis in a compiler by building symbol tables, nested scopes and static type checking in C++, with diagnostics and CTest tests.

  6. 06

    How to Build a Compiler IR in C++: Three-Address Code

    Build an intermediate representation in C++. Lower a checked AST into three-address-style instructions with temporaries, variables, basic blocks, branches, loops and function calls.

  7. 07

    How to Build a Compiler Backend in C++: x86-64 Generation

    Build a compiler backend in C++ that translates checked IR into x86-64 NASM assembly with stack frames, arithmetic operations and native Linux executables.

  8. 08

    x86-64 Function Calls in a C++ Compiler: System V ABI and Runtime

    Add System V AMD64 function calls to a C++ compiler with x86-64 argument registers, stack alignment, return values, a NASM runtime and ELF64 linking.

  9. 09

    Compiler Control Flow in C++: x86-64 Branches, Loops and Jumps

    Implement compiler control flow in x86-64 assembly by lowering IR comparisons, conditional branches, if/else statements and while loops.

  10. 10

    Compiler Optimisation in C++: Constant Folding and Dead Code Elimination

    Build an IR optimiser in C++ with constant folding, branch simplification, unreachable-code removal and dead code elimination while preserving behaviour.

What You Will Build

The project connects a C++ compiler, a small assembly runtime, and a command-line toolchain. Together, they translate Si programs such as this one into executable code:

fn multiply(a: int, b: int): int {
    return a * b;
}

fn main(): int {
    let answer: int = multiply(6, 7);
    out(answer);
    return 0;
}

The example calls a function, multiplies two integers, and prints 42. Supporting it requires the compiler to recognise the source text, check its meaning, generate instructions, and link the program with the runtime.

Si provides a manageable set of language features: typed variables, arithmetic, functions, conditions, loops, and output. The focus is on understanding and testing every stage of the implementation.

Find a Specific Topic

If you want to understand…Read
How the compiler stages fit togetherChapter 1: How a Compiler Works
Language syntax, grammar, and project setupChapter 2: Language Design
Turning source characters into tokensChapter 3: Building a Lexer
Operator precedence and abstract syntax treesChapter 4: Recursive Descent Parsing
Symbol tables, scopes, and type checkingChapter 5: Semantic Analysis
Lowering an AST into intermediate instructionsChapter 6: Intermediate Representation
Generating x86-64 assemblyChapter 7: The Compiler Backend
Function calls, the System V ABI, and linkingChapter 8: Functions and Runtime
Implementing branches and loopsChapter 9: Control Flow
Compiler optimisationsChapter 10: Optimisation

Before You Start

You should be comfortable reading C++, using classes and common data structures, and running commands in a terminal. Previous compiler-development experience is not required.

Assembly concepts are introduced as the project reaches code generation. If you would like to practise assembling and linking a small program first, read the x86-64 Assembly Hello World guide.

The project uses:

ToolPurpose
C++20Compiler implementation
CMakeBuilds and test configuration
DockerReproducible Linux development environment
NASMAssembling generated x86-64 code
GNU linkerLinking object files and the runtime
GDBInspecting and debugging programs

Chapter 2 covers the environment and initial project setup.

How to Follow the Series

Work through one chapter at a time, build the changes, and run its tests before continuing. Keep a few small Si programs alongside the compiler so you can inspect how their tokens, syntax trees, intermediate instructions, and assembly change as features are added.

The implementation chapters link to their corresponding versions in the Si compiler repository. Use the version linked from the chapter you are reading to keep the code and explanation aligned.

Begin with Chapter 1 →

Subscribe / Stdin

Get the next part

New parts of From Source to Machine Code land in your inbox the day they publish. No spam, unsubscribe in one click.

We send your address to Kit, our email provider, and ask you to confirm by email before anything is sent. See our Privacy Statement.