概念介紹
在介紹算法之前,我們回顧下基本概念:
- |X| :X的度數(shù),(無向圖中)節(jié)點(diǎn)的鄰居個數(shù)。
- CFG :控制流圖。
- successor :本文指CFG中基本塊的后繼。
- 四元式 :(op,result,arg1,arg2),比如常見的
a=b+c
就可以看作四元式(+,a,b,c)。 - SSA(Static Single Assignment) :靜態(tài)單賦值。
- use/def :舉個例子,對于指令
n: c <- c+b
來說 use[n]={c,b},def[n]={c}。 - live-in :當(dāng)以下任一條件滿足時,則稱變量a在節(jié)點(diǎn)n中是live-in的,寫作a∈in[n]。節(jié)點(diǎn)n本文中代表指令。
- a∈use[n];
- 存在從節(jié)點(diǎn)n到其他節(jié)點(diǎn)的路徑使用了a且不包括a的def。
- live-out : 變量a在節(jié)點(diǎn)n的任一后繼的live-in集合中。寫作a∈out[n]
- 干涉 :在某一時刻,兩個變量在同一
live-in
集合中。 - RIG(Register Interfere Graph) : 無向圖,其點(diǎn)集和邊集構(gòu)成如下:
- 節(jié)點(diǎn):變量
- 邊:如果兩節(jié)點(diǎn)存在干涉,那么這兩節(jié)點(diǎn)之間就有一條干涉邊
- k-著色 :給定無向圖G=(V,E),其中V為頂點(diǎn)集合,E為邊集合。將V分為k個組,每組中沒有相鄰頂點(diǎn),可稱該圖G是k著色的。當(dāng)然可著色前提下,k越小越好。
需要注意的是,我們后續(xù)的算法會作用在最普通的四元式上,而不是SSA。在介紹寄存器分配算法之前,我們需要活躍變量分析來構(gòu)建干涉圖。
活躍變量分析與圖著色算法
活躍變量分析
簡單來說,就是計算每個點(diǎn)上有哪些變量被使用。
算法描述如下[1]:
input: CFG = (N, E, Entry, Exit)
begin
// init
for each basic block B in CFG
in[B] = ?
// iterate
do{
for each basic block B other than Exit{
out[B] = ∪(in[s]),for all successors s of B
in[B] = use[B]∪(out[B]-def[B])
}
}until all in[] do't change
活躍變量分析還有孿生兄弟叫Reaching Definitions,不過實(shí)現(xiàn)功能類似,不再贅述。
舉個例子:對圖1的代碼進(jìn)行活躍變量分析
圖1[2]
可以得到每個點(diǎn)的活躍變量如圖2所示:
圖2
過程呢?限于篇幅,僅僅計算第一輪指令1的結(jié)果,剩余部分讀者可自行計算。
步驟 | 下標(biāo) | out | in |
---|---|---|---|
第一次迭代 | 1 | {} | {b,c} |
... | ... | ... | ... |
可畫出RIG如圖3:
圖3
圖著色
經(jīng)過上文的活躍變量分析,我們得到了干涉圖,下一步對其進(jìn)行上色。
但是圖著色是一個NP問題,我們會采用啟發(fā)式算法對干涉圖進(jìn)行著色?;舅悸肥牵?/p>
- 找到度小于k的節(jié)點(diǎn);
- 從圖中刪除;
- 判斷是否為可著色的圖;
- 迭代運(yùn)行前3步直到著色完成。
算法描述[3]:
input: RIG, k
// init
stack = {}
// iterate
while RIG != {} {
t := pick a node with fewer than k neighbors from RIG // 這里RIG可以先按度數(shù)排序節(jié)點(diǎn)再返回
stack.push(t)
RIG.remove(t)
}
// coloring
while stack != {} {
t := stack.pop()
t.color = a color different from t's assigned colored neighbors
}
對于例子1,假設(shè)有4個寄存器r1、r2、r3、r4可供分配。
步驟 | stack | RIG |
---|---|---|
0 | {} | |
1 | {a} | |
2 | {d,a} | |
所以圖3中的RIG是4-著色
的。但如果只有三種顏色可用,怎么辦呢?
沒關(guān)系,我們還有大容量的內(nèi)存,雖然速度慢了那么一點(diǎn)點(diǎn)。著色失敗就把變量放在內(nèi)存里,用的時候再取出來。
依然是上例,但是k=3,只有三個顏色。
如果f的鄰居是2-著色
的就好了,但不是。那就只能選一個變量存入內(nèi)存了。這里我們選擇將變量f
溢出至內(nèi)存。溢出后的IR和RIG如圖:
圖4 溢出后的IR
圖5 溢出后的RIG
所以,溢出其實(shí)是分割了變量的生命周期以降低被溢出節(jié)點(diǎn)的鄰居數(shù)量。溢出后的著色圖如圖6:
圖6 著色后的圖5
這里溢出變量f
并不是明智的選擇,關(guān)于如何優(yōu)化溢出變量讀者可自行查閱資料。
至此,圖著色算法基本介紹完畢。不過,如果代碼中的復(fù)制指令,應(yīng)該怎么處理呢?
寄存器分配之前會有Copy Propagation和Dead Code Elimination優(yōu)化掉部分復(fù)制指令,但是兩者并不是全能的。
比如:代碼段1中,我們可以合并Y和X。但是代碼段2中Copy Propagation就無能為力了,因?yàn)榉种?dǎo)致不同的Y值。
// 代碼段1
X = ...
A = 10
Y = X
Z = Y + A
return Z
// 代碼段2
X= A + B
Y = C
if (...) {Y = X}
Z = Y + 4
所以,寄存器分配算法也需要對復(fù)制指令進(jìn)行處理。如何處理?給復(fù)制指令的源和目標(biāo)分配同一寄存器。
那么如何在RIG中表示呢?如果把復(fù)制指令的源和目標(biāo)看作 RIG中相同的節(jié)點(diǎn) ,自然會分配同一寄存器。
- 相同節(jié)點(diǎn)?可以擴(kuò)展RIG:新增虛線邊,代表合并候選人。
- 成為合并候選人的條件是:如果X和Y的生命周期不重合,那么對于
Y=X
指令中的X和Y是可合并的。 - 為了保證合并合法且不造成溢出:合并后局部的度數(shù)
那么如何計算局部的度數(shù)?介紹三種算法:
- 簡單算法
- Brigg's 算法
- George's 算法
- 簡單算法:
(|X|+|Y|)
,很保守的算法但是可能會錯過一些場景
比如k=2時,圖7應(yīng)用簡單算法是沒辦法合并的
圖7[3]
但明顯圖7可以合并成圖8:
圖8[3] - Brigg's 算法:X和Y可合并,如果X和Y中度數(shù)≥k的鄰居個數(shù)<k。但是如果X的度數(shù)很大,算法效率就不高
- George's算法:X和Y可合并,如果對Y的每個鄰居T,|T|
?比如k=2時,圖9就可以合并X和Y。
圖9[3]
相對于Brigg算法、George算法不用遍歷節(jié)點(diǎn)的鄰居。注意,圖著色時可以按節(jié)點(diǎn)度數(shù)從小到大依次訪問。
到此,圖著色算法介紹完畢。
-
寄存器
+關(guān)注
關(guān)注
31文章
5301瀏覽量
119863 -
編譯器
+關(guān)注
關(guān)注
1文章
1617瀏覽量
49019 -
CFG
+關(guān)注
關(guān)注
0文章
10瀏覽量
9794
發(fā)布評論請先 登錄
相關(guān)推薦
評論