• 沒有找到結果。

由於 CVM 的分層架構設計良好,移植到不同處理器平台所需要修改或實作的程式碼 並不多,主要可以分為兩個階段:直譯器及 JIT 編譯器[6]。

直譯器與處理器相關的程式碼不多,主要包含原生函式的呼叫者及平台相關的設定。

原生函式的呼叫者需要使用組合語言來編寫,其主要工作依序為:解析原生函式的 signature、

將存放於 java stack 的參數移動到 c stack 或暫存器中、將控制權轉移到原生函式中及將原 生函式的回傳值存回 java stack。平台相關的設定包含了位元組排列的方式、浮點運算的相 容性(是否需要額外的檢查或設定程式碼)、同步方式等。

Code Emitter IR-Gen

bytecode

IR-Parser

IR

Requests

Compiled code

值得一提的是,因為 Andes 平台的 ABI[3]規定必須使用 register 0 傳遞儲存空間的指標 供 callee 來存放 64 位元回傳值,因此會造成與回傳 32 位元值的 callee 不同的參數存放順序,

這一點在實作原生函式呼叫者時必須特別注意;另外一個比較需要注意的地方同樣因為 ABI 中的規定:在傳遞 64 位元參數時,必須對齊暫存器的編號於偶數。除了 ABI 之外,目 前我們並未實作 CAS(compare-and-set)為基礎的同步機制[6][7],而改用 pthread 中的 mutex。

主要的原因是為了加速移植的過程,並避免不完整的實作造成測試上的困難,這部份預期 在下個年度完成。

移植 JIT 編譯器的工作較直譯器複雜,雖然也是分層式的設計(參考圖三),但其中包含 較多的組件必須實作以符合平台的特性。為了加速移植過程,我們選擇與 Andes 架構較為 相似的 MIPS 版本 CVM 做為基礎來修改。主要修改部分為圖三中紅色的部份(Processor specified code )中標示為必需的部份。可以分成幾個比較重要的部份:

圖三. CVM JIT Compiler 實作架構圖

4.1.1 CPU Abstraction Interface

這部份包含了描述處理器的特色(如 delay slot、post increment load 等)、可用暫存器的 數目及其用途(參數、Java stack pointer 等)、及各種功能的選擇(如 null pointer 的處理方式)[6]。

處裡器特色的設定與處理器本身相關,直接根據目標平台定義即可。暫存器的設定是最複 雜的部份,除了必須的特定用途外(如 JSP 及 JFP 等),許多設定都是由移植者自行決定的,

這部份我們將配合下年度的計畫,調校效能時詳加考慮。另外,為了避免偵錯的困難,我 們決定暫時將許多選擇性的設定/優化關閉,這部份也預期下年度會實作完成。

4.1.2 Instruction Emitter

CVM 定義了一系列的 JIT-compiler-related functions 稱為 Emitter APIs[6],負責產生特 Processor

Specified Code Shared

RISC Code Shared Layer

Shared Code RISC Layer

Invoker/

Glue Instruction

Emitter

定功能對應的原生程式碼到記憶體(Code Cache)中。必須修改 MIPS 版本中與平台相關的 emitter APIs 使之能產生 Andes 平台的原生程式碼。相關的修改大致可分成兩類:

a. 指令格式的不同:

不同的 ISA 對於指令的編碼格式不相同,且指令的定義亦不相同,因此必須改變暫存 器及立即值等的偏移量,使之對應到正確的位置,並改變操作的代碼使之正確運作。

b. 平台架構的差異:

根據不同平台所有的架構不同,會有不同的特殊設計,例如:MIPS 架構中的 Zero 暫 存器專門用來存取立即值 0,而 Andes 架構下並沒有此功能。因此移植時,必須避開 專用的特殊架構,修改成正確的運行方式。

移植的過程是由 MIPS 架構作為來源,移向 Andes 平台,舉例如下:

/* Purpose: Emits instructions to do the specified 32 bit unary ALU operation. */

void

CVMCPUemitUnaryALU(CVMJITCompilationContext *con, int opcode, int destRegID, int srcRegID, CVMBool setcc) {

switch (opcode) {

case CVMCPU_NEG_OPCODE:

CVMCPUemitBinaryALURegister(con, CVMCPU_SUB_OPCODE, destRegID, CVMMIPS_zero, srcRegID, setcc);

break;

default:

CVMassert(CVM_FALSE);

} CVMJITdumpCodegenComments(con);

}

此函式的目的為產生 negative 操作的原生程式碼。原來的版本產生的是 Zero 暫存器減 去來源暫存器的結果存到目的暫存器的程式碼。因為 Andes Architecture 並沒有所謂的 Zero 暫存器,因此必須將該函式改寫如下:

