7.1 Intermediate Representations: Three-Address Code, Quadruples & Triples
💡 Core Intuition: The 3-Second Mental Model
🍳 The Everyday Analogy: The Universal Esperanto of Compilers
Imagine a global diplomatic summit where delegates speak different languages, and listeners require translations into regional dialects:
- Without an Intermediate Language: You would need direct bilingual translators. If an 11th language is introduced, you must construct 10 brand-new translation channels from scratch!
- With a Universal Intermediate Language (IR): Every speaker translates their speech into one shared bridge language (Esperanto / Universal IR). Then, a backend interpreter translates from Esperanto into each listener's dialect.
- Complexity Drops from to : Adding a new source programming language (e.g. Rust) requires only 1 new frontend. Adding a new CPU architecture (e.g. RISC-V) requires only 1 new backend. All machine-independent code optimizations take place directly on the shared intermediate representation!
💻 Bridging to Computer Science: Linearizing Syntax Trees into 3-Address Instructions
In compiler architecture, Intermediate Code Generation (ICG) acts as the universal pivot between analysis (front-end) and synthesis (back-end):
- It flattens tree-structured Abstract Syntax Trees into a linearized sequence of atomic instructions called Three-Address Code (TAC).
- Each three-address instruction contains at most one operator on the right-hand side, decomposing complex nested expressions using compiler-generated temporary variables.
- This uniform representation is implemented in memory using Quadruples, Triples, or Indirect Triples.
📑Table of Contents
🏗️ Architectural Foundations: The Retargeting Pipeline
The Compiler Retargeting Architecture via Universal IR
Decoupling M source frontends from N target backends
Language-Specific Frontends
C, C++, Rust, Swift, Go, Python frontends parse source code into AST.
Intermediate Representation (IR / TAC)
Canonical machine-independent instruction stream.
Target-Independent Optimizer
Dead code elimination, loop unrolling, constant propagation.
Target Hardware Backends
x86-64, ARM64, RISC-V, WebAssembly machine code generators.
📐 Formal Foundations: Three-Address Code (TAC)
Three-Address Code (TAC) is an abstract linearized instruction set where every instruction has at most one operator on the right-hand side. Consequently, no operation can reference more than three memory locations or registers (two operands and one result).
A complex nested expression like:
is decomposed into a linear sequence of atomic instructions using compiler-generated temporary variables ():
📋 The 8 Standard Forms of Three-Address Statements
-
Binary Assignments:
-
Unary Assignments:
-
Copy Statements:
-
Unconditional Jumps:
-
Conditional Jumps:
-
Procedure Calls & Parameters:
-
Indexed Copy Statements (Array Operations):
-
Address & Pointer Operations:
🏗️ Data Structures: Quadruples, Triples & Indirect Triples
In an actual compiler, TAC instructions are represented in memory using specialized record structures:
Quadruples vs Triples: Structural Tradeoffs
Comparing explicit temporary naming against implicit positional referencing
Quadruples Representation
- •Record with 4 fields: (op, arg1, arg2, result).
- •Explicit temporary variable names (t1, t2) placed in result field.
- •Optimization friendly: instructions can be moved, sorted, and reordered freely without rewriting operands.
- •Requires extra symbol table overhead to store temporary names.
Triples Representation
- •Record with 3 fields: (op, arg1, arg2).
- •Zero explicit temporaries: results are referred to by instruction index (i).
- •Saves memory and eliminates symbol table clutter.
- •Optimization nightmare: moving instruction (i) requires updating every downstream reference to (i).
1️⃣ Quadruples (Quads)
A quadruple is a record with four fields: (op, arg1, arg2, result).
- For unary operators (e.g. ),
arg2is blank ornil. - For unconditional jumps
goto L,op = gotoandresult = L. - For relational branches
if x < y goto L,arg1 = x,arg2 = y,result = L.
2️⃣ Triples
To eliminate the space overhead of creating explicit temporary variable entries in the symbol table, Triples use only three fields: (op, arg1, arg2).
- Any reference to the value computed by line
(i)is made using the integer index(i)inarg1orarg2. - Ternary array stores like require two triple steps:
(0) [ ] x i(computes address)(1) = (0) y(stores value into address)
3️⃣ Indirect Triples
Indirect triples decouple the physical list of triple records from their execution order.
- The compiler maintains an array of pointers
statement[]that point to triple records. - When an optimization pass (e.g. loop-invariant code motion) needs to move an instruction, it only swaps the pointers in
statement[]without changing the internal triple indices!
Indirect Triples Execution Architecture
Permuting instruction order by reordering the pointer array
Order Array: statement[]
statement[0] -> (35), statement[1] -> (12), statement[2] -> (13).
Physical Triples Table
Fixed storage pool where triple locations never physically move.
Hardware Code Emitter
Traverses statement[] in order, emitting native machine instructions.
🏭 Real-World Production Context: How Industry Builds This
In modern industrial production compilers:
LLVM IR is a universal, strongly-typed, three-address language in Static Single Assignment (SSA) form with an infinite virtual register file (%1, %2, %var):
%3 = add nsw i32 %1, %2
%cmp = icmp slt i32 %3, 100
br i1 %cmp, label %if.then, label %if.else
GCC translates high-level Abstract Syntax Trees into Gimple, an internal three-address representation where:
- Complex nested expressions are unnested into at most three operands per statement.
- Control flow is represented as explicit basic blocks with conditional and unconditional gotos.
- High-level control loops (
for,while) are lowered into conditional branches.
📐 Numerical Applications & Worked Examples
Example 1: Full Decomposition into Quads, Triples & Indirect Triples
Consider the high-level assignment statement:
Generate:
- Three-Address Code with minimum temporaries.
- Quadruples table.
- Triples table.
- Indirect Triples table.
Step-by-Step Analytical Solution:
Step 1: Three-Address Code (TAC) Generation Applying common subexpression elimination to avoid recomputing and :
(Without optimization, a naive generator generates 5 temporary variables for the repeated subexpressions).
Step 2: Quadruples Table (Unoptimized Form)
| Index | op | arg1 | arg2 | result |
|---|---|---|---|---|
(0) | uminus | c | — | t1 |
(1) | * | b | t1 | t2 |
(2) | uminus | c | — | t3 |
(3) | * | b | t3 | t4 |
(4) | + | t2 | t4 | t5 |
(5) | = | t5 | — | a |
Step 3: Triples Table
In the triples representation, no temporary names (t1, t2...) appear. Instead, the row index in parentheses represents the computed result:
| Index | op | arg1 | arg2 |
|---|---|---|---|
(0) | uminus | c | — |
(1) | * | b | (0) |
(2) | uminus | c | — |
(3) | * | b | (2) |
(4) | + | (1) | (3) |
(5) | = | a | (4) |
Step 4: Indirect Triples Table
The indirect triples structure maintains a pointer list statement[] pointing to entries in the triples table:
Array statement[]:
statement[0] -> (0)
statement[1] -> (1)
statement[2] -> (2)
statement[3] -> (3)
statement[4] -> (4)
statement[5] -> (5)
If instruction (2) and (3) are hoisted out of a loop, the compiler simply permutes statement[] without modifying any operand references in the physical Triples table.
Example 2: Array Address Dereference TAC Translation
Given the array assignment statement in a C program:
where is a 2D integer array of size stored in row-major order with 4-byte integers. Base address is stored in identifier , and indices are 0-based ().
Write the exact sequence of Three-Address Code instructions generated by the compiler.
Step-by-Step Analytical Derivation:
-
Address Formulation:
-
Row-major offset formula:
-
-
Three-Address Code Generation:
Total: 5 three-address instructions and 4 temporary variables.
🎯 Exam & Interview Pitfall Check
Question 1: "Why are Quadruples preferred over Triples in optimizing compilers?"
Key Focus Points:
- Optimization Freedom: Quadruples explicitly record temporary variable names in the
resultfield. - Code Reordering: When optimization passes (such as loop-invariant code motion or instruction scheduling) move an instruction, quadruple references remain valid without modification. In Triples, moving instruction
(i)alters all downstream positional references, requiring a costly full-table update.
Question 2: "What is the maximum number of operators permitted on the right-hand side of a Three-Address Code statement?"
Key Focus Points:
- Exactly one operator (either one binary operator or one unary operator ).
- Statements like are strictly illegal in TAC and must be split into two atomic operations.
Trap 1: Confusing array load with array store in Triples.
Answer: Subtle semantic difference. An array load produces an evaluatable value and is referenced by downstream instructions. An array store produces no value; it performs a memory mutation and cannot be referenced as an operand.
Trap 2: Assuming SSA form requires infinite hardware registers.
Answer: Conceptual confusion. SSA uses an unbounded number of virtual registers during intermediate optimization. Later, during the Target Code Generation phase, the Register Allocator maps these virtual registers down to the CPU's small fixed physical register file using Graph Coloring.