日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 综合教程 >内容正文

综合教程

数据流

發(fā)布時(shí)間:2024/9/19 综合教程 34 生活家
生活随笔 收集整理的這篇文章主要介紹了 数据流 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

數(shù)據(jù)流

引子

編譯器后端會(huì)對(duì)前端生成的中間代碼做很多優(yōu)化,也就是在保證程序語(yǔ)義不變的前提下,提高程序執(zhí)行的效率或減少代碼size等優(yōu)化目目標(biāo)。優(yōu)化需要依靠代碼分析給出的"指導(dǎo)信息"來(lái)相應(yīng)地改進(jìn)代碼,而代碼分析中最重要的就是數(shù)據(jù)流分析。另外數(shù)據(jù)流分析是程序靜態(tài)分析的基礎(chǔ)。所以掌握數(shù)據(jù)流分析對(duì)編譯后端極為重要。

何為數(shù)據(jù)流分析

數(shù)據(jù)流分析指的是一組用來(lái)獲取有關(guān)數(shù)據(jù)如何沿著程序執(zhí)行路徑流動(dòng)的相關(guān)信息的技術(shù)

數(shù)據(jù)分析的目的是提供一個(gè)過(guò)程(或一大段程序)如何操作其數(shù)據(jù)的全局信息。

從上面的表述中,我們可以看到數(shù)據(jù)流分析通過(guò)靜態(tài)代碼來(lái)"推斷"程序執(zhí)行的相關(guān)信息,數(shù)據(jù)流分析并不真正執(zhí)行程序。雖然數(shù)據(jù)流分析和符號(hào)執(zhí)行在某些方面比較相似,但還是兩種不同的概念,更確切的說(shuō)數(shù)據(jù)流分析是符號(hào)執(zhí)行的基礎(chǔ)。

數(shù)據(jù)流分析和符號(hào)執(zhí)行從某些方面都很相似,例如符號(hào)執(zhí)行有程序點(diǎn)(ProgramPoint)的概念,并且在當(dāng)前程序存儲(chǔ)著程序運(yùn)行到此刻的所有狀態(tài)和值信息(一般情況下不會(huì)維護(hù)歷史程序點(diǎn)的信息,開(kāi)銷太大)。數(shù)據(jù)流分析中也有程序點(diǎn)的概念,程序點(diǎn)存儲(chǔ)著數(shù)據(jù)流信息。兩者都是在CFG(Control Flow Graph)圖的基礎(chǔ)上,進(jìn)行的分析。Clang的靜態(tài)分析示意圖如下所示,Clang會(huì)時(shí)刻維護(hù)符號(hào)執(zhí)行當(dāng)前的狀態(tài)和內(nèi)存信息。從這一點(diǎn)上看,符號(hào)執(zhí)行和虛擬機(jī)更為相似。

但數(shù)據(jù)流分析和符號(hào)執(zhí)行還是不同的,雖然都有程序點(diǎn),但程序點(diǎn)存儲(chǔ)的信息卻是兩個(gè)不同的概念。數(shù)據(jù)流分析中程序點(diǎn)存儲(chǔ)的是數(shù)據(jù)流值,這些數(shù)據(jù)流值是和具體的數(shù)據(jù)流問(wèn)題相關(guān)的,有可能是當(dāng)前程序點(diǎn)的定值信息,也可能是可用表達(dá)式信息,這些信息標(biāo)識(shí)這該程序內(nèi)含的一些屬性。符號(hào)執(zhí)行中程序點(diǎn)存儲(chǔ)的是程序符號(hào)執(zhí)行到此處的所有狀態(tài)和值信息,這些信息和程序運(yùn)行更為相關(guān)。

而且兩者的分析方法也不同,符號(hào)執(zhí)行是單次執(zhí)行,而數(shù)據(jù)流分析大多采用迭代分析的框架,然后在迭代分析的過(guò)程中不斷更新程序點(diǎn)的數(shù)據(jù)流信息,最終得到比精確解更小(更保守)的解。但為了進(jìn)行更為激進(jìn)的優(yōu)化,要求數(shù)據(jù)流分析在保證保守的同時(shí)又盡可能是激進(jìn)的。

數(shù)據(jù)流抽象

在前面的文章中我們也提到過(guò),程序的執(zhí)行可以看作是程序狀態(tài)的一系列轉(zhuǎn)換。程序狀態(tài)是由程序中所有的變量的值以及運(yùn)行時(shí)棧幀上的相關(guān)值組成。程序語(yǔ)句對(duì)應(yīng)著轉(zhuǎn)換函數(shù),將前一個(gè)程序點(diǎn)的輸入程序轉(zhuǎn)換到下一個(gè)程序點(diǎn)的新的輸出狀態(tài)。

上圖所示中的紅點(diǎn)表示的就是程序點(diǎn),數(shù)據(jù)流轉(zhuǎn)換函數(shù)就是作用在程序點(diǎn)上的狀態(tài),并沿著程序路勁一步步進(jìn)行的。其實(shí)這個(gè)過(guò)程就是一個(gè)自動(dòng)機(jī),抽象出的自動(dòng)機(jī)如下所示。程序點(diǎn)代表自動(dòng)機(jī)中的一個(gè)節(jié)點(diǎn),程序語(yǔ)句或者說(shuō)是轉(zhuǎn)換函數(shù)代表自動(dòng)機(jī)中的一條邊。一般來(lái)說(shuō),一個(gè)程序有無(wú)窮多條可能的執(zhí)行路徑,執(zhí)行路徑的長(zhǎng)度并沒(méi)有上屆(例如死循環(huán))。程序分析可以推斷出各個(gè)程序點(diǎn)的程序狀態(tài)(有窮的特性集合),當(dāng)然很少有哪種數(shù)據(jù)流分析會(huì)用到所有的數(shù)據(jù)流信息,一般只是提取出感興趣的特性集合進(jìn)行分析。

