原本 pitifulVM 中實現 stack frame 的 op_stack
為 int32_t
type 的陣列,但在 Dhrystone 中需要可以存放 long
大小的 stack,且為了之後可以支援浮點數,將 stack 重新改寫,用 stack_frame_t
結構表示 JVM 中的一個 stack frame:
其中 stack_entry_t
為主要存放資料的地方,結構如下:
type
用來標示這個 data 的 type,目前支援存放 8 個 bytes 的資料,但這樣實作較浪費空間,不管型態為何都會佔用 8 個 bytes。之後或許可以使用 Arrays of Length Zero 的結構來減少空間浪費。
在 JVM 中操作 stack 的指令存有 bipush
, sipush
,但相對算術運算的指令只有 int
型態,bipush
會將資料放在 entry[0]
上,short
則將資料放在 entry[1]
上。n所以在取出資料時,為了方便處理,我使用 stack_to_int
函式將取出來的資料轉型成 64 bits 的整數:
這樣可以保證在 stack 中的操作都為 64 bits 的整數,才不會有類型不同的問題。
根據 Java Virtual Machine Specification
The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
我們需要一個實際空間來存放類別資訊以及物件資訊,這邊我將 heap 分成兩個區塊:
class heap
用來儲存所有類別資訊:
其內部有一個 type 為 meta_class_t *
的陣列,而 meta_class_t
實作如下:
用來存放 class 的 meta data,而 class_file_t
則當作 Runtime Area,存放程式執行時所需資料。
object heap
用來儲存所有 new
產生的物件資訊:
與 class heap
一樣為陣列,其 object_t
用來當作 Java 物件:
type
指向此物件的類別資訊,而 ptr
則指向一個陣列,此陣列之長度是由此物件的類別內 field 的數量決定。
每個 variable
代表物件內的一個 field,利用 union
來選擇該 field 的值,ptr_value
為參考 (reference) 時使用。variable_type_t
用來標示 field 的 type。
在每次要使用到 class 的時候,都會先去 class heap
中尋找有無紀錄此類別,如果找不到則開檔並呼叫 get_class
取得該類別的 constant pool 等資訊,並加入至 class_heap
當中。
此過程在每個對 class 操作的指令都會檢查一次 (e.g. new
, invoke
, getstatic
)。
new
new
指令會先檢查 class 是否存在 (如上方程式碼),找到 class 後在 object heap 中新增一個物件,其大小為 field count * sizeof(vaariable_t)
。接著尋找該 class 是否有 <clinit>
method,有的話也在此時呼叫。
根據 Java: What is the difference between <init> and <clinit>?
<clinit>
是用來初始化 static field,此過程需比<init> (初始化 non static field)
還要早。
invokespecial
為呼叫 constructor (<init>
) 時的指令,先找到 class 及 method,與 invokestatic
一樣將所需參數從 stack 中取出放入 local variable 中,且 local variable 的第 0 個位置存放該物件的指標。
再執行 execute
即可。如有繼承關係時,在 <init>
method 中會透過 invokespecial
執行 parent class 的 <init>
,此過程會一直持續直到呼叫 java.lang.Object 的 <init>
停止。
java.lang.Object 的 <init>
只有一行 return
指令,不會繼續呼叫 invokespecial
。
invokevirtual
invokevirtual
會根據 method 的 access flag,選擇執行 native method 或 java method,java method 的實作與 invokestatic
類似,而 native method 會在下方說明。
invokedynamic
invokedynamic
是 Java 7 中新增的指令
當第一次執行 invokedynamic
指令時,會呼叫對應的 bootstrap method,並回傳一個 CallSite 物件,當下次再執行相同的 invokedynamic
時,利用這個 CallSite 就能更快速的執行 method。這個指令優點在於能夠在執行時期建立需要執行的程式,常見於出現 anonymous class 的應用 (e.g. Lambda, String Concatenation)。
在 Dhrystone 中出現 invokedynamic
的地方為 String Concatenation,且為了實作方便,移除 CallSite 的功能,在每次執行 invokedynamic
時都從 bootstrap method table 尋找對應的方法來呼叫。
首先在 parse class 時透過 read_bootstrap_attribute
把 bootstrap method table 建立起來,在 invokedynamic
時會先從 constant pool 找到 tag 為 InvokeDynamic
的 entry,這個 entry 存有兩個值,分別是對應 bootstrap method 的 index 及所需要的參數的在 constant pool 中的 index。
在 bootstrap method table 找到的 method 會發現為:
java/lang/invoke/StringConcatFactory.makeConcatWithConstants
,其只需要一個 String 類型的參數,這個參數會以 unicode 的字元及 constants 組成,unicode 的部份為程式呼叫時給予的參數,而 constants 則是在編譯時期就定義好的。
考慮以下程式:
預期 javap -v
以下輸出:
str1
與 str2
被 unicode \u0001 取代,用來標示在執行時要被取代的位置,而 constant
字串則是在編譯時,就已經存入 constant pool #24 當中。
putfield
, getfield
class 檔中關於 field 的資訊只有提供 field name
, field descriptor
以及 class name
,並沒有給予 field 在 class 中的相對位置,所以這邊我先用迴圈找尋每個 field 直到找到匹配的名字後回傳。
TODO:
改用 hash 實作 的查找
ldc
, ldc2_w
這兩個指令用來將 constant pool 的資料放到 stack 中
ldc
目前只支援 int 與 string。
The run-time constant pool entry at index either must be a run-time constant of type int or float, or a reference to a string literal, or a symbolic reference to a class, method type, or method handle
ldc2_w
則只支援 long。
The run-time constant pool entry at the index must be a run-time constant of type long or double
tableswitch
根據規格書:
Immediately after the tableswitch opcode, between zero and three bytes must act as padding
第 2 ~ 3 行是為了做 Alignment,5 ~ 13 行決定 default, low, high 的位置,16 ~ 24 行根據 switch 的值決定選擇的位置。
getstatic
, putstatic
先找到 class 的資訊,接著從 class 中每個 field 比對名字與類型,找到後回傳:
如果找不到,則以 class 的 parent 繼續尋找。
getfield
, putfield
與 getstatic
, putstatic
很像,差別在於找 field 時是用 object 去尋找,透過 object 中的 field 比對,將對應的 field 位置回傳。
newarray
, multianewarray
分別透過 create_array
及 create_two_dimension_array
來實現。為了要正確釋放記憶體,我將分配出來的 array 當成物件存在 object heap 當中。
create_array
會建立一個 object,其中 object 內的 ptr 欄位為一個 variable_t
空間的位置, variable_t
內部的 ptr_value
存放 array 的位置。
create_two_dimension_array
與 create_array
類似,但這邊需要紀錄二維陣列的欄位數,所以我用一個 object 表示二維陣列,其中有三個 field,第一個 field 為二維陣列的位置,第二個 field 存二維陣列的 row 數量,第三個 field 為 column 的數量。
我將 java.lang 與 java.io 中會使用到的類別實作,並建立需要的 method 與 field,如此便能依照喜好決定哪些 method 設定為 native,也不需要把所有需要的 Java 函式庫都載入,造成 pitifulVM 沒有實作的部份被讀取到產生錯誤。
直接編譯 java.lang 與 java.io package 會產生 module conflict,需使用 javac --patch-module java.base=java
來編譯。
當 invokestatic
與 invokevirtual
中 method 的 access flag 為 ACC_NATIVE
時,便會呼叫 native.c
中對應的實做。
到這邊已經能順利執行 Dhrystone 程式,要注意程式沒有垃圾回收功能,當 heap 數量滿時會產生錯誤:
linux2020