LLVM筆記(2) - IR


1. 什么是IR
  IR(intermediate representation)是LLVM獨創的中間表達式. 經典的compiler架構由前端frontend(讀入源代碼, 通過詞法, 語法與語義分析建立AST), 中端optimizer(優化模塊)與backend(通過指令選擇, 寄存器分配等階段最終輸出為目標架構的匯編). 其中frontend隨語言類型變化而變化, backend隨目標架構變化而變化, 而優化部分的實現是不區分語言或架構的. 傳統的編譯器如gcc將中端與后端耦合在一起, 導致每個架構不光要實現后端功能也要實現中端優化, 類似的對不同語言后端也不能復用. 而在LLVM中通過將前端輸出的AST轉換為LLVM IR實現了前端與中端的分離, 在中端優化完后再將其轉換為匯編, 大大節約了編譯器開發的工作量. 更多相關介紹詳見http://www.aosabook.org/en/llvm.html
  LLVM IR有三種表現形式: 在編譯器內部的IR, 在磁盤中存儲的bitcode(用於JIT編譯器)以及最常見的易於閱讀的LLVM IR匯編. 三種格式的IR是等價的(可互相轉化), 因此LLVM IR提供了高效的編譯器優化手段的同時又保證了方便調試與定位問題.
  使用IR的優點. 1. 通用, 任意語言都能轉換為IR, 同一IR能轉換為任意架構匯編. 2. 可移植性好, 容易定位問題, 只要保證IR正確性就能確定問題范圍(前端還是后端還是某個優化pass). 3. 支持LTO(link time optimization).

2. 如何生成IR
  在編譯時添加選項-emit-llvm即可生成IR, 此時的IR為bitcode格式(默認文件名后綴為bc), 若要生成匯編格式還需添加-S選項(默認文件名后綴為ll). 以test.c為例:

 1 [23:13:26] hansy@hansy:~/llvm/llvm (master)$ cat ~/test.c
 2 int test(int a, int b)
 3 {
 4   int c = 0;
 5   if (a) {
 6     c = b;
 7     a = c;
 8   }
 9   return c;
10 }
11 [23:13:29] hansy@hansy:~/llvm/llvm (master)$ ../llvm_build/bin/clang ~/test.c -O0 -emit-llvm -S -o ~/test.ll
12 [23:13:37] hansy@hansy:~/llvm/llvm (master)$ cat ~/test.ll
13 ; ModuleID = '/home/hansy/test.c'
14 source_filename = "/home/hansy/test.c"
15 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
16 target triple = "x86_64-unknown-linux-gnu"
17 
18 ; Function Attrs: noinline nounwind optnone uwtable
19 define dso_local i32 @test(i32 %a, i32 %b) #0 {
20 entry:
21   %a.addr = alloca i32, align 4
22   %b.addr = alloca i32, align 4
23   %c = alloca i32, align 4
24   store i32 %a, i32* %a.addr, align 4
25   store i32 %b, i32* %b.addr, align 4
26   store i32 0, i32* %c, align 4
27   %0 = load i32, i32* %a.addr, align 4
28   %tobool = icmp ne i32 %0, 0
29   br i1 %tobool, label %if.then, label %if.end
30 
31 if.then:                                          ; preds = %entry
32   %1 = load i32, i32* %b.addr, align 4
33   store i32 %1, i32* %c, align 4
34   %2 = load i32, i32* %c, align 4
35   store i32 %2, i32* %a.addr, align 4
36   br label %if.end
37 
38 if.end:                                           ; preds = %if.then, %entry
39   %3 = load i32, i32* %c, align 4
40   ret i32 %3
41 }
42 
43 attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
44 
45 !llvm.module.flags = !{!0}
46 !llvm.ident = !{!1}
47 
48 !0 = !{i32 1, !"wchar_size", i32 4}
49 !1 = !{!"clang version 9.0.0 (https://github.com/llvm-mirror/clang.git 8a55120a7d72bed6c93749e0a6dbd0a2fcd873dd) (https://github.com/llvm-mirror/llvm.git ff5f64e4c8e72159f06487684037dcd3eca2cd8e)"}


  ll文件與bc文件同樣能作為源文件被被編譯, 編譯ll/bc文件時無需特殊選項. 由於這個特性我們可以方便對比不同語言/不同架構的ll來確認問題.