/* Purpose: Emits instructions to do the specified 32 bit unary ALU operation. */

void

CVMCPUemitUnaryALU(CVMJITCompilationContext *con, int opcode, int destRegID, int srcRegID, CVMBool setcc)

CVMJITdumpCodegenComments(con);

}

我們將其改為產生以立即值 0 減去來源暫存器並將結果放到目的暫存器的指令,達到 同樣的效果。

除此之外,因為兩個平台的架構不同,還有相當多的問題必須解決。主要的議題如下:

a. Calling convention[3]:

因為回傳 64 位元值的方式不同於傳統的方式,在呼叫某些 helper 時必須特別調整,使 用 compiled frame 上的暫存空間來傳遞回傳值。

b. Endianness:

在 Andes 架構下指令永遠為 big endian 的排列順序。由於 JIT 編譯器概念上是把指令當 作資料來操作,當設定 CVM 的資料 endian 為 little 時就會發生問題。因為在此時產生 的指令是以 little endian 的順序存放於記憶體中,但處理器卻會假定這些指令的排列順 序為 big endian。所以在產生指令到 code buffer 之前必須多一個簡單的步驟將其調整成 正確的 endianness。

c. Delay slot:

Andes 架構並沒有 Delay slot 的設計,因此必須移去原本 MIPS 程式碼中關於 delay slot 的部份。這部分主要包含:產生放置於 delay slot 的指令、調整相關指令長度的計算方

式。

所有必須支援的 emitter API 均已完成移植的工作。由於程式直接從 MIPS 版本修改過來,

因此我們預期會有部分的程式必須針對 Andes 平台做優化的工作,才能有比較好的效能。

目前我們只完成 32 位元指令集的支援,未來將會繼續實作 16 位元指令集的支援。另外,

CVM 中沒有支援 Andes 特定指令(如 load/store multiple words 等)及特定的 addressing mode,

這部份我們將配合下年度的計畫,在調校效能及實作 16 位元指令集的支援時加以評估並以 適合的方式實作。

4.1.3 Invokers/Glue code

在移植 CVM 的 JIT 編譯器裡和處理器相關的程式碼時,有幾乎一半的工作是編寫 assembly code。這些 assembly code 負責處理一些使用 C 語言無法描述的工作(如暫存器的 設定)以及負責部分的優化工作[6][7]。這些 assembly code 主要可分為兩個項目:

a. Invoker

這個項目的程式碼負責處理從 compiled code 到 interpreter/原生程式碼(CNI/JNI)以及反 方向轉移時必需的設定(包含堆疊及暫存器等)[6][7]。以下介紹相關的兩個檔案中的內 容。

z jit_cpu.s

此檔案內的程式碼負責設定 interpreter 和 compiled code 間轉換執行權時的設定。

當 interpreter 欲切換至 compiled code 時,VM 會呼叫 CVMJITgoNative。

CVMJITgoNative 是一段必須由目的平台提供的 assembler glue code,主要是用 來設定供 compiled code 使用的 native stack frame,而 native stack frame 必須保 留記憶體供 CVMCCExecEnv 結構使用,在移植程式時,我們必須針對 Andes calling convention 的規定,將 callee-saved 暫存器(s0~s8)依序存至堆疊中。同時 由於我們暫不使用 trap-based 和 patch-based 的 GC check[7],所以必須將 explicit GC check 會使用的 CVMglobals 資料結構的記憶體位址載入至 GLOBAL_REG 中,才能正確執行 GC 動作。

另外一個重要的 assembly function 是 CVMJITexitNative,其用途是將 CVMJITgoNative 所設定的 stack frame 做展開的動作,使其回到 interpreter frame。

這段 glue code 通常是使用在當 compiled code 發生 Java exceptions 的時候。

下圖(圖四)是由 interpreter 中呼叫 compiled method 的範例。圖中顯示 java stack 和 native stack 各自變更的情況。當 Interpreter 呼叫 compiled code 時,

CVMCompiledFrame 會被放入至 Java stack 中,而 native stack 記錄著由 CVMinvokeCompiledHelper 呼叫 CVMJITgoNative 的 frame。

圖四. Calling from an interpreted method to a compiled method

目前此部分相關的程式碼已經移植完成,但仍有少部份的程式碼尚未經過測試,

因為這些程式碼負責的工作,主要是將選擇性優化技術使用到的暫存器初始化。

這部分將待明年度實作相關的優化技術時再驗證。

z ccminvokers_cpu.S

CCM invoker 扮演著 compiled code 與其他不同形式程式碼間轉換的中介角色,