我們考慮的多數(shù)數(shù)據(jù)流分析問(wèn)題關(guān)注的是各種程序?qū)ο螅ǔ?shù),變量,定值,表達(dá)式等)的集合,以及在過(guò)程內(nèi)任意一點(diǎn)這些對(duì)對(duì)象的什么集合是合法的有關(guān)判斷。另外在數(shù)據(jù)流分析中,一般是會(huì)忽略掉路徑條件判斷的,也就是說(shuō)默認(rèn)所有路徑都是可達(dá)(這種近似是正確且有效,現(xiàn)在我還沒(méi)有找到忽略條件也能保證數(shù)據(jù)流分析正確性的證明!),在程序分析中忽略掉程序控制條件,所以核心部分就是狀態(tài)數(shù)據(jù)如何變化了,也就是數(shù)據(jù)流分析。

我們雖然可以對(duì)過(guò)程的控制流圖進(jìn)行數(shù)據(jù)流分析,但通常更為有效的做法是將它分解為局部數(shù)據(jù)流分析全局?jǐn)?shù)據(jù)流分析,局部數(shù)據(jù)流分析針對(duì)每一個(gè)基本塊進(jìn)行,全局?jǐn)?shù)據(jù)流分析針對(duì)控制流圖進(jìn)行分析,其實(shí)就是一個(gè)粒度問(wèn)題。我們可以將同一個(gè)基本塊內(nèi)的各個(gè)語(yǔ)句的作用綜合起來(lái)和合成整一個(gè)基本快的作用。例如我們可以將上面的自動(dòng)機(jī)改造為基于基本塊的形式,如下圖所示:

數(shù)據(jù)流分析模式

在數(shù)據(jù)流分析中,程序點(diǎn)一般和數(shù)據(jù)流值(data-flow value)關(guān)聯(lián)起來(lái),注意這個(gè)數(shù)據(jù)流值不是程序中變量的值。"這個(gè)值是在該點(diǎn)可能觀察到的所有程序狀態(tài)的集合的抽象表示",這句話說(shuō)起來(lái)有點(diǎn)繞口,每個(gè)數(shù)據(jù)流分析問(wèn)題都有其對(duì)應(yīng)的值域,每個(gè)程點(diǎn)的數(shù)據(jù)流值都是該值域的子集。比如,到達(dá)定值的數(shù)據(jù)流值的域是程序的定值集合的所有子集的集合。某個(gè)數(shù)據(jù)流值是一個(gè)定值的集合,數(shù)據(jù)流分析的目的就是推導(dǎo)出所有程序點(diǎn)與其對(duì)應(yīng)的到達(dá)定值的集合。

一個(gè)定值是對(duì)某個(gè)變量的復(fù)制。可能沿著某條路徑到達(dá)某個(gè)程序點(diǎn)的定值稱為到達(dá)定值(reaching definition)

我們把每個(gè)語(yǔ)句s之前和之后的數(shù)據(jù)流值分別記為IN[s]OUT[s]。數(shù)據(jù)流問(wèn)題就是對(duì)一組約束求解,得到所有IN[s]和OUT[s]的結(jié)果。

每個(gè)語(yǔ)句都約束了該語(yǔ)句之前程序狀態(tài)和之后程序狀態(tài)的關(guān)系,也就是說(shuō)語(yǔ)句s限定了IN[s]和OUT[s]之間的關(guān)系。整個(gè)程序就是由無(wú)窮個(gè)這樣的約束構(gòu)成的。數(shù)據(jù)流問(wèn)題(data-flow problem)就是對(duì)這一組約束求解,另外約束不僅有語(yǔ)義(傳遞函數(shù))上的約束,更有基于控制流的約束。

傳遞函數(shù)

在一個(gè)語(yǔ)句之前和之后的數(shù)據(jù)流值受該語(yǔ)句語(yǔ)義道德約束,也就是程序語(yǔ)句前后程序點(diǎn)的數(shù)據(jù)流值受該語(yǔ)句語(yǔ)義的約束,這種約束關(guān)系稱為傳遞函數(shù)(transfer function)。

傳遞函數(shù)有兩種風(fēng)格:數(shù)據(jù)流信息可能沿著執(zhí)行路徑前向傳播,或者沿著程序路徑逆向流動(dòng),相應(yīng)就有前向(forward)數(shù)據(jù)流問(wèn)題后項(xiàng)(backward)數(shù)據(jù)流問(wèn)題。

大部分人剛接觸到后向數(shù)據(jù)流問(wèn)題時(shí)會(huì)比較困惑,數(shù)據(jù)流值怎么會(huì)依賴于后面的數(shù)據(jù)流值信息呢。其實(shí)這是由于有些人還是對(duì)于數(shù)據(jù)流值的概念不是很理解去,將數(shù)據(jù)流值簡(jiǎn)單的歸結(jié)于變量的值,如果這么對(duì)比的話,就會(huì)出現(xiàn)矛盾。

