CS Engineering Gyan

Phases of a Compiler

When you write a single line of code like total = price * quantity; and run your program, that short statement quietly travels through an entire pipeline before it ever becomes something the processor can execute. It gets scanned, checked, restructured, verified for meaning, simplified, optimized, and finally rewritten into instructions the hardware understands. None of this happens in one giant leap. Instead, a compiler breaks the job into a series of clearly defined phases, each with its own responsibility, its own input, and its own output.

Understanding these phases is arguably the single most important step in learning Compiler Design, because almost every other topic in the subject, from parsing techniques to code optimization strategies, fits neatly into one of these stages. Once you have a clear mental picture of the pipeline a program travels through, the rest of the subject starts to feel like filling in details rather than learning something completely new each time.

In this tutorial, you will learn what the phases of a compiler are, how each phase transforms the program, the difference between the analysis phase and the synthesis phase, the role of the symbol table and error handler that support every phase, and how these phases work together using a running example. By the end, you will be able to trace a simple statement all the way from source code to target code in your head.


Why a Compiler is Divided into Phases

It would technically be possible to build a single, giant program that reads source code and directly spits out machine code without any clear internal structure. In practice, however, no serious compiler is built this way, because a monolithic design would be extremely difficult to develop, test, debug, and extend.

Dividing the compiler into distinct phases brings several practical advantages. Each phase can be designed, implemented, and tested somewhat independently of the others, since it only needs to worry about transforming one specific representation of the program into the next. This separation also makes it easier to reuse parts of a compiler, retarget it to new hardware, or add new source languages without rewriting the entire system from scratch. This same idea of splitting a large problem into smaller, well-defined stages is a recurring theme throughout computer science, and Compiler Design is one of the clearest examples of it in action.


The Six Phases of a Compiler

A typical compiler is organized into six main phases. Each phase takes the output of the previous phase as its input, performs a specific transformation or check, and passes its own output forward to the next phase.

Phase Input Output
Lexical Analysis Raw source code as a stream of characters. A stream of tokens representing keywords, identifiers, operators, and literals.
Syntax Analysis Stream of tokens from lexical analysis. A parse tree or syntax tree representing the grammatical structure of the program.
Semantic Analysis Parse tree from syntax analysis. An annotated syntax tree checked for meaning-related correctness, such as type consistency.
Intermediate Code Generation Annotated syntax tree from semantic analysis. A simplified, machine-independent intermediate representation of the program.
Code Optimization Intermediate representation from the previous phase. An improved, more efficient version of the same intermediate representation.
Code Generation Optimized intermediate representation. Final target code, typically assembly or machine instructions for a specific processor.

Let us walk through each of these phases in more detail, using a small running example so the transformations feel concrete rather than abstract.


Phase 1: Lexical Analysis

Lexical analysis is the very first phase a compiler performs, and it is handled by a component usually called the lexical analyzer or scanner. Its job is to read the source program character by character and group those characters into meaningful chunks called tokens. A token might represent a keyword, an identifier, a numeric literal, an operator, or a punctuation symbol.

During this phase, the lexical analyzer also strips out anything that is not meaningful to later phases, such as whitespace and comments, so that the rest of the compiler does not need to worry about formatting details.

Example

Source Code:
total = price * quantity;

Tokens Produced:
IDENTIFIER(total)  ASSIGN_OP  IDENTIFIER(price)  MULT_OP  IDENTIFIER(quantity)  SEMICOLON

Internally, lexical analyzers are usually built using concepts from automata theory, since the patterns that define valid tokens, such as what counts as a valid identifier or a valid number, can be described using regular expressions and recognized using finite automata.


Phase 2: Syntax Analysis

Once the source program has been broken into tokens, the syntax analyzer, often called the parser, takes over. Its job is to check whether the sequence of tokens follows the grammatical rules of the programming language and to organize them into a tree-like structure called a parse tree or syntax tree.

