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
自我介紹
 經歷
   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 工程師
關於今天的演講
 摘要
  簡短的介紹從原始碼到二進制的流程
    三大步驟
    Compiler Driver
  從原始碼到二進制的重要推手: 編譯器
    課本中的編譯器
    真實世界的編譯器
        GCC
        LLVM
春秋: 微言大義
 隱公元年: 夏,五月,鄭伯克段於鄢


 短短幾個字,影含著諸多含意
  段不弟,故不言弟
  如二君,故曰克
  稱鄭伯,譏失教也


 為什麼要了解從原始碼到二進制
  電腦科學所有的問題都可以用另外一層的抽象化來解
   決 (All problems in computer science can be solved by
   another level of indirection)
  了解背後的運作原理, 破解微言大義
微言大義:
現代Linux環境, 程式啟動流程
                                                  動態連結
 Shell                                            靜態連結 hello

          ./hello                   ld-linux.so

                                             動態連結器
                                                   eglibc
                                 libc_start_main               main
          execve

                                      exit
         SYS_execve                 SYS_exit

kernel
         驗證執行檔        啟動loader    棄置Process

         配置Process
從原始碼到二進制:
常見的誤解
 從原始碼到二進制,其實不只需要 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
編譯 Hello world 程式的流程
       C program: hello.c 原始碼      三大步驟

        Compiler (CC1) 編譯器
                                     編譯
     Assembly File: hello.s 組合語言



        Assembler (AS) 組譯器           組譯

       Object File: hello.o 目的檔

                                     連結
          Linker (LD) 連結器



       Executable: hello 執行檔
圖解簡易完整編譯流程
                  #include <stdio.h>
                  int main() {
                    printf (“Hello World”);
                  }

  stdio.h    int printf (const char* fmt, …);
                                                         main:
Preprocess   int main() {                Compile             LDR R1, [R2 + 12]
               printf (“Hello World”);                       BAL printf
             }

                                                   libc.a 0x1000 <printf>:
                                                            EC 00 00 12
               Main:                                        …
  Assemble        EC 00 00 12            Linking
                  F0 ?? ?? ??                            0x2000 <Main>:
                                                            EC 00 00 12
                                                            F0 00 10 00
常見的誤解: GCC 是個 C 編譯器                                             gcc
 答案: GCC 是一個 Compiler Driver
                                                               binutils
              gcc               Source (.c and .h)              glibc

                      cpp
                                        Preprocessed source (.i or .ii)
                      cc1
                                        Asm (.s)
                      as
                                        Reloadable (.o)
                    collect2

 library

   crt*                               Binary
                           ld         (Executable)
link script
                     GCC 這個執行檔, 可以當Compiler, 也能當 Preprocessor,
                     也能當 Assembler, 更能當 Linker
Example: GCC 下給 ld 的參數
 collect2 version 4.4.3 (i386 Linux/ELF)

  /usr/bin/ld --build-id --eh-frame-hdr -m elf_i386 --hash-style=both
  -dynamic-linker /lib/ld-linux.so.2 -z relro /usr/lib/gcc/i486-linux-
  gnu/4.4.3/../../../../lib/crt1.o /usr/lib/gcc/i486-linux-
  gnu/4.4.3/../../../../lib/crti.o /usr/lib/gcc/i486-linux-
  gnu/4.4.3/crtbegin.o -L/usr/lib/gcc/i486-linux-gnu/4.4.3 -
  L/usr/lib/gcc/i486-linux-gnu/4.4.3 -L/usr/lib/gcc/i486-linux-
  gnu/4.4.3/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/i486-
  linux-gnu/4.4.3/../../.. hello.o -v -lgcc --as-needed -lgcc_s --no-as-
  needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i486-
  linux-gnu/4.4.3/crtend.o /usr/lib/gcc/i486-linux-
  gnu/4.4.3/../../../../lib/crtn.o
  GNU ld (GNU Binutils for Ubuntu) 2.20.1-system.20100303
如果有人想當你的程式碼
那你怎樣才能做她的編譯器 (Compiler) ?
 什麼是編譯器?
   Source Language ->
    Target Language

 Recognizer: 讀懂她
 Translator: 轉譯她
 Optimization: 調教她

        High Level Language


             Compiler


    Optimized Low Level Language
編譯器小歷史:
 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
先有雞還是先有蛋
 先有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的編譯器
編譯器的架構 (Textbook)
                                      原始碼

                                字彙分析 (正規語言)

                           語法分析 (Context-Free Grammar)

                                      語法樹

                            語意分析 (Type Checking ..etc)

                                      中間語言

                                      最佳化

                                      目標語言


   屠龍刀: Syntax Directed Translator
編譯器技術=屠龍之技
 莊子: 列禦寇第三十二
  朱泙漫學屠龍於支離益,單千金之家,三年技成而無
  所用其巧。


 用白話說就是有個人花了很多錢和時間學了屠龍
技能, 後來發現沒有龍可以殺

 不過隨著硬體的發展,軟體的複雜度提升,這個世界
上的龍也越來越多了
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/
while (y < z) {
    int x = a + b;
    y += x;
}                    How Compiler Works
                                       原始碼

                                 字彙分析 (正規語言)

                           語法分析 (Context-Free Grammar)

                                       語法樹

                            語意分析 (Type Checking ..etc)

                                      中間語言

                                       最佳化

                                      目標語言


                           http://www.stanford.edu/class/cs143/
while (y < z) {
    int x = a + b;
    y += x;
}                    How Compiler Works
                                       原始碼

                                 字彙分析 (正規語言)

                           語法分析 (Context-Free Grammar)

                                       語法樹

                            語意分析 (Type Checking ..etc)

                                      中間語言

                                       最佳化

                                      目標語言


                           http://www.stanford.edu/class/cs143/
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/
真實世界中的編譯器
GCC, the GNU Compiler Collection
 海灣合作委員會 (X)


 GNU Compiler Collection (O)




         目前最多(*)使用者使用的Compiler(?)
真實世界中的編譯器
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
GCC 的架構
 課本終究還是課本


 GCC 的特性與挑戰:                     int main()
   GCC 支援多種語言 (前端)               {
                                    return 0;
   GCC 支援多種處理器 (後端)                IamDeadCode();
   Syntax Directed Translator    }
      程式的語法樹結構將無法和處理器架構脫鉤


 仔細想想: 有些最佳化和語言跟平台都沒有關係
   消除沒有用到的程式碼 (Dead code elimination)


 所以 GCC 引入中間層最佳化 (編譯器相關最佳化)
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
資料流程分析 (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
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 改變, 故不能提出到迴圈外
Strict Aliasing Rule & Restrict Pointer Aliasing
 如果 Pointer Aliasing 無限上綱
   那碰到 Pointer, Compiler 所有的最佳化都動不了
 To or not to do:
    Cross the Rubicon (O)
    蒯通說韓信自立為王 (X)
 Strict Aliasing Rule
    摩登編譯器會假設不同 type 之間的 pointer 是沒有 aliasing 的
    可是世界上存在相當多原始人程式碼, 所以就造成了很多問題
    Note: 使用 -fno-strict-aliasing 來關閉 Strict Aliasing Rule
 C99: Restrict Pointer Aliasing
   使用 restrict 關鍵字 (--std=c99) 來告訴 Compiler 這個 Pointer
    不會有 Pointer Aliasing
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
使用 SSA 可以加強/加快以下的最佳化
 Constant propagation
 Sparse conditional constant propagation
 Dead code elimination
 Global value numbering
 Partial redundancy elimination
 Strength reduction
 Register allocation
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
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
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
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
GCC後端: RTL Patten Match Engine
                       (set (reg:SI 60 [b])
    MIPS.md                 (plus:SI (reg:SI 61 [a])
                                     (const_int -56 [0xffffffc8])))
(define_insn "*addsi3"
  [(set (match_operand:GPR 0 "register_operand" "=d,d")
        (plus:GPR (match_operand:GPR 1 "register_operand" "d,d")
                   (match_operand:GPR 2 "arith_operand" "d,Q")))]
  "!TARGET_MIPS16"
  "@
    addut%0,%1,%2                                   d代表是Register
    addiut%0,%1,%2"                                 Q代表是整數
  [(set_attr "type" "arith")
   (set_attr "mode" "si")])

          指令的屬性(用於管線排程)
                                                 b = a - 56
指令限制(非MIPS16才可使用)
                                               addiu $2, $3, -56
GCC後端 : 暫存器分配
 暫存器分配其實是個著色問題 (NP-Complete)
   給一個無向圖,相鄰的兩個vertex 不能著同一種顏色
   每個 vertex 代表的是一個變數, 每個 edge 代表的是兩個變數的生
    存時間有重疊
 GCC 的暫存器分配,其實比著色問題更複雜
   在有些平台,某些指令其實只能搭配某組的暫存器
   其實有些變數是常數,或是 memory form,或是他是參數或回傳值,所
    以只能分配到某些特定暫存器
 Old GCC RA: Reload Pass
   GCC 把 pseudo-registers 根據指令的限制對應到硬體暫存器的過程
   其實是個複雜到極點的程式, 所以到最後就沒人看得懂了
 所以後來就重寫了一個
   IRA (Integrated Register Allocator) : 整合 Coalescing, Live range
    splitting 以及挑選較好的 Hard Register 的做法
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
GCC後端 :管線排程 (續)
 GCC use finite state automaton (FA) based pipeline
 scheduling model
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--))
分很多個.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;
深入淺出 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
Compilation Unit
  盡可能使用區域變數