對(duì)于前向數(shù)據(jù)流問(wèn)題,一個(gè)程序語(yǔ)句S的傳遞函數(shù)以語(yǔ)句前程序點(diǎn)的數(shù)據(jù)流值作為輸入,并產(chǎn)生出語(yǔ)句之后程序點(diǎn)對(duì)應(yīng)的新數(shù)據(jù)流值。例如到達(dá)定值就是前向數(shù)據(jù)流問(wèn)題。

對(duì)于后向數(shù)據(jù)流問(wèn)題,一個(gè)程序語(yǔ)句s的傳遞函數(shù)以及語(yǔ)句后的程序點(diǎn)的數(shù)據(jù)流值作為輸入,變換為成語(yǔ)句之前程序點(diǎn)的新數(shù)據(jù)流值。例如活變量分析就是后向數(shù)據(jù)流問(wèn)題。

控制流約束

第二組關(guān)于數(shù)據(jù)流值的約束是從控制流中得到的,基本塊內(nèi)都是順序執(zhí)行,沒(méi)有控制流的約束。但是基本塊之間有相應(yīng)的控制流約束,例如一個(gè)基本塊的最后一個(gè)語(yǔ)句和后繼基本塊的第一個(gè)語(yǔ)句之間地約束,這些約束比較復(fù)雜。

基本塊上的數(shù)據(jù)流模式

前面我們已經(jīng)提到過(guò)程序語(yǔ)句的約束分為兩種,基于程序語(yǔ)句語(yǔ)義的約束和基于控制流的約束。基本塊之間的約束都是基于控制流的約束,由于基本塊內(nèi)沒(méi)有分支,所以我們可以基于整個(gè)基本塊來(lái)描述基于塊對(duì)于數(shù)據(jù)流值的約束,而不是基于程序語(yǔ)句(前面也提到過(guò),我們可以使用局部數(shù)據(jù)流和全局?jǐn)?shù)據(jù)流分析結(jié)合更加高效)。我們以基本塊為最小單位來(lái)研究基本塊上的數(shù)據(jù)流模式。

基本塊的傳遞函數(shù)和基本塊內(nèi)程序語(yǔ)句所表示的傳遞函數(shù)之間的關(guān)系如上圖所示。那么基本塊之間的約束是如何的呢?如下圖所示。

圖中展示出來(lái)的是基本塊之間的前向數(shù)據(jù)流問(wèn)題的約束方程。后向束流問(wèn)題的方程如下圖所示。

數(shù)據(jù)流分析就是根據(jù)這一組約束,得到一個(gè)滿足這些約束的解。和線性算法方程不同,數(shù)據(jù)流方程通常沒(méi)有唯一解。數(shù)據(jù)流分析的目標(biāo)是尋找一個(gè)最"精確的"滿足這兩組約束(即控制流和傳遞函數(shù))的解,當(dāng)然這個(gè)解必須是保守的,能夠保證我們根據(jù)這個(gè)解進(jìn)行代碼優(yōu)化不會(huì)導(dǎo)致不安全的轉(zhuǎn)換。

當(dāng)然數(shù)據(jù)流分析,不是直接聯(lián)立方程求解,一般是通過(guò)一種迭代分析的方法求解的。

到達(dá)定值

什么是到達(dá)值

"到達(dá)值"是最常見(jiàn)的和有用的數(shù)據(jù)流模式之一。編譯器能夠根據(jù)到達(dá)定值信息知道x在點(diǎn)p上的值是否為常量,而如果x在點(diǎn)p上被使用,則調(diào)試器可以指出x是否未經(jīng)定值就被使用。

如果存在一條從緊隨在定值d后面的程序點(diǎn)到達(dá)某一個(gè)程序點(diǎn)p的路徑,并且在這條路徑行d沒(méi)有被"殺死",我們就說(shuō)定值d到達(dá)程序點(diǎn)p。如果在這條路徑上有對(duì)變量x的其他定值,我們就說(shuō)變量x的這個(gè)定值(定值d)被"殺死"了。

到達(dá)值的示意圖如下所示。

注:上面這個(gè)圖不嚴(yán)謹(jǐn),p是程序點(diǎn),應(yīng)該緊挨著下面的矩形而不是表示矩形。圖中的矩形表示的是一條語(yǔ)句。

到達(dá)值有以下用法:

創(chuàng)建use/def鏈

常量傳播

循環(huán)不變量外提

變量x的一個(gè)定值是(可能)將一個(gè)賦值給x的語(yǔ)句。過(guò)程參數(shù),數(shù)組訪問(wèn)和間接引用都可以有別名,因此指出一個(gè)語(yǔ)句是否向特定程序變量x賦值并不是件很容易地事情。

存在別名的情況下需要作別名分析,如果為了提高分析效率而不介意損失一些分析精度的話,可以做保守估計(jì),例如我們不知道當(dāng)前語(yǔ)句對(duì)哪個(gè)變量賦值,我們就在此處針對(duì)每個(gè)變量產(chǎn)生一個(gè)定值。這是一種無(wú)奈的折中。此處我們不考慮別名情況。

到達(dá)定值的傳遞函數(shù)

首先我們做一些假設(shè):