If the tokens do not follow a valid grammatical pattern, for example, if an operator is missing or a statement is not properly terminated, the syntax analyzer detects this as a syntax error and reports it, usually along with the approximate location in the source code where the problem was found.

Example

Tokens:
IDENTIFIER(total)  ASSIGN_OP  IDENTIFIER(price)  MULT_OP  IDENTIFIER(quantity)  SEMICOLON

Simplified Syntax Tree:

        =
       / \
   total   *
          / \
      price  quantity

This tree makes the structure of the statement explicit. It shows that the assignment operator sits at the top, with the variable being assigned on one side and a multiplication expression on the other, which itself has two operands.


Phase 3: Semantic Analysis

Passing syntax analysis only guarantees that a program is structurally valid, not that it actually makes sense. Semantic analysis is the phase responsible for checking meaning-related correctness, using the syntax tree produced in the previous phase along with information stored in the symbol table.

Common checks performed during this phase include verifying that variables are declared before they are used, confirming that operations are performed on compatible data types, and checking that function calls match the expected number and type of arguments. When a check fails, the compiler reports a semantic error, even though the code may have looked perfectly valid from a purely grammatical point of view.

Example

int price;
char quantity;

total = price * quantity;

In this example, multiplying an integer by a character type might be flagged or automatically adjusted depending on the language rules, since semantic analysis is where such type-related decisions and checks take place.


Phase 4: Intermediate Code Generation

After a program has been verified for both structure and meaning, the compiler generates an intermediate representation, a simplified form of the program that sits conceptually between the original source language and the final target machine code. This representation is designed to be easy to analyze and transform, while still being general enough that it does not depend on any particular processor.

A commonly used form of intermediate representation is three-address code, where each instruction involves at most three operands, making the structure of computations very explicit.

Example

Three-Address Code:
t1 = price * quantity
total = t1

Notice how the single source statement has been broken down into two simple steps, each performing exactly one operation. This uniform, simplified structure makes it much easier for the compiler to reason about and improve the program in the next phase.


Phase 5: Code Optimization

Code optimization takes the intermediate representation and improves it, aiming to make the final program run faster, use less memory, or consume fewer resources, all without changing what the program actually computes. Optimization can happen at a local level, examining a small block of instructions at a time, or at a more global level, analyzing the flow of an entire function or program.

Some common optimization techniques include eliminating calculations whose results are never used, avoiding recomputation of values that do not change, and simplifying expressions that can be evaluated more efficiently. It is worth emphasizing that optimization must always preserve the original meaning of the program. A compiler is never allowed to change what a program computes in the name of making it faster.

Example

Before Optimization:
t1 = price * quantity
t2 = t1 + 0
total = t2

After Optimization:
t1 = price * quantity
total = t1

Here, an unnecessary addition of zero has been removed, since it has no effect on the final result. Real compilers apply many such transformations, often far more sophisticated than this simple example, across the entire intermediate representation.


Phase 6: Code Generation

The final phase of a compiler is code generation, where the optimized intermediate representation is translated into the actual target code, typically assembly language or machine instructions specific to a particular processor. This phase must take into account details that earlier, more abstract phases could safely ignore, such as the specific registers available on the target machine, memory addressing modes, and instruction formats.

Example

Target Code:
MOV R1, price
MUL R1, quantity
MOV total, R1

At this point, the program is finally expressed in a form that can be assembled and executed directly by the target machine, completing the journey that began with a single readable line of source code.


Analysis Phase vs Synthesis Phase

The six phases described above are commonly grouped into two broader stages, which provides a useful high-level way of thinking about compiler structure.

Aspect Analysis Phase (Front End) Synthesis Phase (Back End)
Phases Included Lexical analysis, syntax analysis, and semantic analysis. Intermediate code generation, code optimization, and code generation.
Primary Goal Understand the source program and verify that it is correct. Use that understanding to construct efficient target code.
Dependency Depends on the rules of the source programming language. Depends on the architecture of the target machine.
Reusability Can often be reused across different target machines for the same language. Can often be reused across different source languages for the same target machine.