例 如 用 來 呼 叫 JNI/CNI methods 或 是 透 過 interpreter 去 呼 叫 interpreted method[6][7]。

當從 compiled code 中呼叫 JNI method 時,CVMCCMinvokeJNIMethod 會先 被呼叫,這個 assembly function 內主要包含了環境的設定、stack frame 的處理 以及參數的傳遞等工作,然後會透過 CVMinvokeJNIHelper 來呼叫 JNI method。

而當從 compiled code 中呼叫 CNI method 時,則 CVMCCMinvokeCNIMethod 會被呼叫,此 assembly function 內主要包含了環境的設定及參數的傳遞等工作,

並會直接呼叫 CNI method。

至 於 透 過 interpreter 去 呼 叫 interpreted method 主 要 是 透 過 CVMCCMletInterpreterDoInvoke 來達成。如下圖五所示,在從 compiled code 中呼叫 interpreted method 時,interpreted frame 會被 push 到 Java stack 中,並 透 過 NDSexitNative0 將 native stack 內 所 保 留 的 frame ( 經 由 呼 叫 CVMJITgoNative 產生) 做 unwinding 的動作後回到 interpreter,然後執行 interpreted method。

在 ccminvokers_cpu.S 這個檔案中,有數個與執行緒同步相關的 invoker,目 前我們尚未移植優化過的版本,而是交由直譯器來處理。

圖五.Calling from a compiled method to an interpreted method

b. Glue code

在 CVM 的 JIT 編譯器設計裡,有部分較為複雜的工作(如 object allocation)是由 C 程式 碼撰寫的 helper 完成的,當 compiled code 需要執行這些功能時就會直接呼叫或透過 glue code 來呼叫 helper。

這部份的程式碼負責執行部分的 helper 工作(C 語言無法描述的工作或者為了優化),

並在必要時轉移執行到 helper 中完成要求的工作。這部份可依採用 assembler 實作 glue code 的原因分成三類[7]:

i. 由於需要透過 Helper 完成的 work,可區分成具有 fast path 或 slow path 兩 類。Fast path 的 work 較容易實作,slow path 的 work 則較為複雜且不易實 作,為了效率起見,我們將 fast path 透過 ASM helper 的方式實作,而 slow path 則採用 C helper 方式實作。

Example : CVMCCMruntimeCheckCastGlue

使 用 Checkcast 檢 查 物 件 是 否 為 預 定 資 料 類 型 時 , 首 先 會 透 過 CVMCCMruntimeCheckCastGlue 檢查 object class block 的 address 是否為 null,若不為 null 將 guess class block 載入後,再呼叫 C helper 進行 check cast 的動作。

ii. 用於呼叫 C helper 之前,重新配置變數的 ASM helper。

Example : CVMCCMruntimeThrowNullPointerExceptionGlue

當 發 生 Null pointer exception 時 , 系 統 會 先 呼 叫 CVMCCMruntimeThrowNullPointerExceptionGlue 對 compiled frames 進行 修正的動作,接著將 exception message(0)存放至$a3 後,呼叫處理 java exceptions 的 C helper function。

iii. 用於改寫呼叫此 ASM helper 的 compiled code。

Example : CVMCCMruntimeRunClassInitializerGlue

Class 初始化時,先透過 CVMCCMruntimeRunClassInitializerGlue 設定好 相關的變數後,再呼叫 CVM 中對應的 C helper function,進行 class 初始 化動作。待 class 初始化動作結束後,將 NOP 指令 patch 至原本 compiled method 中呼叫此 helper 指令的位址,避免再次執行初始化的動作。

這部分主要包含了兩個檔案:ccmglue_cpu.S 和 ccmallocators_cpu.S。在第一個檔案裡,

除了和 monitor 相關的函式尚未經過完整的測試外(因此尚未啟用,而直接轉呼叫 C helper),其他部份都已經完成實作及測試的工作。至於第二個檔案裡的所有函式,雖 已經實作完成,但同樣尚未經過完整的測試。

大部分的 glue code/invoker 已經移植完成,只有少部份因為複雜度較高且不容易除錯(如同 步相關的 invoker)尚未移植優化的版本。這些函式目前實作的方式是直接轉呼叫 interpreter 或相關的 c helper,由實驗結果得知,這樣的實作方式會影響系統的執行效能,因此我們計

大部分的 glue code/invoker 已經移植完成,只有少部份因為複雜度較高且不容易除錯(如同 步相關的 invoker)尚未移植優化的版本。這些函式目前實作的方式是直接轉呼叫 interpreter 或相關的 c helper,由實驗結果得知,這樣的實作方式會影響系統的執行效能,因此我們計

相關文件