一個(gè)語(yǔ)句節(jié)點(diǎn)至多能夠?qū)σ粋€(gè)變量定值

我們可以通過(guò)節(jié)點(diǎn)編號(hào)索引到該賦值語(yǔ)句

當(dāng)然,在實(shí)際情況中一個(gè)語(yǔ)句節(jié)點(diǎn)可能會(huì)對(duì)不止一個(gè)變量定值。下面我們定義一下gen[n]函數(shù)和kill[n]函數(shù)。

gen[n]:節(jié)點(diǎn)n產(chǎn)生的定值(假設(shè)一個(gè)語(yǔ)句節(jié)點(diǎn)至多一個(gè)定值)

kill[n]:節(jié)點(diǎn)n"殺死"的定值

程序語(yǔ)句

gen[s]

kill[s]

s: t = b op c

{s}

def[t] - {s}

s: t = M[b]

{s}

def[t] - {s}

s: M[a] = b

{s}

{*} - {s}

s: if a op b goto L

{}

{}

s:goto L

{}

{}

s: L:

{}

{}

s: f(a, …)

{}

{}

s: t = f(a, …)

{s}

def[t] - {s}

上面的表格列舉了一些程序語(yǔ)句的gen和kill傳遞函數(shù)形式。第一行的列舉的"s:t=b op c",產(chǎn)生了定值s并"殺死"了除定值s以外所有對(duì)變量t的定值。

注意:定值是一個(gè)程序,對(duì)同一個(gè)變量可以存在多個(gè)不同的定值

我們也可以先計(jì)算出各個(gè)程序語(yǔ)句的gen和kill結(jié)果,然后綜合基本塊中的各個(gè)語(yǔ)句生成整個(gè)基本塊的gen和kill集合。如下圖所示,其中我們先默認(rèn)各個(gè)基本塊的起始和結(jié)束處所有定值都可以到達(dá),下圖程序中總共有7個(gè)定值,分別為d1,d2,d3,d4,d5,d6,d7.

經(jīng)過(guò)第一次的傳遞函數(shù)作用,各個(gè)基本塊到達(dá)定值集合的變化情況如圖左所示。

到達(dá)值的保守性

在前面介紹數(shù)據(jù)流分析時(shí),曾經(jīng)提到過(guò)數(shù)據(jù)流分析允許一定的不精確性。但是它們都是在"安全"或者說(shuō)"保守"的方向上不精確。如下圖所示:

只要我們得到的解偏于保守的一方即可,然后再盡力的向精確的方向靠近,不同的應(yīng)用"保守"的定義也不同。在大部分到達(dá)定值的應(yīng)用中,在一個(gè)定值不可能到達(dá)某點(diǎn)的情況下假設(shè)其能夠到達(dá)是保守的。如下圖所示:

因此在設(shè)計(jì)一個(gè)數(shù)據(jù)流模型的時(shí)候,我們必須知道這些信息將如何被使用,并保證我們做出的任何估算都是在"保守"或者說(shuō)"安全"的方向上。每個(gè)模式合區(qū)應(yīng)用都要單獨(dú)考慮。

也就是說(shuō),不能套用同一個(gè)模式來(lái)判斷"保守"或者"安全"的方向,在可用表達(dá)式中,"安全"的定義就和到達(dá)定值不同。如果可用表達(dá)式?jīng)]有到達(dá)某個(gè)程序點(diǎn),而得出的解表明到達(dá)了,則這是不安全的。

到達(dá)定值的傳遞方程以及控制流方程

到達(dá)定值對(duì)于單個(gè)語(yǔ)句的傳遞方程如下圖所示,一個(gè)基本塊內(nèi)的依據(jù)就是按照這組方程建立起聯(lián)系的。和單個(gè)語(yǔ)句一樣,一個(gè)基本塊也會(huì)生成一個(gè)定值集合,并殺死一個(gè)定值集合。

根據(jù)基本塊之間的控制流得到的約束集合,我們可以生成一個(gè)控制流方程。其實(shí)控制流方程的含義就是在路勁交叉點(diǎn)進(jìn)行數(shù)據(jù)流值的交匯,在到達(dá)定值中,交匯運(yùn)算就是并集運(yùn)算()。

對(duì)于到達(dá)定值來(lái)說(shuō),只要一個(gè)定值能夠沿著至少一個(gè)路徑到達(dá)某個(gè)程序點(diǎn),就說(shuō)這個(gè)定值到達(dá)該程序點(diǎn)。所以控制流方程的交匯運(yùn)算時(shí)并集,但是對(duì)于其他一些數(shù)據(jù)流問(wèn)題交匯運(yùn)算時(shí)交集,例如可用表達(dá)式。

到達(dá)定值的迭代分析算法

假設(shè)每個(gè)控制流圖都有兩個(gè)空的基本塊,代表了控制流圖的ENTRY節(jié)點(diǎn)和EXIT節(jié)點(diǎn)。由于沒(méi)有定值到達(dá)這個(gè)圖的開(kāi)始,所以基本塊ENTRY的傳遞函數(shù)是一個(gè)簡(jiǎn)單的返回空集的常函數(shù),即OUT[ENTRY]=空集。

到達(dá)定值問(wèn)題使用下面方程的定義:

OUT[ENTRY]=空集

且對(duì)于所有的不等于ENTRY的基本塊B,有

OUT[B]=gen(B)(IN[B]-kill(B))

