ssa即靜態單賦值,static single-assignment,這是一種中間表示形式。 之所以稱之為單賦值,是因為每個名字在ssa中僅被賦值一次.
如下圖中的一段程式的控制流圖。從這張圖中可以看到,最後乙個基本塊中y值的定義或者來自左側的分支,或者來自右側的分支。
ssa_example1.1
將每個賦值語句中的變數賦予乙個唯一的名稱後,一般新名稱採用原變數+版本號(version)的形式。 對於上面這段控制流圖,就變成如下形式:
ssa_example1.2
這張圖中有個問題,有分支時,若分支中有對變數的操作,就無法確定使用了哪個版本的變數。 因此,引入了phi節點。如下圖所示:
ssa_example1.3
phi將分支中的y1和y2連線,並生成乙個新的定義y3。有了phi節點後,最後乙個基本塊中y3的定義來自之前的phi節點,phi節點中的兩個運算元y1和y2分別來自左右兩個分支。
在ssa中間表示中,可以保證每個被使用的變數都有唯一的定義,即ssa能帶來精確的使用–定義關係。 而在圖ssa_example1.1中的y值定義卻非常模糊。
概括起來,ssa帶來四大益處:
有了精確的物件使用–定義關係,許多利用使用–定義關係的優化就能更精確、更徹底、更高效。如
因為ssa使得依賴分析更加簡單、精確,而且phi節點中的變數不可能同時活躍。因此在ssa形式能協助完成暫存器分配。 實際上,gcc最早的ssa就是gcc 3中rtl階段。
講了這麼多有關ssa的優點,接下來介紹一下一般編譯器構建ssa的方式。
兩步走戰略:
此外,為了節省記憶體空間,簡化ssa上的演算法,我們需要將插入的phi節點數目最小化。 因為phi節點本身只是乙個概念性的節點,若插入過多不必要的phi節點,演算法就需要在控制流圖的匯聚點針對每個分支做分析。 可以借用變數的支配邊界(dominance frontier)進行phi節點數目最消化。一般都通過直接計算支配邊界的方式插入phi節點。
為什麼還要從ssa轉換回去呢?很簡單,處理器不能直接執行phi節點對應的操作。最簡單的做法,直接拷貝,如下圖所示:
out of ssa
但這樣有乙個問題,如下圖。簡單的拷貝演算法可能改變**的語義:
out of ssa problem
正確的做法:
上面關於ssa的討論基本都是針對單個簡單變數的ssa操作,那麼對於複雜的指標、陣列之類的訪存,ssa應該如何處理呢? 陣列和指標使得編譯器無法確定define和use的具體變數。
參考資料7給出了一種定義方式,通過引入maydef,mayuse和zero version使得編譯器也能對別名(即指標和陣列)存在的程式做ssa分析。 若通過指標為其所指區域賦值,就在此處插入maydef,表示可能對變數做了定義。同理,對使用指標所指向區域的值的,就插入乙個mayuse。 因為無法確定指標所指向的到底是哪個變數,為了正確性,需要對所有變數都插入maydef動作。同樣mayuse也是針對所有變數的。
當指標操作較多時,這種方式就會引入過多的新變數版本。因此就增加了zero version。 zero version的作用就是盡量把maydef所帶來的版本數降低。 將那些很可能不會別名的都使用相同的zero version。 比如某個變數通過maydef產生了乙個新版本之後,若還會有新的maydef操作,則直接生成zero version,不再生成新的version。
在堆上分配的儲存空間,一般編譯器都將整個堆看作乙個物件,來做ssa。
因為結構體也是由很多元素構成的,所以就存在兩種處理方式:把結構體整個看作乙個整體做ssa、把結構體的每個元素看作乙個物件做ssa。 後者相比前者,因為分的更細,在結構體操作頻繁的程式中能帶來不錯的優化效果。
gcc的ssa
tree-ssa-ccp.c:條件常數傳播
tree-ssa-copy.c:條件複寫傳播
tree-vrp.c:取值範圍傳播
tree-outof-ssa.c:從ssa形式轉換回普通形式
open64中的ssa主要用於迴圈巢狀優化、過程間優化以及普通的函式內優化。 除了迴圈變換和內聯優化外的所有機器無關優化都基於ssa做。 這部分可以說是open64的重要賣點,對應的**在osprey/be/opt下。
open64在沒有過程間優化時,主要以函式為單位進行,基於控制流圖和別名分析得到的資訊構建ssa。
LCC編譯器的源程式分析 48 暫存器分配
在 lcc裡是使用非常簡單的暫存器分配演算法,並且侷限於森林裡的臨時變數的分配。下面就來分析暫存器分配的 001 int askregvar symbol p,symbol regs 011 else if p temporary 015 else if r askreg regs,vmask nu...
GCC 編譯器暫存器分配各階段主要任務
本文是閱讀了vladimir makarov的 fighting register pressure in gcc 後的小結,一些文字是直接翻譯過來的。gcc暫存器分配工作依次執行下面階段 一 regmove 對應regmove.c 當目的和源暫存器應該相同時,生成move指令,以滿足雙運算元指令的...
微軟編譯器中暫存器的使用
微軟編譯器中暫存器的使用 翻譯 本文是組合語言指南的第一篇,若你準備閱讀整個指南,你必將有所收穫。乙個暫存器就像變數,只是這種變數的數量是固定的。暫存器是cpu中用來儲存資料的地方。數學計算 加法 減法.只能在暫存器中進行 暫存器常常儲存著記憶體位址 暫存器與記憶體中資料的相互轉移也是經常的事情。i...