This front end and back end separation is one of the most powerful ideas in Compiler Design, since it allows compiler builders to mix and match different front ends and back ends rather than building an entirely new compiler from scratch for every combination of source language and target machine.


Supporting Components: Symbol Table and Error Handler

Alongside the six main phases, two components operate continuously throughout the entire compilation process rather than belonging to a single phase.

The symbol table is a data structure used to record information about every identifier in the program, including variable names, function names, their types, and their scope. It is created and updated starting from the earliest phases and is consulted repeatedly by later phases, particularly semantic analysis and code generation, whenever information about an identifier is needed.

The error handler is responsible for detecting, reporting, and sometimes recovering from problems encountered at any phase, whether it is an invalid character during lexical analysis, a grammar violation during syntax analysis, or a type mismatch during semantic analysis. A well-designed error handler tries to continue processing after an error where possible, so that a single mistake does not prevent the compiler from reporting other unrelated issues in the same run.


Common Mistakes Beginners Make

Mistake Correct Understanding
Assuming all six phases must always run one after another in strict sequence with no overlap. While the logical order is fixed, real compilers often interleave phases for efficiency, such as generating tokens on demand as the parser requests them.
Believing that syntax analysis alone is enough to guarantee a program is correct. Syntax analysis only checks structure. Semantic analysis is still required to catch meaning-related errors.
Thinking optimization is a single step rather than an entire phase with many techniques. Code optimization includes a wide variety of techniques, applied at different levels, all aimed at improving efficiency without changing program behavior.
Overlooking the symbol table and error handler as unimportant side components. These two components are used throughout nearly every phase and are essential to how a compiler actually functions in practice.

Frequently Asked Interview Questions

  1. What are the six phases of a compiler?
    The six phases are lexical analysis, syntax analysis, semantic analysis, intermediate code generation, code optimization, and code generation.
  2. Which phases belong to the analysis phase of a compiler?
    Lexical analysis, syntax analysis, and semantic analysis together make up the analysis phase, also known as the front end.
  3. Which phases belong to the synthesis phase of a compiler?
    Intermediate code generation, code optimization, and code generation together make up the synthesis phase, also known as the back end.
  4. What is the difference between a parse tree and an intermediate representation?
    A parse tree represents the grammatical structure of the source program, while an intermediate representation is a simplified, machine-independent form used for analysis and optimization after semantic checks are complete.
  5. Why is the symbol table important during compilation?
    It stores information about identifiers, such as their type and scope, and is referenced repeatedly by later phases like semantic analysis and code generation.
  6. Can code optimization change the output of a program?
    No, a valid optimization must always preserve the original meaning and output of the program while improving factors like speed or memory usage.
  7. Why is a compiler divided into multiple phases instead of being built as a single step?
    Dividing the compiler into phases makes it easier to design, test, maintain, and reuse individual components, and allows different phases to be improved or replaced independently.

Summary

The phases of a compiler describe the orderly journey a program takes from readable source code to executable target code. Lexical analysis groups characters into tokens, syntax analysis arranges those tokens into a valid grammatical structure, semantic analysis checks that structure for meaning, intermediate code generation produces a simplified representation, code optimization improves that representation, and code generation produces the final target code. Supporting all of this, the symbol table tracks identifier information and the error handler catches problems along the way.

In this tutorial, you learned why compilers are divided into phases, what each of the six phases does with a running example, how those phases group into the analysis and synthesis stages, and the role played by the symbol table and error handler throughout the process. With this pipeline clearly in mind, you are ready to explore the first phase in depth, starting with how lexical analysis actually breaks source code into tokens.


← Previous: Introduction to Compiler Design Next: Lexical Analysis →

Home Visit Our YouTube Channel