static int i;                             int main()
int main()                                {
{                                           int i;
  for (i = 0 ; i < 10; i++)                 for (i = 0 ; i < 10; i++)
    printf ("Hello %dn", i);                 printf ("Hello %dn", i);
}                                         }

main:                                     main:
        mov     r3, #0                            stmfd   sp!, {r4, lr}
                                                            11
        stmfd   sp!, {r4, lr}                     mov     r4, #0
        movw    r4, #:lower16:.LANCHOR0   .L2:
        movt    r4, #:upper16:.LANCHOR0           mov     r1, r4
        mov     r1, r3                            ldr     r0, .L4
        str     r3, [r4, #0]                      add     r4, r4, #1
.L2:                                              bl      printf
        ldr     r0, .L4                           cmp     r4, #10
        bl      printf                            bne     .L2
        ldr     r1, [r4, #0]                      ldmfd   sp!, {r4, pc}
        add     r1, r1, #1                .L4:
        str     r1, [r4, #0]                      .word   .LC0
        cmp     r1, #9
        ble     .L2
        ldmfd   sp!, {r4, pc}
.L4:
        .word   .LC0
GCC 內建函式 (Built-in Function)
 GCC 為了進行最佳化, 會辨認標準函式(因為語意有
 標準規範), 如果符合條件, 就會以處理器最適合的
 方式來計算
  Strcpy => x86 字串處理指令
  Printf => Puts, Putc
  Memcpy => Block Transfer Instruction


 這對開發嵌入式系統的人常造成困擾
  因為開發嵌入式系統的人喜歡開發自己殘廢的printf (X)
  因為開發嵌入式系統需要客製化的printf (O)
     Note: 使用 -fno-builtin-XXX or -fno-builtin 來關閉這個功能
IPO (Inter-Procedural Optimization)
 Procedure / Function: 結構化程式的主軸, 增加可用
 性, 減少維護成本
   副程式呼叫的執行成本
     準備參數: 把變數移到到特定 Register 或 push 到 Stack
     呼叫副程式, 儲存和回復 Register Context
   現代化程式: 有許多很小的副程式
     int IsOdd(int num) {
        return (num & 0x1);
      }
 最簡單的 IPO: Function inlining
   簡單來說: 把整個副程式copy 一份到 caller 的程式碼去
IPO (Inter-Procedural Optimization)
 如何 Inline 一個 外部函式?
    基本上 Compiler 無法做到
        因為 Compiler 在編譯程式的時候不會去看別的
           Compilation Unit 的內容, 不知道內容自然就沒辦法 inline
           一個看不見的函式
 解法: Link Time Optimization (LTO)
    或叫: Whole-Program Optimization (WPO)
    關鍵 Link Time Code Generation

Source 1      Compiler   IR 1    Linker   Full IR   Compiler

Source 2      Compiler   IR 2
                                                    Optimized
Source 3      Compiler   IR 3
                                                     Program
21世紀的另外一種選擇: LLVM
 LLVM (以前稱為 Low Level Virtual Machine)
   用 “現代“ C++ 寫成的 Compiler infrastructure
   設計來進行全時最佳化 (compile-time, link-time, run-
   time, and idle-time optimizations)

                         LLVM 起源
                            2000年UIUC Vikram Adve 與 Chris
                             Lattner 的研究發展而成
                         後來受到蘋果重用而大發異彩
                            蘋果概念股 (?)
                         FreeBSD 10 將使用 Clang/LLVM
                           為預設的編譯器
LLVM: 全時最佳化編譯器平台
   GNU Toolchain 的守備範圍    知道原始碼的結構 (O)
   編譯階段
                          不知道變數的位址 (X)

                          不知道原始碼的結構 (X)
   連結階段
                          知道變數的位址 (O)

                          不知道原始碼的結構 (X)
  載入/安裝階段
                          不知道變數的位址 (X)
                          知道處理器有甚麼能力 (O)
   執行階段
                         正所謂是
                         橫看成嶺側成峰
   空閒階段
                         遠近高低各不同
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 到一半的結果序列化
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)
LLVM Toolchain (llvm-gcc)
 既然 LLVM 只是一個 Compiler-IR, 就代表 LLVM 在剛
  開始的時候是沒有辦法走完整個編譯流程的
 LLVM-GCC (gcc-4.2)
     利用 GCC 已經存在而且眾多大眾使用的 Front-end
     後來 RMS 就出來打槍這種方式 (GCC 4.3, GPLv3)
     後來 Apple 就跳出來發展自己的 Front-end (Clang)

 C Source            C Front-End
                                              Generic
C++ Source       C++ Front-End

                                   Gimplify

            Gimple                             LLVM Bitcode

NOTE: LLVM早期的也沒有自己的 Assembler (後來有 MC) 和 Linker 來建立完整編譯流程
LLVM Toolchain (Clang)
 Clang : C, C++, Objective-C 的編譯器前端
   Apple 為了取代 GCC 重新打造的 Frontend
   重寫一個前端代價是非常昂貴的
     其實你是 慣C? 還是 慣GCC?
     GNU/Linux 世界使用了 GCC 行之有年的非標準語法
   Clang 設計成模組化 Frontend : Clang C API
     可以 Export AST (Ex: RenderScript 用來產生 Java 的 Binding)
     可以用來實作自動完成, Refactor Tool … etc
     可以用來實作靜態程式碼分析工具 (Linting tool … etc)
   Clang 改善了錯誤訊息 (Expressive diagnostics )
   快速編譯, 低記憶使用量, 容易學習和Hack的 Frontend
範例: 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;
       ~~~~~~~~ ^ ~~
Clang/LLVM Toolchain
 C Source                   CLANG
                             Language                          LLVM Bitcode
C++ Source                   Optimizer


 Binding Script .. etc       AST Stream-out


 Clang Compiler Driver
    盡可能和 GCC 相容, 遇到不認識的Option不會停下來
    不需要 Cross Compiler, 本身就有 Cross Compiler 的能力 (?)
          Cross Compiler != Cross Compiler Toolchian




                             來源: http://www.aosabook.org/en/llvm.html
滅諸侯,成帝業,為天下一統


LLVM 千秋萬世, 一統江湖
 Q:挖賽, Clang + LLVM 那麼神, 其他人不是沒戲唱了嗎
   A: 在這地球上還沒有人能統一天下, LLVM 當然也不行
 Clang 之所以不用產生 Cross Compiler 的迷思
    x86/64 clang 產生出來的LLVM Bitcode, 用 i686 的 llvm
     backend 來產生 x86 的機器碼 (-m32)
      可以產生出來, 可是不會動 XD
 C/C++ 不是 Portable Language, 產生出來的 Bitcode 不
 Portable 不是也是很正常的事嗎
   LLVM Bitcode 本身就是 Portable (X)
   使用 host triple 來改變產生出來的 LLVM Bitcode (O)
LLVM TableGen (Target Description)
         Instruction Encoding (For MC Assembler)
                         Input/Output
                                           Assembly (For ASMPrinter)
def ADDPS : PI<0x58,                          Instruction Selection Pattern
    (outs VR256:$dst),                        (For Selection DAG)
    (ins VR256:$src1, VR256:$src2),
    "addps $src2, $src1, $dst",
    [(set VR256:$dst, (fadd VR256:$src1, VR256:$src2))]>;


 編譯器Backend 都有一些很無聊的地方, 用另外一
 種語言來表示比直接寫 C/C++ 簡單好 maintain 的
   GCC RTL Pattern : (MD, Machine Description file)
   LLVM TableGen : (TD, Target Description file)
完美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
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
相信你的編譯器
 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


 哪道指令最好?
相信你的編譯器: 解答
 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.
References
 http://www.redhat.com/magazine/002dec04/features/gcc/
 http://gcc.gnu.org/onlinedocs/gcc
 http://en.wikipedia.org/wiki/Interprocedural_optimization
 http://en.wikipedia.org/wiki/Link-time_optimization
 http://llvm.org/
 http://www.aosabook.org/en/llvm.html
Any Question ?

Hcsm lect-20120913

  • 1.
    FROM SOURCE TOBINARY 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 工程師
  • 3.
    關於今天的演講  摘要 簡短的介紹從原始碼到二進制的流程  三大步驟  Compiler Driver  從原始碼到二進制的重要推手: 編譯器  課本中的編譯器  真實世界的編譯器  GCC  LLVM
  • 4.
    春秋: 微言大義  隱公元年:夏,五月,鄭伯克段於鄢  短短幾個字,影含著諸多含意  段不弟,故不言弟  如二君,故曰克  稱鄭伯,譏失教也  為什麼要了解從原始碼到二進制  電腦科學所有的問題都可以用另外一層的抽象化來解 決 (All problems in computer science can be solved by another level of indirection)  了解背後的運作原理, 破解微言大義
  • 5.
    微言大義: 現代Linux環境, 程式啟動流程 動態連結 Shell 靜態連結 hello ./hello ld-linux.so 動態連結器 eglibc libc_start_main main execve exit SYS_execve SYS_exit kernel 驗證執行檔 啟動loader 棄置Process 配置Process
  • 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
  • 7.
    編譯 Hello world程式的流程 C program: hello.c 原始碼 三大步驟 Compiler (CC1) 編譯器 編譯 Assembly File: hello.s 組合語言 Assembler (AS) 組譯器 組譯 Object File: hello.o 目的檔 連結 Linker (LD) 連結器 Executable: hello 執行檔
  • 8.
    圖解簡易完整編譯流程 #include <stdio.h> int main() { printf (“Hello World”); } stdio.h int printf (const char* fmt, …); main: Preprocess int main() { Compile LDR R1, [R2 + 12] printf (“Hello World”); BAL printf } libc.a 0x1000 <printf>: EC 00 00 12 Main: … Assemble EC 00 00 12 Linking F0 ?? ?? ?? 0x2000 <Main>: EC 00 00 12 F0 00 10 00
  • 9.
    常見的誤解: GCC 是個C 編譯器 gcc 答案: GCC 是一個 Compiler Driver binutils gcc Source (.c and .h) glibc cpp Preprocessed source (.i or .ii) cc1 Asm (.s) as Reloadable (.o) collect2 library crt* Binary ld (Executable) link script GCC 這個執行檔, 可以當Compiler, 也能當 Preprocessor, 也能當 Assembler, 更能當 Linker
  • 10.
    Example: GCC 下給ld 的參數  collect2 version 4.4.3 (i386 Linux/ELF) /usr/bin/ld --build-id --eh-frame-hdr -m elf_i386 --hash-style=both -dynamic-linker /lib/ld-linux.so.2 -z relro /usr/lib/gcc/i486-linux- gnu/4.4.3/../../../../lib/crt1.o /usr/lib/gcc/i486-linux- gnu/4.4.3/../../../../lib/crti.o /usr/lib/gcc/i486-linux- gnu/4.4.3/crtbegin.o -L/usr/lib/gcc/i486-linux-gnu/4.4.3 - L/usr/lib/gcc/i486-linux-gnu/4.4.3 -L/usr/lib/gcc/i486-linux- gnu/4.4.3/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/i486- linux-gnu/4.4.3/../../.. hello.o -v -lgcc --as-needed -lgcc_s --no-as- needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i486- linux-gnu/4.4.3/crtend.o /usr/lib/gcc/i486-linux- gnu/4.4.3/../../../../lib/crtn.o GNU ld (GNU Binutils for Ubuntu) 2.20.1-system.20100303
  • 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的編譯器
  • 14.
    編譯器的架構 (Textbook) 原始碼 字彙分析 (正規語言) 語法分析 (Context-Free Grammar) 語法樹 語意分析 (Type Checking ..etc) 中間語言 最佳化 目標語言  屠龍刀: Syntax Directed Translator
  • 15.
    編譯器技術=屠龍之技  莊子: 列禦寇第三十二  朱泙漫學屠龍於支離益,單千金之家,三年技成而無 所用其巧。  用白話說就是有個人花了很多錢和時間學了屠龍 技能, 後來發現沒有龍可以殺  不過隨著硬體的發展,軟體的複雜度提升,這個世界 上的龍也越來越多了
  • 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 GNUCompiler Collection  海灣合作委員會 (X)  GNU Compiler Collection (O) 目前最多(*)使用者使用的Compiler(?)
  • 21.
    真實世界中的編譯器 GCC, the GNUCompiler 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
  • 22.
    GCC 的架構  課本終究還是課本 GCC 的特性與挑戰: int main()  GCC 支援多種語言 (前端) { return 0;  GCC 支援多種處理器 (後端) IamDeadCode();  Syntax Directed Translator }  程式的語法樹結構將無法和處理器架構脫鉤  仔細想想: 有些最佳化和語言跟平台都沒有關係  消除沒有用到的程式碼 (Dead code elimination)  所以 GCC 引入中間層最佳化 (編譯器相關最佳化)
  • 23.
    GCC 的解決之道 (GCC4.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 改變, 故不能提出到迴圈外
  • 26.
    Strict Aliasing Rule& Restrict Pointer Aliasing  如果 Pointer Aliasing 無限上綱  那碰到 Pointer, Compiler 所有的最佳化都動不了  To or not to do:  Cross the Rubicon (O)  蒯通說韓信自立為王 (X)  Strict Aliasing Rule  摩登編譯器會假設不同 type 之間的 pointer 是沒有 aliasing 的  可是世界上存在相當多原始人程式碼, 所以就造成了很多問題  Note: 使用 -fno-strict-aliasing 來關閉 Strict Aliasing Rule  C99: Restrict Pointer Aliasing  使用 restrict 關鍵字 (--std=c99) 來告訴 Compiler 這個 Pointer 不會有 Pointer Aliasing
  • 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 @ Constantpropagation (常數傳遞)  用了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 TransferLanguage (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
  • 33.
    GCC後端: RTL PattenMatch Engine (set (reg:SI 60 [b]) MIPS.md (plus:SI (reg:SI 61 [a]) (const_int -56 [0xffffffc8]))) (define_insn "*addsi3" [(set (match_operand:GPR 0 "register_operand" "=d,d") (plus:GPR (match_operand:GPR 1 "register_operand" "d,d") (match_operand:GPR 2 "arith_operand" "d,Q")))] "!TARGET_MIPS16" "@ addut%0,%1,%2 d代表是Register addiut%0,%1,%2" Q代表是整數 [(set_attr "type" "arith") (set_attr "mode" "si")]) 指令的屬性(用於管線排程) b = a - 56 指令限制(非MIPS16才可使用) addiu $2, $3, -56
  • 34.
    GCC後端 : 暫存器分配 暫存器分配其實是個著色問題 (NP-Complete)  給一個無向圖,相鄰的兩個vertex 不能著同一種顏色  每個 vertex 代表的是一個變數, 每個 edge 代表的是兩個變數的生 存時間有重疊  GCC 的暫存器分配,其實比著色問題更複雜  在有些平台,某些指令其實只能搭配某組的暫存器  其實有些變數是常數,或是 memory form,或是他是參數或回傳值,所 以只能分配到某些特定暫存器  Old GCC RA: Reload Pass  GCC 把 pseudo-registers 根據指令的限制對應到硬體暫存器的過程  其實是個複雜到極點的程式, 所以到最後就沒人看得懂了  所以後來就重寫了一個  IRA (Integrated Register Allocator) : 整合 Coalescing, Live range splitting 以及挑選較好的 Hard Register 的做法
  • 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 ornot 分很多個.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 CompilationUnit  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
  • 40.
    Compilation Unit 盡可能使用區域變數 static int i; int main() int main() { { int i; for (i = 0 ; i < 10; i++) for (i = 0 ; i < 10; i++) printf ("Hello %dn", i); printf ("Hello %dn", i); } } main: main: mov r3, #0 stmfd sp!, {r4, lr} 11 stmfd sp!, {r4, lr} mov r4, #0 movw r4, #:lower16:.LANCHOR0 .L2: movt r4, #:upper16:.LANCHOR0 mov r1, r4 mov r1, r3 ldr r0, .L4 str r3, [r4, #0] add r4, r4, #1 .L2: bl printf ldr r0, .L4 cmp r4, #10 bl printf bne .L2 ldr r1, [r4, #0] ldmfd sp!, {r4, pc} add r1, r1, #1 .L4: str r1, [r4, #0] .word .LC0 cmp r1, #9 ble .L2 ldmfd sp!, {r4, pc} .L4: .word .LC0
  • 41.
    GCC 內建函式 (Built-inFunction)  GCC 為了進行最佳化, 會辨認標準函式(因為語意有 標準規範), 如果符合條件, 就會以處理器最適合的 方式來計算  Strcpy => x86 字串處理指令  Printf => Puts, Putc  Memcpy => Block Transfer Instruction  這對開發嵌入式系統的人常造成困擾  因為開發嵌入式系統的人喜歡開發自己殘廢的printf (X)  因為開發嵌入式系統需要客製化的printf (O)  Note: 使用 -fno-builtin-XXX or -fno-builtin 來關閉這個功能
  • 42.
    IPO (Inter-Procedural Optimization) Procedure / Function: 結構化程式的主軸, 增加可用 性, 減少維護成本  副程式呼叫的執行成本  準備參數: 把變數移到到特定 Register 或 push 到 Stack  呼叫副程式, 儲存和回復 Register Context  現代化程式: 有許多很小的副程式  int IsOdd(int num) { return (num & 0x1); }  最簡單的 IPO: Function inlining  簡單來說: 把整個副程式copy 一份到 caller 的程式碼去
  • 43.
    IPO (Inter-Procedural Optimization) 如何 Inline 一個 外部函式?  基本上 Compiler 無法做到  因為 Compiler 在編譯程式的時候不會去看別的 Compilation Unit 的內容, 不知道內容自然就沒辦法 inline 一個看不見的函式  解法: Link Time Optimization (LTO)  或叫: Whole-Program Optimization (WPO)  關鍵 Link Time Code Generation Source 1 Compiler IR 1 Linker Full IR Compiler Source 2 Compiler IR 2 Optimized Source 3 Compiler IR 3 Program
  • 44.
    21世紀的另外一種選擇: LLVM  LLVM(以前稱為 Low Level Virtual Machine)  用 “現代“ C++ 寫成的 Compiler infrastructure  設計來進行全時最佳化 (compile-time, link-time, run- time, and idle-time optimizations)  LLVM 起源  2000年UIUC Vikram Adve 與 Chris Lattner 的研究發展而成  後來受到蘋果重用而大發異彩  蘋果概念股 (?)  FreeBSD 10 將使用 Clang/LLVM 為預設的編譯器
  • 45.
    LLVM: 全時最佳化編譯器平台 GNU Toolchain 的守備範圍  知道原始碼的結構 (O) 編譯階段  不知道變數的位址 (X)  不知道原始碼的結構 (X) 連結階段  知道變數的位址 (O)  不知道原始碼的結構 (X) 載入/安裝階段  不知道變數的位址 (X)  知道處理器有甚麼能力 (O) 執行階段 正所謂是 橫看成嶺側成峰 空閒階段 遠近高低各不同
  • 46.
    LLVM核心觀念: LLVM is aCompiler 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)
  • 48.
    LLVM Toolchain (llvm-gcc) 既然 LLVM 只是一個 Compiler-IR, 就代表 LLVM 在剛 開始的時候是沒有辦法走完整個編譯流程的  LLVM-GCC (gcc-4.2)  利用 GCC 已經存在而且眾多大眾使用的 Front-end  後來 RMS 就出來打槍這種方式 (GCC 4.3, GPLv3)  後來 Apple 就跳出來發展自己的 Front-end (Clang) C Source C Front-End Generic C++ Source C++ Front-End Gimplify Gimple LLVM Bitcode NOTE: LLVM早期的也沒有自己的 Assembler (後來有 MC) 和 Linker 來建立完整編譯流程
  • 49.
    LLVM Toolchain (Clang) Clang : C, C++, Objective-C 的編譯器前端  Apple 為了取代 GCC 重新打造的 Frontend  重寫一個前端代價是非常昂貴的  其實你是 慣C? 還是 慣GCC?  GNU/Linux 世界使用了 GCC 行之有年的非標準語法  Clang 設計成模組化 Frontend : Clang C API  可以 Export AST (Ex: RenderScript 用來產生 Java 的 Binding)  可以用來實作自動完成, Refactor Tool … etc  可以用來實作靜態程式碼分析工具 (Linting tool … etc)  Clang 改善了錯誤訊息 (Expressive diagnostics )  快速編譯, 低記憶使用量, 容易學習和Hack的 Frontend
  • 50.
    範例: Clang ExpressiveDiagnostics 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; ~~~~~~~~ ^ ~~
  • 51.
    Clang/LLVM Toolchain CSource CLANG Language LLVM Bitcode C++ Source Optimizer Binding Script .. etc AST Stream-out  Clang Compiler Driver  盡可能和 GCC 相容, 遇到不認識的Option不會停下來  不需要 Cross Compiler, 本身就有 Cross Compiler 的能力 (?)  Cross Compiler != Cross Compiler Toolchian 來源: http://www.aosabook.org/en/llvm.html
  • 52.
    滅諸侯,成帝業,為天下一統 LLVM 千秋萬世, 一統江湖 Q:挖賽, Clang + LLVM 那麼神, 其他人不是沒戲唱了嗎  A: 在這地球上還沒有人能統一天下, LLVM 當然也不行  Clang 之所以不用產生 Cross Compiler 的迷思  x86/64 clang 產生出來的LLVM Bitcode, 用 i686 的 llvm backend 來產生 x86 的機器碼 (-m32)  可以產生出來, 可是不會動 XD  C/C++ 不是 Portable Language, 產生出來的 Bitcode 不 Portable 不是也是很正常的事嗎  LLVM Bitcode 本身就是 Portable (X)  使用 host triple 來改變產生出來的 LLVM Bitcode (O)
  • 53.
    LLVM TableGen (TargetDescription) Instruction Encoding (For MC Assembler) Input/Output Assembly (For ASMPrinter) def ADDPS : PI<0x58, Instruction Selection Pattern (outs VR256:$dst), (For Selection DAG) (ins VR256:$src1, VR256:$src2), "addps $src2, $src1, $dst", [(set VR256:$dst, (fadd VR256:$src1, VR256:$src2))]>;  編譯器Backend 都有一些很無聊的地方, 用另外一 種語言來表示比直接寫 C/C++ 簡單好 maintain 的  GCC RTL Pattern : (MD, Machine Description file)  LLVM TableGen : (TD, Target Description file)
  • 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.
  • 58.
    References  http://www.redhat.com/magazine/002dec04/features/gcc/  http://gcc.gnu.org/onlinedocs/gcc http://en.wikipedia.org/wiki/Interprocedural_optimization  http://en.wikipedia.org/wiki/Link-time_optimization  http://llvm.org/  http://www.aosabook.org/en/llvm.html
  • 59.