IN[B]= OUT[P],其中P是B的一個(gè)前驅(qū)基本塊

我們可以使用下面的算法來(lái)求這個(gè)方程組的解。

到達(dá)定值算法:

輸入:一個(gè)流圖,其中每個(gè)基本塊B的kill(B)集合gen(B)集都已經(jīng)計(jì)算出來(lái)了。

輸出:到達(dá)流圖中各個(gè)基本塊B的入口點(diǎn)和出口點(diǎn)的定值的集合,即IN[B]和OUT[B]。

方法:我們使用迭代的方法來(lái)求解。一開(kāi)始,我們"估計(jì)"對(duì)于所有基本塊B都有OUT[B]=空集,并逐步逼近想要的IN和OUT值。因?yàn)槲覀儽仨毑煌5氐钡礁鱾€(gè)IN值(因此各個(gè)OUT值也)收斂,所以我們使用一個(gè)bool變量change來(lái)記錄每次掃描各基本塊時(shí)是否有OUT值發(fā)生變化。

從算法中我們可以明確看到,數(shù)據(jù)流值是從前驅(qū)P到IN[B]然后在流向OUT[B]這樣一個(gè)從前向后不斷傳播的。然后從空集不斷擴(kuò)大直到越過(guò)精確解到達(dá)"保守解"。

迭代算法不斷從空向到達(dá)定值結(jié)果越來(lái)越多的方向靠近,最終會(huì)跨越精確解到達(dá)保守解的部分,主要因?yàn)閮蓚€(gè)原因?qū)е乱欢〞?huì)越過(guò)精確解:

不考慮路徑條件,假設(shè)所有路徑都可達(dá);這樣某些定值最終會(huì)到達(dá)他們本來(lái)到達(dá)不了的地方

存在別名時(shí),給無(wú)法確認(rèn)的"別名"賦值時(shí),給所有變量添加一個(gè)定值(注意此處并沒(méi)有kill掉所有的定值,因?yàn)樘砑铀锌赡艿亩ㄖ担瑒h除肯定被kill掉定值,這樣才能保證"保守")

這個(gè)算法還有可以改進(jìn)的地方,其中一個(gè)就是精心安排迭代分析時(shí)基本塊的順序,基本按照CFG從入口ENTRY到EXIT順序。如果當(dāng)前基本塊的到達(dá)定值結(jié)果發(fā)生了改變,那么就把其所有后繼基本塊加入待迭代的工作列表workList。

另外到達(dá)定值使用了一種位向量的結(jié)構(gòu),來(lái)表示到達(dá)定值集合。即每個(gè)程序點(diǎn)的到達(dá)定值使用一個(gè)為向量表示,例如該程序有7個(gè)定值,那么位向量為7,初始向量"0000000"表示此時(shí)定值為空,如果第3號(hào)定值到達(dá)了當(dāng)前程序點(diǎn),那么位向量為"0010000"

活躍變量的數(shù)據(jù)流方程

我們給出兩個(gè)定義:

def(or definition)
use

def[v] = 定義變量v的所有CFG節(jié)點(diǎn)集合
def[n] = 節(jié)點(diǎn)n定義的變量集合

use[v] = 使用變量v的CFG節(jié)點(diǎn)集合
use[n] = 在節(jié)點(diǎn)n使用的變量集合

計(jì)算活躍性的規(guī)則:

(1)產(chǎn)生活躍性

(2)活躍性如何越過(guò)程序語(yǔ)句節(jié)點(diǎn)之間的邊

(3)活躍性如何越過(guò)程序語(yǔ)句節(jié)點(diǎn)

我們列出活躍變量的數(shù)據(jù)流方程如下所示,注意此處我們將語(yǔ)義約束和控制約束同時(shí)寫出來(lái)了(因?yàn)槲覀儸F(xiàn)在是以單個(gè)程序語(yǔ)句為圖節(jié)點(diǎn),而不是單個(gè)基本塊)

in [n] = use [n] U ( out [n] - def [n] )

out [n] = U
in [s] , 其中s是節(jié)點(diǎn)n的后繼節(jié)點(diǎn)

從這兩個(gè)方程我們可以看出,對(duì)于活躍變量分析來(lái)說(shuō)數(shù)據(jù)流是從后向前傳播的。我們這里解釋一下為什么要從out[n]中刪除def[n],

下面給出活躍變量分析的算法:


注:此處CFG是以程序語(yǔ)句為單個(gè)節(jié)點(diǎn)構(gòu)建的

當(dāng)然這個(gè)算法沒(méi)有考慮到CFG圖中節(jié)點(diǎn)的順序,效率比較低,我們將CFG圖中的節(jié)點(diǎn)反序,用來(lái)求解。改進(jìn)算法如下:

我們也可以基本塊為單位來(lái)就行活躍變量的分析,但是我們得首先根據(jù)基本塊中程序語(yǔ)句的傳遞函數(shù)合并成為基本塊的傳遞函數(shù)。定義如下:

def [B] 是指如下變量的集合,這個(gè)變量在B中的定值(即被明確地)先于任何對(duì)它們的使用

use [B]是指如下變量的集合,它們的值可能在B中先于任何對(duì)它們的定值被使用

