日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

編譯器在經(jīng)過(guò)詞法分析、語(yǔ)法分析之后,就把源代碼變成了抽象語(yǔ)法樹(shù)(AST)

接下來(lái),編譯器的任務(wù)就是把AST變成機(jī)器碼。

AST,是一個(gè)表示代碼邏輯的樹(shù)形結(jié)構(gòu),它是不能直接順序遍歷的,而只能遞歸遍歷。

遞歸遍歷的邏輯更復(fù)雜,不適合用在機(jī)器碼、匯編碼、字節(jié)碼上。

越是底層代碼越貼近數(shù)字電路,而數(shù)字電路不適合直接處理復(fù)雜的邏輯。

遞歸也屬于復(fù)雜的邏輯,

所以C語(yǔ)言的入門程序之一就是“漢諾塔”,而數(shù)學(xué)歸納法可以成為一種常用的證明方法。

AST的樹(shù)形結(jié)構(gòu),肯定是不適合直接在CPU上運(yùn)行的,而要先把它變成機(jī)器碼。

AST是怎么變成機(jī)器碼的?

1,語(yǔ)義分析,

語(yǔ)義分析的作用有3個(gè):

1)類型檢查,

2)常量表達(dá)式的計(jì)算,

3)函數(shù)重載,

如果不是OOP語(yǔ)言,語(yǔ)義分析時(shí)不需要考慮函數(shù)重載問(wèn)題。

語(yǔ)義分析最主要的作用還是類型檢查

編譯器報(bào)出來(lái)的很多錯(cuò)誤或警告信息,都是類型檢查時(shí)給出來(lái)的,例如:

A* p = malloc(sizeof(A));

C++中就會(huì)報(bào)錯(cuò):從void指針到A的指針沒(méi)有加類型轉(zhuǎn)換(type cast)。

AST

賦值運(yùn)算符=兩邊的類型,是語(yǔ)義分析時(shí)首先要檢查的(其他運(yùn)算符也一樣)。

如果類型不匹配,在強(qiáng)類型語(yǔ)言的編譯器里就要給出錯(cuò)誤提示

另外,sizeof(A) 都是常量表達(dá)式,也要在語(yǔ)義分析時(shí)計(jì)算出來(lái)。

把常量表達(dá)式提前在編譯時(shí)計(jì)算出來(lái),可以提高運(yùn)行時(shí)的速度。

如果是腳本語(yǔ)言解釋器的話,在語(yǔ)義分析之后,就可以直接在AST運(yùn)行了:

給每個(gè)運(yùn)算符節(jié)點(diǎn)定義一個(gè)處理函數(shù),該調(diào)用哪個(gè)就調(diào)用哪個(gè),就可以得出程序的結(jié)果;

if / while / for 等分支循環(huán)的關(guān)鍵字,也可以一樣看做是運(yùn)算符。

如果是編譯器虛擬機(jī),還需要繼續(xù)往下處理。

虛擬機(jī)也是運(yùn)行機(jī)器碼的,只是和CPU的機(jī)器碼不一樣:虛擬機(jī)的機(jī)器碼一般叫字節(jié)碼。

2,三地址碼的生成,

三地址碼,是編譯器的后端常用的中間代碼。

生成了三地址碼之后,就把AST的樹(shù)形結(jié)構(gòu)變成了順序結(jié)構(gòu)了,跟匯編碼、機(jī)器碼、字節(jié)碼一樣了。

struct A { int x, y; };

A* p = malloc(sizeof(A));

生成的三地址碼是這樣的:

call ret; malloc, 8

assign p; ret

第一個(gè)詞是三地址碼的指令(opcode),分號(hào)前面的是目的操作數(shù)(dst operand),分號(hào)后面的是源操作數(shù)列表(src operands list),多個(gè)源操作數(shù)之間以逗號(hào)分隔。

按照源代碼的執(zhí)行順序,把AST遍歷一遍,就可以生成三地址碼。

源代碼里分為順序語(yǔ)句、if else語(yǔ)句、while語(yǔ)句、for語(yǔ)句等等,每種語(yǔ)句類型生成的三地址碼是不一樣的。

if else語(yǔ)句的三地址碼會(huì)有分支跳轉(zhuǎn):往函數(shù)的結(jié)尾方向跳轉(zhuǎn)。

while語(yǔ)句、for語(yǔ)句的三地址碼會(huì)有循環(huán)跳轉(zhuǎn):往函數(shù)的開(kāi)頭方向跳轉(zhuǎn)。

for (i = 0; i < 10; i++) printf("%dn", i);

上述for循環(huán)的AST和三地址碼

從圖中可以看出來(lái),三地址碼跟匯編碼的差異非常??!

匯編碼,是機(jī)器碼人類閱讀版[呲牙]

生成了三地址碼之后,把它變成機(jī)器碼還是很容易的(如果不考慮運(yùn)行效率的話)。

編譯器后端的各種優(yōu)化,主要還是為了讓生成的機(jī)器碼運(yùn)行更快,而不僅僅是為了讓機(jī)器碼運(yùn)行正確。

編譯器的后端優(yōu)化是個(gè)無(wú)底洞,因?yàn)槌绦騿T對(duì)代碼的理解總是比編譯器更深刻。

畢竟程序員是個(gè)大活人,而編譯器有賴于程序員給它升級(jí)版本。

3,正確運(yùn)行相關(guān)的優(yōu)化,

加載(load)和保存(save)指令的位置,都是跟機(jī)器碼的正確運(yùn)行相關(guān)的優(yōu)化。

除非每條運(yùn)算指令都把結(jié)果寫到內(nèi)存,否則加載和保存指令的位置必須正確。

