Welcome to your first lesson on contributing to the LLVM project! Our goal over this course is to equip you with the knowledge and practical skills to navigate the LLVM ecosystem, understand its internals, and ultimately make meaningful contributions. Your background in C++ and systems programming will be a significant asset as we dive into the low-level details.
This first lesson establishes the foundational mental model for everything that follows. We will deconstruct the architecture of a traditional compiler into its main phases and then map those concepts directly onto the specific tools and components you'll encounter in the LLVM world, like Clang, opt, and llc. Understanding this high-level map is the first and most critical step toward being able to triage bugs and comprehend the structure of the vast LLVM codebase.
1. The Classic Three-Phase Compiler Design
Before we can understand LLVM, we need to understand the problem it's designed to solve. Compilers are typically structured into three main stages or "phases." This modular design is a cornerstone of modern compiler engineering.
For a quick, high-level overview of these phases, let's watch a short segment from the video "Why LLVM is a Game Changer for Compilers."
Why LLVM is a Game Changer for Compilers
This video provides a concise introduction to the key stages of a compiler.
Watch the section that introduces the three traditional stages: frontend, middle-end (optimizer), and backend. This will give you the basic vocabulary for our discussion.
As the video explained, the three phases are:
-
The Frontend: This phase interacts with the source code. It parses the text written in a specific programming language (like C++, Rust, or Fortran), checks for syntax errors, and builds an internal representation of the code, often called an Abstract Syntax Tree (AST). Its job is language-specific.
-
The Optimizer (or Middle-End): This phase takes the intermediate representation from the frontend and performs a series of transformations to improve it. "Improvement" can mean making it run faster, use less memory, or consume less power. Crucially, these optimizations are largely independent of both the original source language and the final target hardware.
-
The Backend: This phase takes the optimized intermediate representation and converts it into machine code that can be executed by a specific CPU architecture (like x86-64 or ARM64). Its job is target-specific.
This separation of concerns is incredibly powerful. To understand why, let's turn to a chapter from "The Architecture of Open Source Applications," which provides a definitive explanation of LLVM's design philosophy.
The Architecture of Open Source Applications (Volume 1)LLVM
This chapter, written by LLVM's creator Chris Lattner, is one of the best available introductions to its architecture. We'll start by focusing on the classical compiler design that motivated LLVM.
Please read the introduction and the first major section, "A Quick Introduction to Classical Compiler Design." Pay close attention to the discussion on retargetability and the benefits of separating the phases. Start from the beginning and read until the end of the section about the social benefits of the design. You can stop at the sentence "lesser known languages" is the last phrase of the intro and then read the section A Quick Introduction to Classical Compiler Design.
As the reading highlights, this design avoids the need to create N*M compilers to support N languages on M targets. Instead, you can write N frontends and M backends that all plug into a common, shared optimizer. This is the key principle that makes projects like GCC and LLVM possible.
2. The LLVM Implementation
LLVM takes this three-phase design and refines it into a well-architected, library-based system. The "lingua franca" that connects LLVM's phases is its Intermediate Representation, or LLVM IR. Every frontend translates its source language into LLVM IR, the optimizer optimizes LLVM IR, and the backend consumes LLVM IR to produce machine code.
The diagram below illustrates this architecture perfectly. You can see various language frontends on the left all producing LLVM IR, which feeds into a common optimizer and then fans out to various target-specific backends on the right.

The secret to LLVM's success is that LLVM IR is a complete, first-class language in its own right. Let's return to the "Architecture of Open Source Applications" chapter to see why this is so important.
The Architecture of Open Source Applications (Volume 1)LLVM
This section explains how LLVM specifically implements the three-phase model and the central role of LLVM IR.
Please read the section titled "LLVM's Implementation of Three-Phase Design." Focus on the subsection about the complete code representation. This passage explains a critical difference between LLVM and other compilers like GCC.
3. Mapping Phases to LLVM Tools
Now we get to the practical part: mapping this theory onto the actual command-line tools you will use as an LLVM developer. While a single command like clang++ my_program.cpp can perform all three phases at once, it's essential to know about the standalone tools that represent each stage.
A fantastic talk from FOSDEM demonstrates these tools in action. We'll watch a few key segments.
Introduction to LLVM Building simple program analysis tools and instrumentation
This talk gives a great practical introduction to the LLVM toolchain. We'll use it to meet the tools for the frontend, optimizer, and backend.
Frontend (clang): Watch the segment from this timestamp which shows how to use clang++ with the -emit-llvm flag. This command tells the frontend to stop after producing LLVM IR and not proceed to the backend. Optimizer (opt): Next, watch the segment that introduces the opt tool, from this timestamp. opt is the standalone driver for the middle-end. It reads LLVM IR, runs a series of optimization passes, and writes out the resulting (hopefully improved) LLVM IR. Backend (llc): Finally, watch the introduction to llc, the LLVM static compiler, from this timestamp. llc is the backend. It takes LLVM IR and compiles it down to target-specific assembly code.
So, we have a clear mapping:
- Frontend:
clang(for C-family languages) - Optimizer (Middle-end):
opt - Backend:
llc
The following diagram visualizes this concrete toolchain flow. The top flow shows the generic compiler model we've been discussing. The middle and bottom flows show how specific LLVM tools (clang, llc) are chained together to accomplish the same task. The opt tool would fit conceptually between the IR and llc stages.

This toolchain—clang -> opt -> llc—is the mental model you should keep in mind. When you are debugging a compiler issue, the first step is often to determine which of these stages is at fault. For example, if the compiler crashes, does it crash in the frontend while parsing, in the optimizer during a transformation, or in the backend during code generation? By using these tools to isolate the stages, you can narrow down the problem significantly.
Conclusion
In this lesson, we've established the fundamental architectural pattern of a modern compiler and mapped it directly to the LLVM project.
Key Takeaways:
- Compilers are traditionally built in three phases: a language-specific frontend, a language/target-agnostic optimizer, and a target-specific backend.
- This design promotes reusability, allowing many languages to target many hardware architectures without reinventing the entire compiler stack each time.
- LLVM implements this design with a set of modular libraries and tools.
- The LLVM Intermediate Representation (IR) is the crucial link connecting these phases.
- The key tools corresponding to each phase are:
- Frontend:
clang(which parses C/C++ into LLVM IR). - Optimizer:
opt(which runs optimization passes on LLVM IR). - Backend:
llc(which converts LLVM IR into assembly code).
- Frontend:
In our next lesson, we will build on this foundation by exploring the benefits of LLVM's specific three-phase design, particularly its nature as a collection of libraries rather than a monolithic program, and see how that enables its use in a wide variety of applications beyond traditional compilers.