程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> J2ME >> KVM的執行引擎(下)——指令集

KVM的執行引擎(下)——指令集

編輯:J2ME

指令集是虛擬機中最底層也是最核心的部分,Java程序中的變量賦值、函數調用等所有操作最後都要被轉化為一條條的指令來執行。

指令集是在Java虛擬機規范中定義的,各種虛擬機實現要給予精確的實現,下面就來介紹一下指令集的分類以及在KVM中是如何實現的。

在頭文件kvm/vmcommon/h/interpret.h中有如下對指令集種類的定義:

typedef enum {
        NOP         = 0x00,
        ACONST_NULL = 0x01,
        ICONST_M1   = 0x02,
……
        LASTBYTECODE          = 0xDF
} ByteCode ;

以及每條指令的名字:

#define BYTE_CODE_NAMES {              
    "NOP",              /*  0x00 */  
    "ACONST_NULL",      /*  0x01 */  
"ICONST_M1",        /*  0x02 */  
……
"CUSTOMCODE"            /*  0xDF */ }

Java虛擬機的指令集非常多,大概有200種左右,本篇不詳細介紹每一條指令的功能和參數,只選取幾個典型的指令作為例子,介紹它們是如何實現的。

KVM中,所有指令的實現都放在kvm/vmcommon/src/bytecodes.c中,每一條指令都遵從如下的形式:

SELECT(指令號)
    {Operations}
DONE(跳轉位置)

注:
#define SELECT(l1)                      case l1: {
#define SELECT2(l1, l2)                 case l1: case l2: {
#define SELECT3(l1, l2, l3)             case l1: case l2: case l3: {
#define SELECT4(l1, l2, l3, l4)         case l1: case l2: case l3: case l4: {
#define SELECT5(l1, l2, l3, l4, l5)     case l1: case l2: case l3: case l4: case l5: {
#define SELECT6(l1, l2, l3, l4, l5, l6) case l1: case l2: case l3: case l4: case l5: case l6: {
#define DONE(n)    } goto next##n;
#define DONEX      }
#define DONE_R     } goto reschedulePoint;

{Operations}部分是該指令的具體實現。

整個bytecodes.c文件其實是一個switch分支結構中的cases部分,這個文件中定義了所有的case。這個文件會被源文件kvm/vmcommon/src/execute.c所包含,execute.c中定義有一個方法

void SlowInterpret(ByteCode token);

它是解釋執行Java指令的主要函數,參數token就是一條指令,在本函數中會有一個switch()結構來選擇token的執行路徑: void SlowInterpret(ByteCode token) {

switch (token) {

#include "bytecodes.c"

next3:  ip++;
next2:  ip++;
next1:  ip++;
next0:
reschedulePoint:
    return;
}

函數結尾處的幾個標簽是指令完成後會跳轉到的地方。

 

依據Java虛擬機規范,虛擬機指令可以分為裝載和存儲指令、運算指令、類型轉換指令、對象創建與操縱指令、操作數棧管理指令、控制轉移指令、方法調用和返回指令、拋出和處理異常指令、實現finally指令和同步指令等10類,下面從中選取幾個簡單的指令來看一看它們是如何設計的:

1ICONST_0

說明:

無參數,向操作數棧中壓入int型常量0

實現代碼:

SELECT(ICONST_0)       /* Push integer constant 0 onto the Operand stack */
        pushStack(0);
DONE(1)
宏經適當展開後為:
case ICONST_0: {
    *++GlobalState.gs_sp = 0;
} goto next1;

GlobalState.gs_sp是當前幀內操作數棧的指針,ICONST_0指令要做的只是把指針向後移動一個字(注意是“字”而不是“字節”),然後給新字賦值為0;最後程序計數器ip自加1,表明沒有跳轉,接著執行下一條指令。

2DSTORE

說明:

本指令帶有一個字節的參數offset,作用是從操作數棧中讀取一個double型的值(雙字)並存放到局部變量區中的offsetoffset+1位置。

實現代碼:

SELECT(DSTORE)           /* Store double into local variable */
        unsigned int index = ip[1];
        lp[index+1] = popStack();
        lp[index]   = popStack();
DONE(2)
宏展開為:
case DSTORE: {
        unsigned int index = GlobalState.gs_ip[1];
        GlobalState.gs_lp[index+1] = *GlobalState.gs_sp --;
        GlobalState.gs_lp[index]   = *GlobalState.gs_sp --;
} goto next2;

首先從程序計數器的下一個字節中取出目標位置的偏移量index,然後從操作數棧中彈出兩個字分別作為double型數的底位和高位存入局部變量lp所指向的區域中的合適位置。

3I2L

說明:

無參數,將操作數棧中的當前操作數由int型轉換為long型。

實現代碼:

SELECT(I2L)                              /* Convert integer to long */
        long value = *(long *)sp;
#if BIG_ENDIAN
        ((long *)sp)[1] = value;
        ((long *)sp)[0] = value >> 31;
#elif LITTLE_ENDIAN || !COMPILER_SUPPORTS_LONG
        ((long *)sp)[1] = value >> 31;
#else
        SET_LONG(sp, value);
#endif
        getSP()++;
DONE(1)

由於longint表示的范圍大,所以在擴展時多出來的高位只是用於符號擴展。先從操作數棧中取出int型整數並把它作為一個long型,如果定義了宏BIG_ENDIAN,說明操作數棧中的存儲規則是高字節在前,這時要把value的值向後移一個字作為低字來用,高字用於作符號擴展;如果操作數棧中是低位在前的話,原位置中的字不用動,只要把下一個字作符號擴展即可。最近,由於當前操作數由一個字變為兩個字,所以sp要自加1

4LMUL

說明:

無參數;從棧中彈出兩個long型數,相乘,然後將所得long型結果壓回棧。

實現代碼:

SELECT(LMUL)                             /* Mul long */
        long64 rvalue = GET_LONG(sp - 1);
        long64 lvalue = GET_LONG(sp - 3);
        SET_LONG(sp - 3, ll_mul(lvalue, rvalue));
        getSP() -= 2;
DONE(1)

 

先從操作數棧中分別取出兩個雙字長的長整型數,使用ll_mul()宏把它們相乘(這個宏的實現是依賴於操作系統的),然後再把相乘的結果寫入棧中。整個操作從棧中彈出了四個字而壓入兩個,在過程中指針sp都沒有變過,所以最後要把sp向前移兩個字。

 

以下作為例子的都是一些簡單的指令,但並不是所有指令都這樣簡單,像對象操作、異常處理和方法調用幾類指令都十分的復雜,本篇只是演示指令的原理,所以不介紹太復雜的指令。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved