當前位置:
首頁 > 最新 > java開發C語言編譯器:把printf編譯成java位元組碼的演算法詳解

java開發C語言編譯器:把printf編譯成java位元組碼的演算法詳解

上一節,我們探討了jvm函數調用時,參數是如何傳遞的。上節對參數傳遞的方式有個錯誤,這裡先更正一下。在上節,我是這麼說明jvm函數調用時的參數傳遞方式的:

當上面函數運行時,在執行函數lotsOfArguments前,jvm會把輸入參數全部放到堆棧上,當函數被執行時,參數會從堆棧拷貝到局部變數隊列,因此當lotsOfArguments執行前,堆棧上參數如下:

stack:

d

c

b

a

函數執行時堆棧上的參數會依次拷貝到局部變數隊列,情況如下:

local_list: d c b a 。

上面說法錯誤在於,參數從堆棧拷貝到局部隊列的次序說反了,參數拷貝到隊列後的次序應該是:

local_list: a b c d.

由此,當我們的編譯器把C含有傳遞參數的函數調用的代碼編譯成java位元組碼時,需要注意處理上面所說的參數傳遞的次序,上一節,我們使用了一個名為:getLocalVariableIndex(Symbol symbol)的函數來查找函數輸入參數對應在局部變數隊列上的位置,這裡我們需要對其實現根據上面的修改來更正一下,好在需要改正的代碼不多,內容如下:

說到有輸入參數的函數調用,在我們的C語言代碼里,用到最多的是printf,因此有必要就該函數如何編譯成java位元組碼再進行詳細的解析。假設C代碼中有這樣一條語句:

上面的代碼本意是要把整形變數x的值輸出到控制台,但我們實現的編譯器在解析上面代碼時,會先解析變數x, 當解析到一個變數x時,假設變數x在局部隊列中的次序是0,那麼我們當前實現的編譯器一旦解析到變數x時,會先輸出一條java位元組碼指令:

ILOAD 0

解析完變數x後,編譯器才會明白printf不是一個變數,而是一個函數名稱,這時我們的編譯器才會知道,要把printf與java對象System.out對應起來,於是就會把System.out對象壓入到堆棧上,因此就會輸出java位元組碼指令:

getstatic java/lang/System/out Ljava/io/PrintStream;

這樣的話就會把System.out對象壓入到堆棧上,此時堆棧的情況如下:

stack:

System.out

x

這樣一來就有問題了,要想正確列印x變數的值,堆棧上的情況應該是:

stack:

x

System.out

也就是說兩者在堆棧上的位置發生了顛倒。為了處理這個問題,需要修改一下編譯器的編譯流程,當編譯器先解讀了變數x,導致x的值先壓入堆棧,當編譯器接著解讀到printf時,我們必須先把變數x從堆棧上,因為變數x是從局部變數隊列載入到堆棧上的,也就是說上面執行完語句ILOAD 0 後,變數x同時存在在堆棧和隊列上:

stack:

x

local_list: x

此時,我們需要把x從堆棧上挪開,挪開後放到哪呢?我們把它挪開後,放到局部變數隊列的末尾,因此為了把x挪到局部變數隊列的末尾,我們接著執行指令:ISTROE 1, 於是堆棧和隊列的情況在執行指令後如下:

stack:

null

local_list: x , x

也就是說,此時變數x的值在隊列上有兩份,你會疑問直接把x的值從堆棧頂部彈掉,壓入System.out對象後,再次把x從隊列裡面再壓入堆棧不就可以了嗎,亦或者我們把x從堆棧頂部挪開時,直接挪回x原來在隊列中的位置,也就是執行ISTORE 0 不就可以了嗎。這麼做是不可以的,因為如果是這種情況:

此時我們要輸出的是x+1的值,輸出後變數x的值是不變的,編譯器解讀上面代碼時,會先解讀到x+1,於是它會把這個表達式的值壓入堆棧,形成如下情景:

stack:

x+1

local_list: x

因此如果直接把堆棧頂部元素彈開,那麼x+1的值就找不來了,如果直接把堆棧頂部的內容存回到x變數在隊列中的位置,那就變成:

stack:

null

local_list: x+1

這樣的話,變數x的內容就改變了,因此也不行,所以要把堆棧頂部元素的值存到局部變數隊列的末尾,也就是執行指令ISTORE 1:

stack:

null

local_list: x, x+1

接著把System.out壓入堆棧,也就是執行指令:

getstatic java/lang/System/out Ljava/io/PrintStream;

這時候情況如下:

stack:

System.out

local_list: x, x+1

這時候,再把隊列末尾的元素壓到堆棧頂部,也就是執行語句ILOAD 1,於是情況變成:

stack:

x+1

System.out

local_list: x, x+1

此時再調用System.out對象的print(I)V方法,也就是列印一個整形的方法,因此對應的指令就是:

invokevirtual java/io/PrintStream/print(I)V

所以綜合起來說,當編譯器編譯語句:

