1. FROM SOURCE TO BINARY
How Compiler Works
From source to binary
從原始碼到二進制 Luse Cheng
ソースからバイナリへ Sep 13, 2012 / 新竹碼農
Von Quelle Zu Binären
Luse Cheng
De source au binaire
Jim Huang
Desde fuente a binario March 31, 2011 / 北科大
Binarium ut a fonte
2. 自我介紹
經歷
Compiler Eng. at MediaTek Inc. (2011~Present)
Compiler Lead at Andes Technology Corporation (2011~2011)
Compiler Eng. at Andes Technology Corporation (2007~2011)
國民革命軍軍人 (Andes Technology Corporation)
從事 Compiler / Toolchain 研究, 開發
從事 Android 開發
從事 Open Source 工作 (GCC … etc)
專門打雜的 Compiler 工程師 (MediaTek Inc.)
應徵的時候是 LLVM 工程師
事實上是專門打雜, Debug 的 Compiler 工程師
6. 從原始碼到二進制:
常見的誤解
從原始碼到二進制,其實不只需要 Compiler
真相: 需要很多 Tool 同心協力一起完成 (Toolchain)
真正的Compiler 其實只做下面的事…
軟體 硬體
Source Code HDL Source
A compiler is a computer
program (or set of programs)
Design Compiler (DC) that transforms source code
Compiler (gcc, llvm)
RTL Compiler (RC) written in a programming
language (the source language)
Selecting Technology into another computer
Instruction Mapping language (the target language,
often having a binary form
Assembly Netlist known as object code)
source : http://en.wikipedia.org/wiki/Compiler
11. 如果有人想當你的程式碼
那你怎樣才能做她的編譯器 (Compiler) ?
什麼是編譯器?
Source Language ->
Target Language
Recognizer: 讀懂她
Translator: 轉譯她
Optimization: 調教她
High Level Language
Compiler
Optimized Low Level Language
12. 編譯器小歷史:
First compiler (1952)
第一個編譯器
A-0, Grace Murray Hopper
First complete compiler (1957)
第一個完整的編譯器
Fortran, John Backus (18-man years) Grace Hopper, inventor of
A-0, COBOL, and the
First multi-arch compiler (1960) term “compiler.”
第一個多平台編譯器
Cobol, Grace Murray Hopper el al.
The first self-hosting compiler (1962)
第一個能自己編譯自己的編譯器
LISP, Tim Hart and Mike Levin
Image source:
http://www-history.mcs.st-and.ac.uk/PictDisplay/Hopper.html
http://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Grace_Hopper.jpg/300px-Grace_Hopper.jpg
13. 先有雞還是先有蛋
先有C語言還是先有C編譯器
一個有趣的故事:
Ken Thompson : Reflections on Trusting Trust
(Compiling a compiler)
Cross-compiling and Boot-strapping
用X語言 (like Fortran or ASM) 寫一個C-的編譯器
用C-寫一個C的編譯器
用 C 寫一個C的編譯器
16. while (y < z) {
int x = a + b;
y += x;
} How Compiler Works
T_While
T_LeftParen 原始碼
T_Identifier y
T_Less 字彙分析 (正規語言)
T_Identifier z
T_RightParen 語法分析 (Context-Free Grammar)
T_OpenBrace
T_Int 語法樹
T_Identifier x
T_Assign
語意分析 (Type Checking ..etc)
T_Identifier a
T_Plus
中間語言
T_Identifier b
T_Semicolon
T_Identifier y
最佳化
T_PlusAssign
T_Identifier x 目標語言
T_Semicolon
T_CloseBrace
http://www.stanford.edu/class/cs143/
17. while (y < z) {
int x = a + b;
y += x;
} How Compiler Works
原始碼
字彙分析 (正規語言)
語法分析 (Context-Free Grammar)
語法樹
語意分析 (Type Checking ..etc)
中間語言
最佳化
目標語言
http://www.stanford.edu/class/cs143/
18. while (y < z) {
int x = a + b;
y += x;
} How Compiler Works
原始碼
字彙分析 (正規語言)
語法分析 (Context-Free Grammar)
語法樹
語意分析 (Type Checking ..etc)
中間語言
最佳化
目標語言
http://www.stanford.edu/class/cs143/
19. while (y < z) {
int x = a + b;
y += x;
} How Compiler Works
原始碼
Loop: x=a+b
y=x+y 字彙分析 (正規語言)
_t1 = y < z
if _t1 goto Loop
語法分析 (Context-Free Grammar)
x=a+b 語法樹
Loop: y=x+y
_t1 = y < z 語意分析 (Type Checking ..etc)
if _t1 goto Loop
中間語言
add $1, $2, $3
loop: add $4, $1, $4 最佳化
slt $6, $1, $5
beq $6, loop
目標語言
http://www.stanford.edu/class/cs143/
20. 真實世界中的編譯器
GCC, the GNU Compiler Collection
海灣合作委員會 (X)
GNU Compiler Collection (O)
目前最多(*)使用者使用的Compiler(?)
21. 真實世界中的編譯器
GCC, the GNU Compiler Collection
從 GNU C Compiler 到 GNU Compiler Collection
支援多種語言 (7), 多種處理器平台 (30+) (GCC 4.6.0)
GCC 4.6.0 支援 GO 程式語言 (GCC-GO)
GCC 4.6.0 超過 2 百萬行程式碼
開放原始碼編譯器平台
GCC = Compiler Driver
CC1 = 真正的 C Compiler
23. GCC 的解決之道 (GCC 4.0 以後)
C Source C Front-End
C++ Source C++ Front-End
Generic 語言相關
Gimplify
Gimple 編譯器相關
Tree SSA
Optimizer
RTL
RTL 平台相關
Target ASM Optimizer
Code Generator IR
24. 資料流程分析 (Data-flow analysis)
為什麼需要資料流程分析
Q: 兩個指令怎樣才能交換位置 (Code Motion)
A: 如果兩個指令沒有相依性的話
資料流程分析 = 偵測指令相依性
Reaching definition for a given instruction is another
instruction, the target variable of which may reach the
given instruction without an intervening assignment
Compute use-def chains and def-use chains
25. Code Motion & Pointer Aliasing
C 語言中阻擋 Compiler 最佳化的一大原因
Pointer Aliasing: 兩個不同的指標指到同一個記憶體位置
while (y < z) { int t1 = a + b;
int x = a + b; while (y < z) {
y += x; y += t1;
} }
Q: How about this?
void foo(int *a, int *b, int *y, int z) {
while (*y < z) {
int x = *a + *b;
*y += x;
}
}
A: Compiler 不能把*a + *b 移到迴圈外
因為不確定使用者會不會這樣使用: foo(&num, &num, &num, 5566);
這樣 a, b, y 互為別名, *y 的值改變會使 *a , *b 改變, 故不能提出到迴圈外
27. Static single assignment, SSA Form
什麼是 SSA Form
Social Security Administration (X)
Diego Novillo, GCC Internals - IRs
靜態單賦值形式 (O)
静的単一代入 (O)
用白話講, 在這個表示法當中
每個被 Assign 的變數只會出現一次
作法 1. 每個被Assign的變數給一個
Version number 2. 使用 Φ functions
Example: (From Wiki)
INPUT: y = 1, y =2, x = y
SSA: y1 = 1, y2 = 2, x1 = y2
28. 使用 SSA 可以加強/加快以下的最佳化
Constant propagation
Sparse conditional constant propagation
Dead code elimination
Global value numbering
Partial redundancy elimination
Strength reduction
Register allocation
29. SSA @ Constant propagation (常數傳遞)
用了SSA後每次 Constant propagation 都考100分
EX: GCC-3.x GCC-4.x Example
GCC-3.x (No-SSA) int main()
{
main: int a = 11,b = 13, c = 5566;
… int i, result;
mov r4, #0 for (i = 0 ; i < 10000 ; i++)
.L5: result = (a*b + i) / (a*c);
mov r1, #61184 return result;
add r0, r4, #143 }
add r1, r1, #42
add r4, r4, #1 GCC-4.x (SSA)
bl __divsi3
cmp r4, r5 main:
ble .L5 mov r0, #0
… bx lr
30. GCC 前端: Source -> AST -> Generic
GCC C/C++ frontend
BISON (3.x) b = (a - 56) / c
Hand-written Recursive descent parser (4.x)
C++ in 2004 =
C and Objective-C in 2006
辨識 C 原始碼, 並且轉換成剖析樹 b /
(Parsing)
- c
Abstract Syntax Tree (AST)
抽象語法樹
剖析樹 + 語意 a 56
EX: b = (a - 56) / c
31. GCC中端: Gimple & Tree SSA Optimizer
Gimple 是從 Generic Gimplify 而來的
被限制每個運算只能有兩個運算元 (3-Address IR)
t1 = A op B ( op is operator like +-*/ … etc )
被限制語法只能有某些控制流程
Gimple 可以被化簡成 SSA Form
可以使用 -fdump-tree-<type>-<option> 來觀看其結構
Tree SSA Optimizer
100+ Passes
Loop, Scalar optimization, alias analysis …etc
Inter-procedural analysis, Inter-procedural optimization
32. GCC後端: Register Transfer Language (RTL)
b = a - 56
(set (reg:SI 60 [b])
(plus:SI (reg:SI 61 [a])
(const_int -56 [0xffffffc8])))
LISP-Style 的表示法
RTL Uses Virtual Register (無限多個 Register)
Register Allocation (Virtual Register -> Hard Register)
Instruction scheduling (Pipeline scheduling)
GCC Built-in Operation and Arch-defined Operation
Peephole optimization
35. GCC後端 :管線排程
以經典課本的五級Pipeline 為例子
lw $8, a IF ID EX ME WB IF ID EX ME WB
lw $9, b
Clock 0
mul $10,$8,$8
Clock 1
add $11,$9,$10
Clock 2
lw $12,c
Clock 3
add $13,$12,$0
Clock 4
lw $8, a Clock 5
lw $9, b Clock 6
lw $12,c Clock 7
mul $10,$8,$8 Clock 8
add $13,$12,$0
36. GCC後端 :管線排程 (續)
GCC use finite state automaton (FA) based pipeline
scheduling model
37. GCC後端: Peephole optimization
以管窺天最佳化法
掃過整個IR, 看附近2 ~ n 個IR有沒有最佳化的機會
感覺起來就像是用奧步
可是如果奧步有用,那就得要好好使用
在某些時候, 這個最佳化很好用, 尤其要生數據的時候 XD
;; sub rd, rn, #1
;; cmn rd, #1 (equivalent to cmp rd, #-1)
;; bne dest
;; subs rd, rn, #1
;; bcs dest ((unsigned)rn >= 1)
;; This is a common looping idiom (while (n--))
38. 分很多個.c file or not 分很多個.c file
為什麼要分很多檔案?
因為要讓事情變得簡單
為什麼我們在編譯程式的時候可以下 make -j24?
因為編譯器在Compile 每個 .c 的時候視為獨立個體, 彼
此之間沒有相依性
用Compiler的術語稱之為 Compilation Unit
Static Global Variable vs Non-Static Global Variable
Static: 只有這個 Compilation Unit 可以看到這個變數
Non-Static: 所有Compilation Unit 可以看到這個變數
Q: 怎麼使用別的別的 Compilation Unit 內的變數
A: 使用 extern 關鍵字. EX: extern int i;
39. 深入淺出 Compilation Unit
Compilation Unit
Compilation Unit 產生的限制
Internal Data
Visible Data 關於沒有人使用的 Static Global
External Data Reference
Variable
Compiler 可以砍掉它來節省空間
Internal Function
關於沒有人使用的 Non-Static
Global Variable
Visible Function
Compiler 不能砍掉它
因為不能確定別的 Compilation
Unit 會不會用到它
External Func Reference
Note: 如果確定沒有別的檔案
Internal Function
會使用到這個變數或函式, 請
宣告成 static
46. LLVM核心觀念:
LLVM is a Compiler IR
既然 Compiler framework 就是要支援多種 Front-end 和
多種 Back-end
使用 RISC-Like 平台無關的指令 (LLVM Instruction)和資料
(Metadata) 來表達原始碼
LLVM IR 的三變化 (等價形式)
In-Memory Form: JIT
Assembly language representation: ASM format (.ll)
Bitcode representation: Object code format (.bc)
特點
Self-contained Representation 且有良好規範的 Compiler IR
可以將 Compiler 到一半的結果序列化
47. LLVM 的架構 : 從原始碼到二進制
LLVM Optimizer
C Front-End BitCode Stream-out
LLVM bitcode Passes Optimized
C++ Front-End LLVM bitcode
Selection DAG
Passes
… Front-End Machine
Passes
Not in LLVM LLVM C/C++ LLVM ARM LLVM x86
Back-End Back-End Back-End
In LLVM C/C++ Source ARM ASM X86 ASM
Use LLVM
Target ARM Object X86 Object
Execution (JIT) Execution (JIT)
50. 範例: Clang Expressive Diagnostics
struct a {
virtual int bar();
};
struct foo : public virtual a {
};
void test(foo *P) {
return P->bar() + *P;
}
g++-4.7
xx.cc: In function 'void test(foo*)':
xx.cc:9:24: error: no match for 'operator+' in '(((a*)P) + ((sizetype)(*(long int*)(P-
>foo::<anonymous>.a::_vptr.a + -32u))))->a::bar() + * P'
xx.cc:9:24: error: return-statement with a value, in function returning 'void' [-fpermissive]
clang-3.1
xx.cc:9:21: error: invalid operands to binary expression ('int' and 'foo')
return P->bar() + *P;
~~~~~~~~ ^ ~~
54. 完美Compiler進化論: LLVM的多變化
Source 1 Frontend LLVM IR 1 LLVM Link
IPOed
executable/
Source 2 Frontend LLVM IR 2 LLVM Optimizer
Share object
LLVM ARM
Source 3 Frontend LLVM IR 3
Backend
Compile Time Link Time
Source 1 Frontend LLVM IR 1 LLVM Link LLVM ARM Backend
Source 2 Frontend LLVM IR 2 LLVM Optimizer Processor Specific
executable/
Source 3 Frontend LLVM IR 3 Share object
Compile Time Link Time Load /Installation Time
LLVM IR
PGOed/ Runtime Opted
LLVM Optimizer LLVM ARM Backend executable/ Share object
Profile Data
Idle Time Load Time
55. LLVM 的發展
史記: 項羽本紀第七
太史公曰:吾聞之周生曰「舜目蓋重瞳子」,又聞項羽亦重瞳子。
羽豈其苗裔邪?何興之暴也!
LLVM 也有兩個 L, 加上一家可以掌握全局垂直整合的公司在後面力
挺, LLVM 也是銳不可擋
最美好的時代, 最壞得的時代
LLVM Bitcode 用來當傳遞格式還有很多問題
Binary Compatibility 最為人所詬病
http://lists.cs.uiuc.edu/pipermail/llvm-commits/Week-of-Mon-
20120521/143197.html
CASE STUDY: OpenCL SPIR
The OpenCL Khronos group proposes to extend LLVM to be the
standard OpenCL IR (called SPIR)
http://www.khronos.org/registry/cl/specs/spir_spec-1.0-provisional.pdf
Khronos SPIR For OpenCL Brings Binary Compatibility
http://www.phoronix.com/scan.php?page=news_item&px=MTE4MzM
56. 相信你的編譯器
Linux-Kongress 2009: Source Code Optimization
http://www.linux-kongress.org/2009/slides/compiler_survey_
felix_von_leitner.pdf
Q: x86 中把一個暫存器清成0
mov $0,%eax
and $0,%eax
sub %eax,%eax
xor %eax,%eax
哪道指令最好?
57. 相信你的編譯器: 解答
x86 的指令是可變長度的:
b8 00 00 00 00 mov $0,%eax
83 e0 00 and $0,%eax
29 c0 sub %eax,%eax
31 c0 xor %eax,%eax
So, sub or xor? Turns out, both produce a false
dependency on %eax. But CPUs know to ignore it for
xor.