原文地址:http://www.lingcc.com/2011/08/13/11685/精確的資料流分析是讓編譯最佳化能高效進行的基礎。 SSA就是一種高效的資料流分析技術,目前幾乎所有的現代編譯器,如GCC、Open64、LLVM都有將SSA技術的支援, 不僅僅是編譯器,Jikes RVM, HotSpot JVM, .Net的Mono,Python的Pypy, Andoroid的Dalvik,這些虛擬機器/解譯器中的Just-in-Time Compiler也有了SSA的支援。
Firefox的下一代JavaScript引擎IonMonkey中,也將為其JIT引入SSA。
可以看到,幾乎所有熱門的語言所用的熱門編譯器/解譯器/虛擬機器中都有了SSA。
Table of Contents
- 1 SSA是什嗎?
- 2 SSA的作用
- 3 SSA的轉換
- 3.1 從普通中間表示到SSA
- 3.2 從SSA到普通中間表示
- 4 更複雜情況下的SSA
- 4.1 數組、指標等別名
- 4.2 堆上的儲存
- 4.3 複合結構–結構體
- 5 GCC中的SSA
- 6 open64 中的SSA
- 7 相關資料和文獻
1 SSA是什嗎?
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分別來自左右兩個分支。
2 SSA的作用
在SSA中間表示中,可以保證每個被使用的變數都有唯一的定義,即SSA能帶來精確的使用–定義關係。 而在圖SSA_example1.1中的y值定義卻非常模糊。
概括起來,SSA帶來四大益處:
- 因為SSA使得每個變數都有唯一的定義,因此資料流分析和最佳化演算法可以更加簡單
- 使用-定義關係鏈所消耗空間從指數增長降低為線性增長。若一個變數有N個使用和M個定義,若不採用SSA,則存在M×N個使用-定義關係。
- SSA中因為使用和定義的關係更加的精確,能簡化構建幹擾圖的演算法
- 來源程式中對同一個變數的不相關的若干次使用,在SSA形式中會轉變成對不同變數的使用,因此能消除很多不必要的依賴關係。
有了精確的對象使用–定義關係,許多利用使用–定義關係的最佳化就能更精確、更徹底、更高效。如
- 常數傳播
- 無作用程式碼刪除
- 全域
- 部分冗餘刪除
- 強度削弱
- 寄存器分配
2.1 SSA與寄存器分配
因為SSA使得依賴分析更加簡單、精確,而且PHI節點中的變數不可能同時活躍。因此在SSA形式能協助完成寄存器分配。 實際上,GCC最早的SSA就是GCC 3中RTL階段。
3 SSA的轉換
講了這麼多有關SSA的優點,接下來介紹一下一般編譯器構建SSA的方式。
3.1 從普通中間表示到SSA
兩步走戰略:
- 插入PHI節點: PHI節點要插在控制流程圖的匯聚點處(joint point), 只要在匯聚點之前的分支中有針對某個變數的修改, 就需要在該匯聚點插入針對該變數的PHI節點。 PHI節點的運算元是分支路徑中重新定義的變數。
- 變數重新命名: 在插入PHI節點後,SSA中所有針對變數的定義就具備了,接下來就依次在定義處重新命名變數,並替換對應的變數使用處。
此外,為了節省記憶體空間,簡化SSA上的演算法,我們需要將插入的PHI節點數目最小化。 因為PHI節點本身只是一個概念性的節點,若插入過多不必要的PHI節點,演算法就需要在控制流程圖的匯聚點針對每個分支做分析。 可以借用變數的支配邊界(dominance frontier)進行PHI節點數目最消化。一般都通過直接計算支配邊界的方式插入PHI節點。
3.2 從SSA到普通中間表示
為什麼還要從SSA轉換回去呢?很簡單,處理器不能直接執行PHI節點對應的操作。最簡單的做法,直接拷貝,如所示:
out of ssa
但這樣有一個問題,如。簡單的拷貝演算法可能改變代碼的語義:
out of ssa problem
正確的做法:
- 對PHI節點的運算元和結果重新命名,使其名稱相同,即變成同一個變數
- 再在分支中插入拷貝操作
4 更複雜情況下的SSA4.1 數組、指標等別名
上面關於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。
4.2 堆上的儲存
在堆上分配的儲存空間,一般編譯器都將整個堆看作一個對象,來做SSA。
4.3 複合結構–結構體
因為結構體也是由很多元素構成的,所以就存在兩種處理方式:把結構體整個看作一個整體做SSA、把結構體的每個元素看作一個對象做SSA。 後者相比前者,因為分的更細,在結構體操作頻繁的程式中能帶來不錯的最佳化效果。
5 GCC中的SSA
GCC的SSA
- tree-ssa.c
- tree-into-ssa.c:將函數轉換為SSA形式,插入PHI節點,對於未初始化的變數給出警告。
- tree-ssa-dce.c:掃描整個函數,標記無副作用且結果並未被使用的語句,所有儲存操作都視為有副作用。
- tree-ssa-dom.c:支配關係相關最佳化:複寫傳播、常數傳播、運算式簡化、冗餘消除、Jump Threading?
- tree-ssa-forwprop.c:前向傳播單一引用變數,通過將僅使用一次的變數用相應的運算式替代來嘗試容易刪除。
- tree-ssa-copyrename.c:嘗試將由拷貝動作產生的SSA變數用原變數替換之,最佳化符號表。
- tree-ssa-phiopt.c: 識別表達條件運算式的phi節點,並將其重寫為直線代碼。
- tree-ssa-alias.c:流敏感基於SSA的指向分析,得到可能-別名,一定-別名和逃逸分析資訊。 這些資訊將用於將變數從記憶體中地址可用對象提升為非別名變數,這樣這些變數就能使用SSA形式的分析和最佳化了。
- tree-ssa-structalias.c:用於過程間的指向分析。
- tree-sra.c:將合適的無別名局部複合變數轉換為一個標量集合,並進而轉換為SSA形式。
- tree-ssa-dse.c:刪除那些無用的儲存操作
- tree-ssa-sink.c:將儲存和指派陳述式盡量下沉到和它們的使用點接近的位置。
- tree-ssa-pre.c:部分冗餘刪除、load語句移動、完全冗餘刪除
- tree-ssa-loop.c: SSA形式的迴圈最佳化
- tree-ssa-loop-im.c:迴圈無關語句移動
- tree-ssa-loop-ivcanon.c:迴圈標準化
- tree-ssa-loop-ivopts.c:索引變數最佳化
- tree-ssa-loop-unswitch.c:將迴圈無關的條件跳轉移到迴圈外
- tree-vectorizer.c, tree-vect-analyze.c, tree-vect-transform.c:自動向量化
- tree-ssa-ccp.c:條件常數傳播
- tree-ssa-copy.c:條件複寫傳播
- tree-vrp.c:取值範圍傳播
- tree-outof-ssa.c:從SSA形式轉換回普通形式
6 open64 中的SSA
open64中的SSA主要用於迴圈嵌套最佳化、過程間最佳化以及普通的函數內最佳化。 除了迴圈變換和內聯最佳化外的所有機器無關最佳化都基於SSA做。 這部分可以說是Open64的重要賣點,對應的代碼在osprey/be/opt下。
Open64在沒有過程間最佳化時,主要以函數為單位進行,基於控制流程圖和別名分析得到的資訊構建SSA。
- opt_goto.cxx:goto語句轉換,方便做SSA
- opt_loop.cxx:迴圈正規化
- opt_sym.cxx:構建相關符號表
- opt_alias_class.cxx:別名分類,方便別名分析
- opt_cfg.cxx:構建控制流程圖,包括支配樹,不可到達代碼識別,if語句轉換
- opt_tail.cxx:尾遞迴消除
- opt_alias_analysis.cxx:流無關別名分析
- opt_ssa.cxx:構建基於WHIRL的SSA
- opt_dse.cxx:死store刪除
- opt_htable.cxx:構建HSSA–基於雜湊的全域值編號SSA
- opt_ivr.cxx:索引變數標準化
- opt_prop.cxx:複寫傳播
- opt_revise_ssa.cxx:將非直接變數展開成直接變數
- opt_dce.cxx:無作用程式碼刪除
- opt_cfg_trans.cxx:控制流程轉換
- opt_rename.cxx:SSA變數重新命名、更新
- opt_du.cxx:構建define-use資訊
- opt_etable.cxx:基於運算式的部分冗餘刪除
- opt_estr.cxx:強度削弱
- opt_ehoist.cxx:代碼提升
- opt_lftr2.cxx:線性代碼測試、替換
- opt_vn.cxx:基於值編號的完全冗餘刪除
- opt_ltable.cxx:針對load的部分冗餘刪除
- opt_stable.cxx:store Partial Redundancy Elimination 針對store的部分冗餘刪除
- opt_bdce.cxx: Bitwise dead code elimination–針對結構體
- opt_htable_emit.cxx: 從SSA轉換回WHIRL中間表示
7 相關資料和文獻
- http://en.wikipedia.org/wiki/Static_single_assignment_form
- http://en.wikipedia.org/wiki/SpiderMonkey_%28JavaScript_engine%29
- Advanced Compiler Design and Implementation, by Steven S. Muchnick, Page 252-265
- Modern Compiler Implementation in C, by Andrew W. Appel, Page 433-473
- Crafting A Compiler, by Charles N. Fischer, etc , Page 410-414
- 8. Static Single Assignment Form, by Marcus Denker,
http://marcusdenker.de/talks/08CC/08IntroSSA.pdf
- Effective Representation of Aliases and Indirect Memory Operations in SSA Form, Fred Chow, Sun Chan, etc. Compiler Construction 1996