1. 程式人生 > 其它 >llvm 一個簡單函式的優化

llvm 一個簡單函式的優化

  LLVM優化一個簡單函式 一個優化的、領先的編譯器通常被組織為:
  1. 一個將原始碼翻譯為一箇中間表示(IR)的前端。
  2. 一個目標無關的優化流水線:一系列,它們持續重寫IR,以消除低效性以及不能容易翻譯為機器碼的形式。有時稱之為“中端(middle end)”。
  3. 一個目標相關的後端,生成彙編程式碼或機器碼。
優化的函式 bool is_sorted(int *a, int n) {undefined for (int i = 0; i < n - 1; i++) if (a[i] > a[i + 1]) return false; return true; } 1,simple CFG 這裡隱含聲明瞭一個bool 變數來儲存retval; 這裡,基本塊26只是跳轉到塊27。這類塊可以被消除。 https://github.com/llvm-mirror/llvm/blob/release_60/lib/Transforms/Scalar/SimplifyCFGPass.cpp 這個檔案實現死程式碼消除與基本塊合併,連同一組其他窺孔控制流優化。例如:
  • 刪除沒有前驅的基本塊。
  • 如果僅有一個前驅且該前驅僅有一個後繼,將基本塊與且前驅合併。
  • 消除只有一個前驅的基本塊的PHI節點。
  • 消除僅包含無條件分支的基本塊。
  • 將invoke指令改為呼叫nounwind函式。
  • 把形如“if (x) if (y)”的情形改為“if (x&y)”
CFG清理的大多數機會是其他LLVM-PASS的結果。例如,死程式碼消除與迴圈不變程式碼移動可以容易地建立空基本塊。 2 SROA SROA(聚集物件的標量替換,scalar replacement of aggregate),是我們其中一個重要的優化。這個名字有點誤導,因為SROA僅是它其中一個功能。這個遍消除每條alloca指令(臨時變數)並嘗試把它提升到SSA暫存器。,單個alloc將被轉換為多個暫存器。SROA會消除與臨時變數相關的所有alloc,store ,load指令。在這個過程中它將插入一些phi指令(匹配store指令)。 %3, tmpret %4, arg1 %5, arg2 %6,i Early CSE
early common subexpression elimination
接下來是“早期公共子表示式消除”(CSE)。CSE嘗試消除人們編寫的程式碼和部分優化的程式碼中出現的冗餘子計算。“早期CSE”是快速的,一種查詢平凡冗餘計算的簡單CSE。SSA形式後,每個SSA的 值是不會被更改的,僅被賦值一次。所以相同的表示式的指令是可以合併的。如下圖的%10,%17。可以用10%代替所有的%17. 指令合併 :instruction combiner 主要執行一組多種多樣的“窺孔優化”,它們(通常)將一組由資料流連線的指令重寫為更高效的形式。InstCombine將不改變函式的控制流。 在本例中沒有優化的空間。 舉例 a = b +1 ; a = a - 1; ==> a = b; 這裡不是從%1減去1來計算%4,我們決定加上-1。這是一個規範化而不是優化。當有多種方式表示一個計算時,LLVM嘗試規範化到一個形式(通常任意選擇),這個形式是LLVM-PASS與後端期望看到的。由InstCombine進行的第二個改變是將計算%7與%11的兩個符號擴充套件操作規範化為零擴充套件(zext)。在編譯器可以證明sext的運算元是非負時,這是一個安全的或轉換。這裡就是這個情形,因為迴圈歸納變數從零開始,在到達n之前停止(如果n是負的,迴圈永遠不會執行)。最後的改變是向產生%10的指令新增“nuw”(沒有無符號迴繞)標記。我們可以看到這是安全,通過觀察到(1)歸納變數總是遞增的,(2)如果一個變數從零開始且遞增,在到達僅次於UINT_MAX的無符號迴繞邊界前,穿過僅次於INT_MAX的有符號迴繞邊界,它將變成未定義的。這個標記可用於證明後續優化的合理性。 再一次simplifyCFG,刪除了兩個無用的塊。   翻轉迴圈 initializer if (condition) goto BODY else goto EXIT BODY: body   modifier if (condition) goto BODY else goto EXIT EXIT: 因為現在,與原始迴圈相比,翻轉迴圈後少一個分支。,但旋轉迴圈還提供了其他優化機會。如果我們可以證明第一個條件始終為真,從而始終執行迴圈,我們可以重用迴圈中定義的值,而無需迴圈退出節點中的 phi  
global value numbering
全域性值編號,重用全域性變數。左邊迴圈裡的兩個load對應a[i]與a[i+1]。這裡GVN斷定載入a[i]是不必要的,因為來自一次迴圈迭代的a[i+1]可以轉發到下一次迭代作為a[i]。這個簡單的技巧將這個函式load指令減半。LLVM與GCC都是最近得到這個轉換的  https://blog.regehr.org/archives/1603