3. IR的特點
  引用官方文檔對IR的描述: LLVM is a Static Single Assignment (SSA) based representation that provides type safety, low-level operations, flexibility, and the capability of representing ‘all’ high-level languages cleanly. 作為一門語言IR有如下特點: 基於SSA, 類型安全, 提供低層級操作與支持所有高級語言.

  3.1. 什么是SSA. SSA(static single assignment), 靜態單賦值, 是指變量必須在使用前被定義(static)且指賦值一次(single assignment). 舉個簡單的例子:

x = 0;
x = 1;
y = 2 * x;

  編譯器如何確定y值的來源? 通過引入冗余變量使每個賦值都有對應變量名.

x1 = 0;
x2 = 1;
y = 2 * x2;

  在SSA模式下可以方便的定義def-use chain. 然而對於y值來源不確定的情況則需要特殊處理. 以上文程序為例, 局部變量c可能為0(a = 0)或b, 那么在程序返回的節點c的來源可能有兩個, 類似的a可能為0(a = 0或a = b)或b.

  a1 = a;
  c1 = 0;
  if (a1) {
    c2 = b;
    a2 = c2;
  }
  a = phi(a, a2)
  c = phi(c1, c2)

  為了解決這個問題SSA引入了phi函數, phi表示該值的來源於多個前驅節點. 關於phi節點的表述在后文中分析.

  3.2. 類型系統. LLVM IR是強類型語言, 由於類型系統的支持, LLVM可以無需額外分析即可實現部分轉化. LLVM支持的類型如下:
  void type - 無返回值, 無大小
  integer type - 基礎類型(simple type), 表示為iN, 其中N為指定位寬, 大小為指定長度位的整型, 注意是無符號整型
  floating point type - 浮點類型, 對應為IEEE754標准浮點數類型
  pointer type - 指針類型, 指向某個內存對象, 注意沒有void *, 而使用i8 *代替
  array type - 數組類型, 包含元素類型與個數
  label type - 標識代碼塊, 類似於匯編的label
  token type - 與指令相關的特殊值類型, 比如phi / select等
  struct type -
  aggregate type -
  metadata type -
  x86_mmx type -
  function type -
  first class type -
  single value type -

4. IR語法簡介
  關於IR的reference manual請參考docs/LangRef中完整介紹, 本節的目標是介紹最基礎的IR語法.

  4.1. 標識符
  LLVM中包含兩類標識符: 全局類型與局部類型. 全局類型(函數, 全局變量)以@為起始字符, 局部類型(寄存器名, 類型)以%為標識. 一共有三類標識符, 分別為: 有名變量, 以%或@為起始字符, 后接字符串; 匿名變量以%或@為起始字符, 后接數字; 常量, 后文介紹. 其中有名變量通常源於代碼中的變量, 匿名變量來自於LLVM中的臨時變量, 寄存器等. 注意匿名變量是LLVM在輸出ll時自動順序命名的(編譯過程中並不存在同名的變量, 后文會講述), 因此必須順序連續排列(比如將以上代碼中%2修改為%3會導致編譯不通過).
  4.2. 常量
  與類型系統對應, LLVM提供各類型(bool, int, float, pointer, token)以及復雜類型的常量. 注意兩個特殊的常量: zeroinitializer(用於指定內存對象的零初始化)與metadata node(無類型常量).
  4.3. metadata
  metadata是LLVM提供的一種附加信息到某個指令的手段. 有兩類metadata原語: metadata node與metadata string. 所有的metadata均以!起始. metadata string是以雙引號包含的字符串, metadata node類以於結構體常量的表現方式(用逗號分割的元素列表).
  4.4. Instruction
  //TODO
  4.5. Intrinsic
  //TODO


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM