1. 程式人生 > >《自己動手寫Java虛擬機器》學習筆記(五)指令集和直譯器

《自己動手寫Java虛擬機器》學習筆記(五)指令集和直譯器

第五章 指令集和直譯器

本章基於第三章(解析.class檔案)和第四章(執行時資料區),編寫一個建議的直譯器。

5.1 位元組碼和指令集

每一個類或者介面都可以被Java編譯器編譯成為一個.class檔案,類或介面的方法資訊就放在.class檔案的method_info結構中。如果方法不是抽象的,也不是本地方法,方法的Java程式碼就會被編譯器便已成為位元組碼(即使是空的,也有回一條return語句),存放在method_info的Code屬性。

JVM每條指令都以一個單位元組的操作碼開頭。即:操作碼最多有256個。每條操作碼都有對應的助記符。操作碼後面可以跟零位元組或多位元組的運算元。比如:getstatic,如果其運算元是0x0002,表示常量池的第二個常量。為了讓編碼後的位元組碼更加緊湊,很多操作碼本身就隱含了運算元。比如:iconst_0表示把常數0推入運算元棧。

運算元棧和區域性變量表只存放資料的值,並不記錄資料型別。那麼,指令必須要知道自己在操作什麼型別的資料。比如:iadd就是對int值進行加法。

助記符首字母及對應的java資料型別:a(reference),b(byte),c(char),d(double),f(float),i(int),l(long),s(short)。

指令按照用途分為11類:常量(constants)指令,載入(loads)指令,儲存(stores)指令,運算元棧(stack)指令,數學(math)指令,轉化(conversions)指令,比較(comparies)指令,控制(control)指令,引用(references)指令,擴充套件(extended)指令,保留(reserved)指令。

5.2 指令和指令解碼

java虛擬機器直譯器的大致邏輯:

do {
    atomically calculate pc and fetch opcode at pc;
    if(operands) fetch operands;
    execute the action for the opcode;
} while(there is more to do);

那麼我們需要一個結構體來抽象指令。基於這個結構暫時提供四種實現:NoOperandsInstruction、BranchInstruction(需要有一個offset欄位表示位移)、Index8Instruction(需要一個Index欄位。適用於儲存和載入指令。他們都需要按照單位元組索引存取區域性變數)、Index16Instruction(需要一個Index欄位適用於某些訪問執行時常量池的指令。常量池索引是兩位元組)。

位元組碼閱讀器。他需要有一個標誌記錄已經讀到了哪個位元組。我們把它成為pc。另外他需要得到方法位元組碼的全部內容。

各個指令按照其具體意義實現:

常量指令把常量推入運算元棧頂。常量來自與三個地方:隱含在操作碼裡(const系列),運算元和執行時常量池(ldc系列)。另外加上nop指令(啥也不幹)。

載入指令從區域性變量表獲取變數,然後推入運算元棧。

儲存指令把變數運算元棧頂彈出,存入區域性變量表。

棧指令直接對運算元棧進行操作。

數學指令包括算術指令、位移指令和布林運算指令。

型別轉化指令大致對應java語言中的基本型別強制型別轉換操作。

比較指令分為:1.比較結果推入運算元棧頂,2.根據比較結果跳轉。(a>b,結果為1;a==b,結果為0;a<b,結果為-1)。

控制指令:return系列和goto,tableswitch和lookupswitch。

擴充套件指令:multianewarry建立多維陣列;wide指令拓展前述指令;ifnull,ifnonnull根據引用是否是null進行跳轉;goto_w。

5.3 直譯器

1.能夠得到Code屬性。

2.執行方法需要的區域性變數,運算元棧空間以及方法位元組碼。

3.要先建立一個Thread例項。

4.要建立一個幀,並推入虛擬機器棧頂。

5.執行。

6.迴圈執行位元組碼:計算PC,解碼指令,執行指令。

5.4 Go語言語法

Go位移操作符右側必須是無符號整數。

Go語言沒有Java中的>>>操作符,為了達到目的,需要把value轉化成為無符號整數,位移操作之後,在轉回有符號整數。