成為java位元組碼時,編譯後的代碼為:

我們再看複雜一點的情況:

該函數有4個輸入參數,因此函數在jvm執行時,四個輸入參數會放置在局部變數隊列上:

local_list: a, b , c , x, d

變數a處於局部變數隊列的低0處,變數b處於隊列位置為1處,變數c處於隊列位置為2處,變數x處於隊列位置為3處。由於函數還有一個局部變數d,因此變數d在隊列的末尾,也就是處於隊列位置為4處。

在編譯器解讀語句printf(「%d, %d, %d, %d」, a, b, c, x);時,根據我們前面的討論,編譯器會先解讀函數的輸入參數,也就是先解讀變數a,b,c,d,因為每解讀到一個變數是,我們的編譯器會自動執行iload語句,把變數載入到堆棧上,因此就產生了如下指令:

上面指令執行後,jvm堆棧和隊列情況如下:

stack :

x

c

b

a

local_list: a, b , c, x , d

根據前面討論,我們需要先把變數從堆棧上挪到隊列的末尾,因此編譯器要執行指令:

於是堆棧和隊列的情景如下:

stack:

null

local_list: a, b , c, x, d, x, c, b , a

這時,編譯器再把System.out壓入堆棧,也就是執行指令:

接著把處於位置8處的變數a的值再重新放回堆棧,也就是執行指令ILOAD 8,此時運行環境為:

stack:

a

System.out

local_list: a, b, c, x ,d ,x ,c ,b, a

這時調用指令invokevirtual java/io/PrintStream/print(I)V,執行System.out對象的Print函數,把a變數的值輸出到控制台。接著反覆執行這幾個步驟,把剩下幾個變數的值依次列印到控制台上,於是上面代碼中的printf語句編譯成java位元組碼後的內容如下:

大家可以看到,一條C語言語句編譯成java位元組碼時,編出的語句數量是原語句的十幾倍,這也是為何在編譯原理中,代碼優化是極為重要的一環,沒有優化技術,就算編出來的代碼能跑,但是效率也是非常低下的。

實現上面編譯功能的代碼在ClibCall.java中:

最後,我們再看看jvm的運算指令,對整形進行四種基礎運行的jvm指令為:iadd, isub, imul, idiv. 如果要計算 1+2, 那麼分別把數值1和2壓入堆棧,然後執行指令iadd, 那麼堆棧頂部的元素就會被彈出,他們的和也就是3會被壓入到堆棧,其他運算指令的原理相同。如果計算的是浮點數而不是整形,那麼就得使用對應指令,他們分別是fadd, fsub, fmul, fdiv,如果計算的是長整形,那麼對應的指令就是ladd, lsub, lmul, ldiv, 在我們實現的編譯器中,目前暫時只支持對整形的運算指令,相應的代碼實現在BinaryExecutor.java:

完成本節代碼後,我們的編譯器能將下面C代碼編譯成java位元組碼:

上面代碼編譯成java彙編代碼的結果為:

把上面java彙編代碼在編譯成二進位位元組碼運行後結果如下:

更詳細的講解和調試演示過程請參看視頻。

更多技術信息,包括操作系統,編譯器,面試演算法,機器學習,人工智慧,請關照我的公眾號:

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Coding迪斯尼 的精彩文章:

java開發系統內核:應用程序與系統內核的內存隔離
jvm的return指令以及局部變數的操作

TAG:Coding迪斯尼 |

您可能感興趣

Windows 版本的 Chrome 停用微軟的編譯器 改用 Clang
一統所有AI晶元:Facebook揭秘深度學習編譯器Glow
棄用微軟 C+編譯器,Win版Chrome 改用 Clang
Dropbox創建了一種新的Python編譯器:mypyc
C sharp 翻身?微軟重寫開源的 C sharp 編譯器!
GameMaker 偏門理論:關於反編譯器
TVM:Deep Learning模型的優化編譯器
業界 | 一統所有AI晶元:Facebook揭秘深度學習編譯器Glow
自製Monkey編程語言編譯器:增加數組操作API和Mapsh數據類型
助力 Android 抗衡 iOS,華為發布方舟編譯器!
TVM: Deep Learning模型的優化編譯器(強烈推薦, 附踩坑記錄)
Unity編譯器測試框架
藉助 Valve 的新編譯器,Linux 遊戲在 AMD GPU 中獲得了性能提升
升級Azure、開源Q#編譯器和模擬器,一文看盡微軟Build大會
Renesas Synergy增加對IAR Systems先進編譯器技術的支持,進一步鞏固該平台在IOT領域的性能優勢
解讀gcc和g++編譯器分別對c與c++文件影響
Julia官宣:為機器學習構建一種語言和編譯器
Valve為AMD GPU開發新的Mesa著色器編譯器,編譯速度更快,遊戲幀數有所提高
這個改造了Android的華為方舟編譯器,厲害不?
從GPUTurbo和方舟編譯器可看出,華為開發操作系統很容易