加載指令的位置在基本塊開(kāi)頭,保存指令的位置在基本塊的末尾

基本塊,指的是沒(méi)有跳轉(zhuǎn)指令的順序語(yǔ)句塊。

for (i = 0; i < 10; i++) printf("%dn", i);

還是以前面的for循環(huán)為例子:

assign i, 0

這就是第1個(gè)基本塊,因?yàn)榻酉聛?lái)就要比較 i < 10了,而且還可能從后面跳回來(lái)多次比較。

cmp i, 10

這是第2個(gè)基本塊,接下來(lái)會(huì)根據(jù)i < 10的比較結(jié)果進(jìn)行跳轉(zhuǎn)。

call printf, "%dn", i

inc i

這兩行是第3個(gè)基本塊,它們之間也沒(méi)有跳轉(zhuǎn),而再往后就要跳轉(zhuǎn)回開(kāi)頭,再次檢查條件表達(dá)式。

跳轉(zhuǎn)的源位置和跳轉(zhuǎn)的目的位置,都是一個(gè)新的基本塊的開(kāi)始位置:

因?yàn)樗鼈儯ㄇ懊婊蚝竺妫┑拇a的執(zhí)行分支可能變化的,所以要單獨(dú)劃到一個(gè)基本塊里。

for循環(huán)的流程圖

如果在某個(gè)基本塊里修改了某個(gè)變量,就要在末尾把它保存到內(nèi)存

如果在某個(gè)基本塊里要使用某個(gè)變量,就要在開(kāi)頭把它加載到寄存器。

如上圖:

對(duì) i 賦值 0 之后要把它保存到內(nèi)存,見(jiàn)綠色的save i指令;

在比較i < 10之前要把它從內(nèi)存加載到寄存器,見(jiàn)綠色的load i指令。

這樣生成指令,就可以保證運(yùn)行結(jié)果的正確。

但為了讓它運(yùn)行的更快,一般會(huì)把循環(huán)內(nèi)的加載放到循環(huán)的開(kāi)頭,把循環(huán)內(nèi)的保存放到循環(huán)的末尾。

當(dāng)然,這么做的前提是:循環(huán)的出口入口必須都是唯一的。

goto不受待見(jiàn),從編譯器的層面來(lái)看,就是goto會(huì)導(dǎo)致循環(huán)的出口入口有多個(gè)。

4,變量之間的沖突,

互相沖突的變量不能使用相同的寄存器,這是寄存器分配的原則。

怎么判斷變量之間互相沖突呢?

就是某個(gè)變量如果在后面還要用,那么它跟其他變量就是沖突的。

例如:

a = 1;

b = 2;

c = a + b;

那么a和b就是沖突的。

因?yàn)?strong>第3行代碼都要用到它們兩個(gè),所以在第2行代碼的時(shí)候:a占用的寄存器不能騰出來(lái),b必須找其他寄存器用。

第3行代碼之后,a和b都沒(méi)用了,c可以跟a或b的任何一個(gè)使用相同的寄存器。

所以上面的代碼翻譯成匯編,可以這樣:

mov eax, 1

mov ebx, 2

add eax, ebx

a和c都使用eax,b使用ebx。

但是a和b不能都使用eax,否則結(jié)果就是錯(cuò)的。

變量之間是否沖突,要從后往前看。

編譯器里在檢查變量的沖突時(shí),都是從函數(shù)的末尾往開(kāi)頭反向遍歷每一條代碼,看看哪些變量之間是沖突的。

有興趣的可以看看我寫的scf編譯器框架的代碼,在文件scf_basic_block.c里。

5,寄存器的分配,

確定了變量之間的沖突情況之后,就可以給代碼里的變量分配寄存器了。

例如:

a = 2;

b = 3;

c = 4;

d = a + b + c;

這個(gè)例子的abc三者之間都是沖突的,因?yàn)樵诘?行同時(shí)用到了它們3個(gè)。

畫個(gè)變量的沖突圖如下:

變量的沖突圖

a, b, c之間是互相沖突的,每一對(duì)沖突的變量之間有一條連線,所以正好畫成一個(gè)三角形。

在分配寄存器時(shí),abc互相之間不能分配相同的寄存器。

也就是說(shuō),把寄存器的編號(hào)作為顏色的話,上圖三角形的3個(gè)頂點(diǎn)不能著同樣的顏色。

變量的沖突圖上,每一條邊的兩個(gè)端點(diǎn)必須著不同的顏色。

這就是用于寄存器分配的圖的著色算法。

分配完寄存器之后就好辦了,按照每種CPU的手冊(cè)生成指令的機(jī)器碼就可以了。

例如:

給a, b, c分別分配eax, ebx, ecd,

d與a共用eax,

那么匯編指令就是:

mov eax, 2

mov ebx, 3

mov ecx, 4

add eax, ebx

add eax ecx.

x64的機(jī)器碼我實(shí)在記不住,但在匯編層面,我還是可以充當(dāng)個(gè)人形編譯器的[大笑]

有興趣的可以看看我的scf編譯器框架的源碼,非常的清晰。

編譯原理(龍書)確實(shí)沒(méi)怎么細(xì)說(shuō)后端是怎么寫的,只是提了一下圖的著色算法。

不過(guò)對(duì)于我來(lái)說(shuō),我只要知道有這么個(gè)軟件,我就能把它寫出來(lái)!

求個(gè)贊賞[捂臉]

雖然重陽(yáng)一生不弱于人,但還是要靠九陰真經(jīng)來(lái)破解古墓派的武功啊。

分享到:
標(biāo)簽:編譯
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定