這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
SSA概述
SSA在Go1.7中被引入,這個特性對編譯器的效能有很大的提高,但是也導致編譯過程有些減速。下面來結合網上的資糧和書籍,簡單說明一下SSA以及SSA的應用。
SSA 代表 static single-assignment,是一種IR(中間表示代碼),要保證每個變數只被賦值一次。這個能協助簡化編譯器的最佳化演算法。
y := 1 y := 2 x := y
比如上面這段代碼,y = 1
其實是停用,這個要通過定義的可達分析來確定y
是要用1還是2,而SSA有一個標識符可以稱之為版本或者“代“。
y1 := 1 y2 := 2 x1 := y2
這樣就沒有任何間接值了。用SSA表示的好處是對於同一個變數的無關使用表示成不同“代”,可以方便很多編譯器的最佳化演算法的實現。
一個概念:
Φ(讀作fai) 函數,表示要根據控制流程賦值的“代”。
例子可以參考維基裡的這一段。
三個定義:
A dominate B,如果從起點開始必須通過A到達B。也就是說A是到B的必經之路。
A strictly dominate B,如果 A dominate B,並且A和B不相等。
A 的 dominance frontier 含有B,如果A沒有strictly dominate B,但是 dominate 了B的一個前驅節點。
用遍曆的方式確定 dominance frontier 的虛擬碼。
for each node b if the number of immediate predecessors of b ≥ 2 for each p in immediate predecessors of b runner := p while runner ≠ idom(b) add b to runner’s dominance frontier set runner := idom(runner)
idom(b) 代表相鄰的strictly dominate b的結點。這樣的點只有一個,因為相鄰的點有兩個的話就不會是必經之路了。
是一個例子,2的前驅是1和7,7沒有sd(strictly dominate) 2,所以把2加入到7的DF(dominate frontiers)當中。3是7的相鄰sd,然後3不是2的sd所以2加入到3的DF當中,接著2是3相鄰的sd,然後2不是2的sd所以把2加入到2的DF中,最後遍曆到7,5和6不是7的sd所以把7加入到5和6的df當中。
df(A)可以認為是可以通過A到達的,但不是必經之路的點的集合。
有了這個定義以後就可以插入Φ函數了和重新命名了。如果X中有定義a那麼所有df(X)都需要a的Φ函數。並且Φ函數本身也是一個定義。
比如還是同一個例子。
1當中有j的定義,但是df(1)是空的,5當中有j的定義,並且5的df有7所以7當中要插入一個φ(j, j)。j現在在7中定義了(通過φ函數)所以df(7)中的2也要有φ(j, j),6也有j的定義但是7已經有了φ函數了,2的df有2,但是2已經有φ函數了。類似的方式可以應用到i和k。接著對定義重新命名就可以完成SSA的轉換了。
SSA的應用
上面只是通俗的解釋了一下SSA,沒有給出更多詳細的理論和演算法以及證明,因為證明實在難看懂,下面說一下SSA的應用。
DEAD CODE 消除
因為每個變數都有“代”(因為大家都只被賦值一次),所以很容易檢查出沒有被使用的變數並且刪除對應的定義。另外如果刪除v=x這樣的定義還要在x的use表中把這條語句刪除。
簡單的常量擴充
比如v = φ(c1,c2,...,cn)
這種格式,如果c都是相等的可以直接替換成c,或者v=c
,如果c是常量的話也可以直接替換掉。在做這些的同時也可以做其他的最佳化,能夠在一趟遍曆中都完成,比如copy propagation
,x=y
或者x=φ(y)
都可以直接用y替換掉x。比如constant folding
,x=a+b
如果a+b
是常量的話,可以直接用常量賦值。
當然還有其他的最佳化演算法,出發點都是基於SSA的簡單性。
從SSA轉換回原始代碼
y = φ(x1, x2, x3)
這樣的形式要根據條件分支再拆分回原來的形態,比如滿足1條件,使用y=x1
這樣的形式。並且可能會很自然的想把x1和x2變回使用同一個寄存器,但是通過最佳化過程中的一些手段(copy propagation)已經把大部分move指令給最佳化掉了,並且重新推回x可能會有生命週期的影響,所以還是保留了“代”。
總結
其實這個就是把def-use換成了use-def方便做代碼最佳化呀:)害我看那麼久
參考文獻
- 虎書第十九章
- 維基百科