注意:上述我們標(biāo)注的黑色部分,在def[B]中需要被明確定值,而在use中條件弱化,只需要可能就行了。類似于到達(dá)定值,這樣做是為了保守性。在活躍變量中,假設(shè)變量活躍到程序結(jié)束是沒(méi)有問(wèn)題的,只是會(huì)損失些可以優(yōu)化的點(diǎn)(例如寄存器分配時(shí)兩個(gè)變量的活躍區(qū)間相互重疊的概率就會(huì)變得很大),但是如果將變量的活躍期縮短的話,有可能就將該寄存器挪做他用,這樣就會(huì)導(dǎo)致程序錯(cuò)誤。所以在活躍變量分析中,將活躍變量盡可能的向前傳播是有利于偏向保守的。但是不能一味的偏于保守,否則得到的信息就沒(méi)有任何價(jià)值,在保證保守的同時(shí),盡可能的向精確解靠攏(所以殺死被明確賦值的變量)

所以我們?cè)跉⑺雷兞繒r(shí)(即在基本塊內(nèi)明確定義,在 def[B] 中)必須明確規(guī)定,但是盡可能地向前傳播(也就是如果可能在use[B] 中,直接加入就好)。如下圖所示,我們所求得的結(jié)果必須能夠保證在保守解部分,并盡力向精確解靠近。為了保證保守性,需要做到如下兩點(diǎn):

忽略路徑分支條件,保證所有路徑都可達(dá)

只要有可能是活的,就向其中加入該變量。只有在該活躍變量被明確殺死時(shí)(例如被明確賦值),才刪除

如果以基本塊為單位,那么得到的數(shù)據(jù)流方程如下圖所示:

第一個(gè)方程描述了邊界條件,即在程序出口處沒(méi)有變量是活躍的。
第二個(gè)方程說(shuō)明一個(gè)變量要在進(jìn)入一個(gè)基本塊時(shí)活躍,必須滿足兩個(gè)條件中的一個(gè):要么它在基本塊內(nèi)被重新定值之前就被使用;要么它在基本塊的出口處活躍且在基本塊內(nèi)沒(méi)有對(duì)它進(jìn)行重新定值。
第三個(gè)方程說(shuō)明一個(gè)變量在一個(gè)基本塊的出口處活躍當(dāng)前僅當(dāng)它在該基本塊的某個(gè)后繼入口處活躍。

和到達(dá)定值相同,活躍變量不需要在后繼基本塊入口都活躍,只要在其中一個(gè)基本塊入口活躍即可。但是活躍變量是后向數(shù)據(jù)流模式。在各個(gè)數(shù)據(jù)流模式中,我們都沿著路徑傳播信息,有的數(shù)據(jù)流問(wèn)題,要求對(duì)應(yīng)性質(zhì)需要在所有路徑上都成立,而有的數(shù)據(jù)流只需要存在一個(gè)滿足該性質(zhì)的路徑即可。

基于基本塊的活躍變量分析算法:
輸入:一個(gè)流圖,其中每個(gè)基本塊的use和def已經(jīng)計(jì)算出來(lái)了。
輸出:該流圖中的各個(gè)基本塊B的入口和出口處的活躍變量集合,即 IN[ B ] 和 OUT[ B ]。

該算法得到的具有最小活躍變量(亦即盡量向精確解靠近)的集合。

可用表達(dá)式

如果從流圖入口結(jié)點(diǎn)到達(dá)程序點(diǎn) p每條路徑都對(duì)表達(dá)式 x + y 求值,且從最后一個(gè)這樣的求值之后到p點(diǎn)的路徑上沒(méi)有再次對(duì)x或y賦值,那么 x + yp 點(diǎn)上可用(available)。

注意在可用表達(dá)式的定義中,我特意加黑了每條路徑,這是和到達(dá)定值不同的,對(duì)于到達(dá)定值來(lái)說(shuō)至少存在一條這樣的路徑即可。

對(duì)于可用表達(dá)式數(shù)據(jù)流模式而言,如果一個(gè)基本塊對(duì) xy 賦值(或可能對(duì)它們賦值),并且之后沒(méi)有再重新計(jì)算 x + y,我們就說(shuō)該基本塊"殺死"了表達(dá)式 x + y

如果一個(gè)基本塊一定對(duì) x + y 求值,并且之后沒(méi)有再對(duì) xy 定值,那么這個(gè)基本塊生成表達(dá)式 x + y

可用表達(dá)式信息的主要用途就是尋找全局公共子表達(dá)式。每個(gè)程序都有有限個(gè)表達(dá)式,這有限個(gè)表達(dá)式就是可用表達(dá)式數(shù)據(jù)流分析的值域,也就是每個(gè)程序點(diǎn)的可用表達(dá)式就是這個(gè)值域的子集。

int z = x * y;
print s + t;
int w = u / v;
// ...
// program contains expressions { x * y, s + t, u / v, ...}
1
2
3
4
5

可用性是表達(dá)式在數(shù)據(jù)流中的一個(gè)屬性,"這個(gè)表達(dá)式是否計(jì)算過(guò)?"。在一條指令之前,每個(gè)表達(dá)式只能是可用或者不可用,所以通常都是從指令的角度來(lái)考慮表達(dá)式的可用性,每條指令(或者流圖中的一個(gè)結(jié)點(diǎn))都關(guān)聯(lián)著一組可用表達(dá)式。

int z = x * y;
print s + t;
int w = u / v; // 3: avail(3) = { x * y, s + t}
1
2
3

例如在結(jié)點(diǎn)3處,有兩條可用表達(dá)式"x * y""s + t"。從很多方面來(lái)看,可用表達(dá)式和活變量都有相似之處,都是數(shù)據(jù)流的一種屬性,并且在每個(gè)程序點(diǎn)都關(guān)聯(lián)著一組值的集合。在活躍變量分析中,數(shù)據(jù)流從后向前傳播,一個(gè)對(duì) x 的賦值語(yǔ)句,會(huì)"殺死"變量x的活躍性,在可用表達(dá)式的分析中,數(shù)據(jù)流從前向后傳播,一個(gè)對(duì) x 的賦值語(yǔ)句會(huì)"殺死"所有包含 x 運(yùn)算子的表達(dá)式。

除了數(shù)據(jù)流方向這一個(gè)區(qū)別之外,還有一個(gè)很重要的區(qū)別,就是在可用表達(dá)式分析中,我們必須能夠保證該表達(dá)式在當(dāng)前程序點(diǎn)絕對(duì)可用,也就是說(shuō)我們必須保證該表達(dá)式被計(jì)算過(guò)(即使有丟失可用表達(dá)式的可能),而不是該表達(dá)式在此處可能可用。也就是說(shuō)可用表達(dá)式分析是一種must分析,而活躍變量分析是一種may分析。

如果一個(gè)表達(dá)式被認(rèn)為是可用的,我們有可能會(huì)做一些比較危險(xiǎn)的事情(例如刪除重復(fù)計(jì)算該表達(dá)式的指令)。在活變量分析中,更多的活變量就更能夠保證安全性,但是在可用表達(dá)式中,越少的可用表達(dá)式才更能保證安全性。

當(dāng)程序運(yùn)行時(shí)可用表達(dá)式不可用表達(dá)式如下圖所示,這個(gè)圖表示的動(dòng)態(tài)執(zhí)行時(shí)的精確解(也就是如果某個(gè)基本塊不可能執(zhí)行到,那么這個(gè)基本塊對(duì)可用表達(dá)式分析的影響為0)。

假設(shè)有以下代碼,在數(shù)據(jù)流分析中不可能真正確切的知道哪些路徑可達(dá),所以假設(shè)所有路徑可達(dá)是安全的,雖然會(huì)損失些可以優(yōu)化的機(jī)會(huì)。

在安全的前提下,數(shù)據(jù)流分析還是會(huì)盡量向精確靠近,這樣才能把優(yōu)化發(fā)揮的更徹底。上述代碼對(duì)應(yīng)的可用表達(dá)式的圖示如下。其中 x + y 是我們?cè)跀?shù)據(jù)流分析的過(guò)程中將其殺死的,其實(shí)在真正代碼的執(zhí)行過(guò)程中,B3塊可能不會(huì)被真正執(zhí)行,也就是說(shuō) x + y 可能是可用的。但是數(shù)據(jù)流分析的第一準(zhǔn)則是安全性,然后才會(huì)在安全的前提下做更為激進(jìn)的分析。和活變量分析類似,我們盡量會(huì)在安全的前提下,向精確解靠近。

我們可以用類似于計(jì)算到達(dá)定值的方式計(jì)算可用表達(dá)式。假設(shè) U 是所有出現(xiàn)在程序中一個(gè)或多個(gè)語(yǔ)句的右部的表達(dá)式的全集。對(duì)于每個(gè)基本塊B,令IN[ B ]表示在B的開(kāi)始處可用的的U中的表達(dá)式的集合。令OUT[ B ]表示在B的結(jié)尾處可用的表達(dá)式集合。定義e_gen[B]為B生成的表達(dá)式的集合,而e_kill[B]為被B殺死的U中的表達(dá)式的集合。所以我們可以相關(guān)的數(shù)據(jù)流方程和控制流方程。

上面的方程和到達(dá)定值的方程組看起來(lái)幾乎一樣,但是一點(diǎn)很重要的區(qū)別是這個(gè)方程組的交匯運(yùn)算是交集運(yùn)算,而不是并集運(yùn)算。因?yàn)橹挥挟?dāng)一個(gè)表達(dá)式在一個(gè)基本塊的多有前驅(qū)的結(jié)尾處都可用,它才會(huì)在該基本塊的開(kāi)頭可用。

在到達(dá)定值方程的過(guò)程中,我們首先假設(shè)任何地方都沒(méi)有定值到達(dá),然后逐漸增大到到達(dá)定值的集合,最終構(gòu)建得到該解。我們最終會(huì)求解到達(dá)定值方程組,得到符號(hào)"到達(dá)"定義的最小集合

而在求解可用表達(dá)式的過(guò)程中,我們首先假設(shè)除了入口塊之外的所有基本塊的出口處,所有可用表達(dá)式都是可用的,然后不斷的將這個(gè)解縮小,直到得到一個(gè)最大的可用表達(dá)式集合的解

例如我們開(kāi)始假設(shè)所有表達(dá)式可用,然后不斷的縮減得到的解,直到越過(guò)了精確解范疇。

由于數(shù)據(jù)流分析會(huì)忽略所有的路徑條件,假設(shè)所有的路徑可達(dá),所以數(shù)據(jù)流解的集合會(huì)不斷的縮小直到一個(gè)最大的精確的安全解。

這里我們證明一下為什么考慮全路徑的情況下,一定會(huì)越過(guò)精確解。考慮下面的代碼:

