Compiler 2018:
Big Picture
Lequn Chen
March 16, 2017
Why Compilers Course
• Improve programming skills
• ~10k loc
• Very important project experience in your CV
• programming skills
• perseverance
What Are Compilers
Compiler
Source Language Target Language
gcc
C Linux x86-64 ELF
javac
Java JVM Bytecode
your compiler
M* Linux x86-64 Assembly
What Are Virtual Machines
• Interpreter
• JIT Optimization focused on Code Generation
Java JVM on x86-64 Linux
Kotlin JVM on x86-64 Windows
JVM Bytecode
Scala JVM on x86-64 macOS
Clojure JVM on Embedded Systems
• Language Features
• Optimization based on High Level Semantics
not Low-Level Virtual Machine anymore
LLVM
• Code Generation
• Transforms and Optimizations
C/C++ x86-64
Rust RISC
LLVM IR
Swift ARM
Haskell MIPS
• Language Features
• Optimization based on High Level Semantics
not Low-Level Virtual Machine anymore
LLVM
• Code Generation
Front-end • Transforms and Optimizations
C/C++ x86-64
Back-end
Rust RISC
LLVM IR
Swift ARM
Haskell MIPS
• Language Features
• Optimization based on High Level Semantics
Compilers
• Overview
• Front-end → Intermediate Representation → Back-end
• More detail?
• Lexing
• Parsing
• Semantic Analysis
• IR Generation
• IR Optimization
• Code Generation
• Target-dependent Optimizations
About the Course
• Language: whatever you want
• Lexing and parsing library: whatever you want
• Source language: M* (C-and-Java-like)
• Target platform: Linux x86-64 Assembly in NASM
• Additional language features: whatever you want
• as long as it is compatible with the manual
• Optimizations: whatever you want
• as long as you can pass the tests
Lexing & Parsing
Source Code
while f3 < 100 {
f3 = f1 + f2;
f1, f2 = f2, f3;
}
Lexing
while f3 < 100 {
f3 = f1 + f2;
f1, f2 = f2, f3;
}
KEYWORD while
IDENTIFIER f3
SYMBOL <
LITERAL 100
SYMBOL {
IDENTIFIER f3
SYMBOL =
IDENTIFIER f1
SYMBOL +
IDENTIFIER f2
SYMBOL ;
IDENTIFIER f1
SYMBOL ,
IDENTIFIER f2
SYMBOL =
IDENTIFIER f2
SYMBOL ,
IDENTIFIER f3
SYMBOL ;
SYMBOL }
Parsing
while f3 < 100 {
f3 = f1 + f2;
f1, f2 = f2, f3;
}
KEYWORD while Abstract Syntax Tree
IDENTIFIER f3 WHILE
SYMBOL <
EXPRESSION BODY
LITERAL 100
SYMBOL { < STATEMENT STATEMENT
IDENTIFIER f3
SYMBOL = f3 100 ASSIGN UNPACKING
IDENTIFIER f1
SYMBOL + f3 EXPRESSION TARGET SOURCE
IDENTIFIER f2 + f1 f2 f2 f3
SYMBOL ;
IDENTIFIER f1 f1 f2
SYMBOL ,
IDENTIFIER f2
SYMBOL =
IDENTIFIER f2
SYMBOL ,
IDENTIFIER f3
SYMBOL ;
SYMBOL }
Syntax Error
while f3 < 100 {
f3 = f1 + f2;
f1, f2 = f2, f3;
}
KEYWORD while Abstract Syntax Tree
IDENTIFIER f3 WHILE
SYMBOL <
EXPRESSION BODY
LITERAL 100
SYMBOL { <
IDENTIFIER f3
SYMBOL = f3 100 ???
IDENTIFIER f1
SYMBOL +
IDENTIFIER f2
SYMBOL ;
IDENTIFIER f1
SYMBOL ,
IDENTIFIER f2
SYMBOL = Syntax Error: Expect loop body
IDENTIFIER f2
SYMBOL ,
IDENTIFIER f3
SYMBOL ;
SYMBOL }
Syntax Error
while f3 < 100 {
f3 = f1 + f2;
f1, f2 = f2, f3;
}
KEYWORD while Abstract Syntax Tree
IDENTIFIER f3 WHILE
SYMBOL <
EXPRESSION BODY
LITERAL 100
SYMBOL { < STATEMENT STATEMENT
IDENTIFIER f3
SYMBOL = f3 100 ASSIGN UNPACKING
IDENTIFIER f1
SYMBOL + f3 EXPRESSION TARGET SOURCE
IDENTIFIER f2 + f1 f2 f2 f3
SYMBOL ;
IDENTIFIER f1 f1 f2
SYMBOL ,
IDENTIFIER f2
SYMBOL = Syntax Error: Missing }
IDENTIFIER f2
SYMBOL ,
IDENTIFIER f3
SYMBOL ;
SYMBOL }
Parsing: Grammars
stmt: expr NEWLINE
| ID '=' expr NEWLINE
| NEWLINE
;
expr: <assoc=right> expr op='^' expr
| expr op=('*'|'/') expr a = 2
| expr op=('+'|'-') expr
| INT b = 3
| ID c = 4 + a * b
| '(' expr ')'
d = 5 - c * (3 + b) / a
MUL : '*';
DIV : '/';
e = a ^ (b + 10) ^ c ^ d
ADD : '+';
SUB : '-';
ID : Letter LetterOrDigit*
fragment Letter: [a-zA-Z_]
fragment Digit: [0-9]
fragment LetterOrDigit: Letter | Digit
NEWLINE: '\r'? '\n'
WS : [ \t]+ -> skip
Parsing: Grammars
Parsing: Grammars
Expr → Expr + Term Expr → Term Expr’
| Expr - Term Expr’ → + Term Expr’
LL ✘ LL ✔
| Term | - Term Expr’
LR ✔ | Ɛ LR ✔
Term → Term * Factor
| Term / Factor Term → Factor Term’
Term’ → * Factor Term’
Factor → ( Expr ) | / Factor Term’
| Integer | Ɛ
Factor → ( Expr )
| Integer
def Expr():
Expr()
match('+') Infinite Recursion!
Term()
Lexer & Parser?
• Usually, lexer and parser can be completely separated.
• However,
• vector<pair<int, int>>
Pragmatic Solution
• What to do
• Build AST
• Check syntax errors
• Use parser generators, especially, ANTLR 4
• Check https://github.com/antlr/grammars-v4
• Read if you want
• https://abcdabcd987.com/using-antlr4/
• https://abcdabcd987.com/notes-on-antlr4/
Challenge Yourself
• Hand-written lexer and parser
• Check Parsing Techniques: A Practical Guide
Semantic Analysis
Semantic Error
while f3 < 100 {
f3 = f1 + f4;
f1, f2 = f2, f3;
}
KEYWORD while Abstract Syntax Tree
IDENTIFIER f3 WHILE
SYMBOL <
EXPRESSION BODY
LITERAL 100
SYMBOL { < STATEMENT STATEMENT
IDENTIFIER f3
SYMBOL = f3 100 ASSIGN UNPACKING
IDENTIFIER f1
SYMBOL + f3 EXPRESSION TARGET SOURCE
IDENTIFIER f2 + f1 f2 f2 f3
SYMBOL ;
IDENTIFIER f1 f1 f4
SYMBOL ,
IDENTIFIER f2
SYMBOL = Semantic Error: f4 used before declaration
IDENTIFIER f2
SYMBOL ,
IDENTIFIER f3
SYMBOL ;
SYMBOL }
Language Features
• x, y = y, x
•c = sum(x * y for x in a for y in b)
• a.sort(key=lambda x: x[0])
Pragmatic Solution
• What to do
• Walk the AST tree
• Build symbol table
• Check all kinds of semantic errors
Challenge Yourself
• Add features to the language
• unpacking
• list comprehension
• lambda
• lifetimes
•…
IR Generation
IR: What & Why
• Intermediate Representation
• Focus less on the source language
• Pay more attention to the target platform
• Most of transformation and analysis are done in IR
IR Design
• IR design is closely related to
• Source language
• Target machine
• Transforms / Analysis
IR: Multiple Levels
• A compiler can use more than one IR, and of course, there
are more than one level.
• HIR/MIR: Carry more information. May have type system
similar to the source language. Higher level analysis &
transforms can be performed on. (Alias analysis works
better with type knowledge)
• point1.x => (LoadField point1 “x”)
• LIR: Closer to the target machine. Don’t have much type
information (General/FP Reg). Focus on code generation.
• point1.x => (LoadMem (Mem baseAddr 4))
IR: Multiple Levels
• A compiler can use more than one IR, and of course,
there are more than one level.
• LLVM: Low Level Virtual Machine
• Actually, its level is not that low.
• And it happens that LLVM use a single representation.
• The more information you own, the more chances you
have to do analysis and transforms.
LLVM IR
• LLVM: almost keep everything!
struct RT {
char A;
%struct.RT = type { i8, [10 x [20 x i32]], i8 }
int B[10][20];
%struct.ST = type { i32, double, %struct.RT }
char C;
};
define i32* @foo(%struct.ST* %s) {
struct ST {
entry:
int X;
%arrayidx = getelementptr inbounds %struct.ST,
double Y;
%struct.ST* %s, i64 1, i32 2, i32 1, i64 5, i64 13
struct RT Z;
ret i32* %arrayidx
};
}
int *foo(struct ST *s) {
return &s[1].Z.B[5][13];
}
IR Design: Structure
• Tree (the Tiger Book)
• ✘ I cannot understand it
• ✘ Hard to analyze and transform
• Linear (the Dragon Book)
• ✘ Hard to analyze and transform
• Control Flow Graph
• ✔ Easy to build CFG IR
• ✔ Further analysis and transformations need CFG
Control Flow Graph
if x > y
if x > y:
z = x
foo()
z = x z = y
else: foo() bar()
z = y
bar()
print(z)
print(z)
• Node: Basic Block
• BB: Straight-line piece of code without any jumps or jump targets
• Directed Edge: Jumps
Design: Memory Model?
• Memory-to-Memory
• Reg Alloc: What should be keep in registers?
• ✘ Lots of students wasted lots of time on it
• Register-to-Register:
• Unlimited virtual register
• Reg Alloc: What should be spilled to memory?
• ✔ Easy to understand
• ✔ Similar to the target platform
Design: Function?
• Should the “function” and “function call” concept
present in IR?
• I’m strongly in favor of it
• ✔ Simplify things
• Function call doesn’t split basic block
• In optimization’s language, “global” means inside a
function, not the whole program.
Debugging
• I printed my IR in LLVM’s format and run
• Painful!
• No direct memory arithmetic!
• I wrote my own interpreter
• https://github.com/abcdabcd987/LLIRInterpreter
• Life is much more easier!
Pragmatic Solution
• What to do
• Walk the AST tree
• Generate IR
• IR Design
• Use CFG IR. Don’t use tree IR or linear IR.
• Use register-to-register memory model
• Check senior students’ design for reference, for example
• https://github.com/abcdabcd987/LLIRInterpreter
Challenge Yourself
• Design your own IR
• Read for your reference if you want:
• https://speakerdeck.com/abcdabcd987/
compiler2016-by-abcdabcd987
Optimizations
Optimizations
• Loop optimizations
• Loop unrolling • Code generator optimization
• Software pipelining
• Register allocation
• Data-flow optimizations • Instruction selection
• Common subexpression • Instruction scheduling
elimination
• Constant folding and • Others
propagation • Dead code elimination
• Inlining
• SSA-based optimizations
• Global value numbering • …
• Sparse conditional constant
propagation
Register Allocation
• Register-to-register IR: infinite virtual registers
• Real machine: limited number of registers
• Register allocation: map virtual registers to real registers
• Spilling: which virtual registers should move to memory
Register Allocation
• Linear scan algorithm
• ✔ Sounds easier?
• ✔ Allocate faster
• ✘ Slightly worse run time
• Graph coloring algorithm
• ✘ Liveness analysis
• ✘ Write more lines of code
• ✔ Better run time performance
• ✔ Actually, not hard at all. Way simpler than lots of OI/ACM
algorithms.
Pragmatic Solution
• What to do
• Analyze and transform IR
• Graph coloring register allocation
• Inlining
Challenge Yourself
• Try all kinds of optimizations
Code Generation
Pragmatic Solution
• What to do
• Transform IR to target machine assembly
• Do it in a naïve way
Challenge Yourself
• Dive further into x86-64
• Instruction selection
• Instruction scheduling
Standard Library
Pragmatic Solution
• Use libc
Challenge Yourself
• Write your own standard library
• Write your own heap memory allocator
Wrap Up
Pragmatic Solution
• Use ANTLR 4. Imitate existing ANTLR 4 grammars.
• Use CFG IR. Use register-to-register model.
• Use graph coloring register allocation.
• Use libc
• Talk to classmates, TAs, senior students
• Ask for help. Don’t plagiarize others’ code.
Challenge Yourself
• And help others