1. 程式人生 > 其它 >控制流分析1

控制流分析1

控制流分析

1.基本概念

1.1支配性問題

支配結點:如果每一條從流圖的入口結點到結點n的路徑都經過結點d,那麼我們可稱d支配n,記作d dom n。t特別地,每個結點都可支配其本身。

1.1.1 構建支配樹

對於龍書上,其尋找支配結點演算法如下:
輸入:一個流圖G,G的結點集合為N,邊集合為E,入口結點是ENTRY;
輸出:對於N中的每個結點n,給出D(n),即支配n的所有結點集合;
方法: 求出如下框架中資料流問題的解。輸入流圖的基本塊就是結點。對於N中的所有結點n, D(n) = OUT[n];

域: N的冪集;

方向:前向;
傳遞函式: \(f_B(x) = x \bigcup \{B\}\)


邊界條件:OUT[ENTRY] = {ENTRY};
交匯運算: \(\bigcap\);
方程式:\(OUT[B] = f_B(IN[B])\)
\(IN[B] = \bigwedge_{P, pred(B)}OUT[P]\)
初始化設定:OUT[B] = N;
在編譯器設計中給出了具體的演算法偽碼:

n = |N| - 1; //表示所有的結點數目;
//初始化;
Dom(0) = {0}; //入口結點的支配結點初始化;
for (int i 1 to n) {
   Dom(i) = N;
}

//迭代更新的標記;
bool change = true;
while (change) {
change = false; //處理時先假定本次會處理乾淨,即不會發生變化;
for (int i = 1 to n) {
     temp = {i} + (\(\bigcap _{j \in preds(i)} Dom(j)\)

)
     if temp \(\not ={ Dom(i)}\) {
      Dom(i) = temp;
     change = true;
    }
}   

1.2順序問題

深度優先搜尋: 逐一訪問流圖中的所有結點,搜尋過程從入口結點開始,並首先訪問離入口結點最遠的結點。這樣搜尋的過程形成一個深度優先生成樹(Depth-first Spanning Tree, DFST) .。
先序遍歷首先訪問一個結點,然後從左到右遞迴地訪問該結點的子結點。
後序遍歷:首先遞迴的從左到右訪問一個結點的子結點,然後再訪問該結點本身。
深度優先排序(depth-first ordering):其會首先訪問一個結點,然後在遍歷該結點的最右子結點,在遍歷這個結點的左邊子節點,以此類推。其順序和後序遍歷順序完全相反。在為流圖生成構造生成樹之前,可以選擇把一個結點的哪個後繼作為樹中的最右子節點,哪個作為樹中的下一個子結點。 這個在編譯器設計中也叫作 逆後序(RPO, Reverse PostOrder).
下面是龍書上的相關演算法:
輸入: 一個流圖G
輸出:G中的一個DFST樹和G中的一個深度優先排序
方法:遞迴呼叫過程search(n),這個演算法首先把G的所有結點初始化為"unvisited", 然後呼叫search(\(n_0\)

), 其中\(n_0\)是G的入口結點。 當它呼叫search(n)時,會先把n標記為"visited",避免再次訪。c作為計數器,從G的結點總數一直倒計數到1。在演算法執行是把c的值賦給結點n的深度優先編號dfn[n]。邊的集合T形成了G的深度優先生成樹。

void search(n)
{
  將n標記為"visited"
 for (n的各個後繼s) {
   if (s為"unvisited")
   將邊n->s加入到T中;
   search(s);
 }
  dfn[n] = c;
 c--;
}

main() {
 T = $\Phi; //邊集合 $
  for (G的各個結點n) {
  把n標記為"unvisited";
  }
   c = G的結點個數;
   search(\(n_0\));
}

1.2.1深度優先生成樹中的邊

當一個流圖構造DFST時,流圖的邊可以分為三大類:

  1. 前進邊(advance edge),指的是那些從一個結點m到達m在樹中的一個真後代結點的邊。    
  2. 有些邊從結點m到達m在樹中的某個祖先(包括m本身),稱這些邊為後退邊(retreating edge)。
  3. 對於有些邊,在DFST中m和n都不是對方祖先,這種邊稱之為交叉邊。交叉邊一個重要的性質是:如果我們把一個結點的子節點按照他們被加入到樹中的順序從左到右排列,那麼所有的交叉邊都是從右到左的。

1.2.2 回邊和可歸約性

回邊:指一條邊a->b,它的頭b支配了它的尾a。對於任何流圖而言,任何回邊都是後退邊,但並不是所有的後退邊都是回邊。對於一個流圖而言,其對應的任何深度優先生成樹中所有後退邊都是回邊,那麼稱該流圖是可歸約的(reducible)。
意思就是,一個流圖是可歸約的,那麼其所有DFST的後退邊的集合都是相同的,並且就是流圖的回邊集合。但如果是不可歸約的,那麼必然存在一些後退邊不是回邊,那麼這樣的後退邊集合在不同DFST中表現不同。因此,如果刪除流圖中所有回邊後得到的流圖仍然是帶環的,那麼該圖就是不可歸約的。反過來也成立。
在實際中,出現的流圖幾乎都是可歸約的。如果只使用if-else-then/while-do/continue和break語句這樣的結構化控制流語句,那麼得到的程式的流圖總是可歸約的,即使是使用了goto語句,程式也經常是可歸約的,因為程式設計師在邏輯上會使用迴圈和分支的方式思考問題。

1.2.3自然迴圈

從程式分析角度看,迴圈在原始碼中以什麼形式出現並不太重要,重要的是它們是否具有易被優化的性質。我們特別關係在迴圈中是否只有一個唯一的入口結點。如果是這樣,編譯器分析可以假設某些初始化條件在迴圈中的每次迭代的開頭成立。這種優化機會引發定義"自然迴圈"的需求。
自然迴圈通過兩種重要的性質來定義:

  1. 它必須具有一個唯一的入口結點,稱為迴圈頭。這個入口結點支配了迴圈中的所有結點,否則它就不會稱為迴圈的唯一入口。
  2. 必須存在一條進入迴圈頭的回邊,否則控制流就不可能從"迴圈"中直接回到迴圈頭,也就是說實際上並沒有迴圈。

構造一條回邊的自然迴圈:
輸入:一個流圖G和一條回邊n->d;
輸出:由回邊n->d的自然迴圈中所有結點組成的集合loop;
方法: 令loop = {n, d}, 把d標記為visited,這樣在搜尋時不至於會越過結點d。從結點n開始對輸入的反向控制流圖進行深度優先搜尋。把所有訪問到的結點都加入到loop中,這個過程可以找到所有不經過d就可以到達n的結點。
除非兩個迴圈具有相同的迴圈頭,否則其要麼是分離的,要麼是一個巢狀在另一箇中。
當自然迴圈具有相同的迴圈頭且沒有哪一個迴圈真正包含在另一個迴圈中,它們將被合併在一起,當做一個迴圈處理。