(1)多考慮一個(gè)路徑,肯定會(huì)殺死一個(gè)原有的可用的表達(dá)式
(2)即使多考慮的路徑中會(huì)生成新的可用表達(dá)式,但是由于數(shù)據(jù)流方程是交集運(yùn)算,所以單單多考慮的路徑的生成還不行,還需要其他的路徑都生成該表達(dá)式,該表達(dá)式才會(huì)生成出來(lái)。也就是說(shuō),多考慮的路徑中生成的表達(dá)式其實(shí)沒(méi)有任何意義。

例如上圖中的代碼,假設(shè) B1 -> B3 -> B4 這條路徑不可達(dá),B3塊會(huì)殺死表達(dá)式x + y,雖然會(huì)生成表達(dá)式 d + c 但是由于可用表達(dá)式的交匯運(yùn)算時(shí)交集,所以必須B2塊生成表達(dá)式 d + c 才算真正生成表達(dá)式 d + c ,也就是無(wú)效路徑生成的表達(dá)式其實(shí)沒(méi)有意思的。也就是多考慮一條路徑只會(huì)殺死更多的表達(dá)式。

不知道一個(gè)表達(dá)式是可用的只會(huì)使我們失去改進(jìn)代碼的機(jī)會(huì),而把一個(gè)不可用的表達(dá)式則會(huì)使我們改變程序的計(jì)算結(jié)果。可用表達(dá)式的迭代算法如下所示:

下面我們總結(jié)一下前面所提到過(guò)的MUSTMAY分析。

特點(diǎn)

May

Must

safe

更大的集合

更小的集合

desired information

small set

large set

Gen

添加可能為真的值

只添加保證為真的值

Kill

只刪除保證為假的值

刪除所有可能為假的值

merge

union

intersection

通過(guò)上面的表格,我們可以看出May分析是盡可能向集合增大的方向前進(jìn),而Must分析是盡可能的向集合減小的方向前進(jìn)。那么有沒(méi)有一個(gè)統(tǒng)一的數(shù)據(jù)流分析框架來(lái)表示呢,不用去關(guān)注最終得到的解釋最大不動(dòng)點(diǎn)還是最小不動(dòng)點(diǎn),是用交集還是用并集等等。答案是有,后面我們介紹數(shù)據(jù)流分析中格的概念,格這種數(shù)據(jù)結(jié)構(gòu)是一個(gè)非常直觀的表示數(shù)據(jù)流分析的框架。

Sound And Complete

前面我通過(guò)可用表達(dá)式的例子說(shuō)明精確解保守解的概念,其實(shí)這種提法并不標(biāo)準(zhǔn)。下面我摘抄《A Brief Introduction to Static Analysis - Sam Blackshear》講義中的內(nèi)容。

當(dāng)我們編寫一個(gè)程序的時(shí)候,我們希望知道程序是否滿足某個(gè)屬性,例如程序P是否沒(méi)有空指針解引用(NPD),或者程序中的所有的類型轉(zhuǎn)換是否是安全的。如果對(duì)程序P進(jìn)行手工驗(yàn)證,在程序P比較復(fù)雜的時(shí)候,過(guò)程會(huì)很繁瑣。所以可以通過(guò)一個(gè)程序(或者靜態(tài)分析工具)去驗(yàn)證程序P的某些屬性是否滿足。

但是驗(yàn)證某個(gè)程序的屬性是不可判定的,見(jiàn)如何理解萊斯定理對(duì)程序靜態(tài)分析的限制。

雖然我們無(wú)法得到程序的精確解,但是我們可以使用overapproximation或者underapproximation來(lái)嘗試得到一個(gè)較為精確的解。

A sound static analysis overapproximates the behaviors of the program. A sound static analyzer is guaranteed to identify all violations of our property φ, but may also report some "false alarms", or violations of φ that cannot actully occur.

A complete static analysis underapproximates the behaviors of the program. Any violation of our property φ reported by a complete static analyzer corresponds to an actual violation of φ, but there is no guarantee that all actual violations of φ will be reported.

上面的的sound static analysis其實(shí)就對(duì)應(yīng)我們上面說(shuō)的保守解,是一種overapproximation,就是考慮程序中實(shí)際并不可行的路徑,所以能夠覆蓋完所有的違反屬性φ的場(chǎng)景,但是有誤報(bào)。

而上面的complete static analysis是一種就對(duì)應(yīng)上面我們所描述的超過(guò)精確解的值,這些值保證都違反了φ,但是并不能覆蓋完所有的值,有漏報(bào)。

Note that when a sound static analyzer reports no errors, our program is guaranteed not to violate φ! This is a powerful guarantee. As a result, most static analysis tools choose to be sound rather than complete.

但是在某些靜態(tài)分析工具中在某些場(chǎng)景中是不可能做到sound的,例如clang static analyzer,在指針場(chǎng)景中,指針ptr有可能指向任意的變量,如果要對(duì)指針ptr指向的內(nèi)存區(qū)域進(jìn)行賦值,"sound static analysis"會(huì)將程序中所有變量進(jìn)行賦值,那么繼續(xù)向下就會(huì)變得非常不精確,這是不可能接受的,整個(gè)分析過(guò)程會(huì)得不到任何有價(jià)值的信息。

總結(jié)

以上是生活随笔為你收集整理的数据流的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。