1. 程式人生 > >2. javascript 引擎Rhino原始碼分析 簡單程式碼分析

2. javascript 引擎Rhino原始碼分析 簡單程式碼分析

  • 1. 簡介
本例子通過執行最簡單的javascript:   var result = 1;             來分析Rhino如何解析,轉換,編譯,執行。。。
  •   2. 基本測試程式碼
public static void main(String[] args){
		//Context 用來儲存對應執行緒的資料,一個執行緒只對應唯一的context
		Context ctx = Context.enter();
		//scope表示一組javascript物件,儲存執行javascript需要的全部標準物件和全域性函式
		Scriptable scope = ctx.initSafeStandardObjects();
		//設定js優化級別,有-1到9,其中 -1表示直接解釋執行;
		ctx.setOptimizationLevel(-1);
		try{
			//傳入並執行javascript程式碼
			ctx.evaluateString(scope, "var result=1;", "", 1, null);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			Context.exit();
		}
    }
  •     3.  AST抽象語法樹轉換過程

        解析器解析及轉換AST: 

//具體見Context.compileImpl()方法;
Parser p = new Parser(compilerEnv, compilationErrorReporter);
AstRoot ast = p.parse(sourceString, sourceName, lineno);
        
  •        4. IRFactory將ast語法樹轉換成為內部表現形式

       ast語法樹轉換成為IR內部表現形式程式碼: 

//具體見Context.compileImpl()方法;
IRFactory irf = new IRFactory(compilerEnv, compilationErrorReporter);
ScriptNode tree = irf.transformTree(ast);

       主要轉換核心程式碼在irf.transformTree(ast);:

switch (node.getType()) {
case Token.SCRIPT:
    return transformScript((ScriptNode)node);
case Token.NAME:
    return transformName((Name)node);
case Token.NUMBER:
		return transformNumber((NumberLiteral)node);
...
...
}

        最終 轉換後的結果存放在Decompiler的sourceBuffer陣列:

        

  •     5. 編譯生成byteCode虛擬位元組碼

    主要生成byteCode位元組碼:

//具體見Context.compileImpl()方法;
Object bytecode = compiler.compile(compilerEnv,tree, tree.getEncodedSource(),returnFunction);

    轉換後的虛擬位元組碼:

MaxStack = 2
 [0] LINE : 1
 [3] REG_STR_C0
 [4] BINDNAME
 [5] ONE
 [6] REG_STR_C0
 [7] SETNAME
 [8] POP
 [9] RETURN_RESULT
注: 以上格式為   [pc計數器]   指令碼

    6. 直譯器順序執行byteCode指令

        6.1  程式碼分析

    6.1.1 Context的evaluateString()方法執行上述解析,編譯為位元組碼後,通過script.exec()方法解釋執行

Script script = compileString(source, sourceName, lineno,securityDomain);
if (script != null) {
    return script.exec(this, scope);
} else {
    return null;
}
              script例項主要資料有:

              itsMaxStack: 當前script最大棧深

itsICode:  儲存虛擬指令的位元組陣列

itsStringTable: 變量表字串陣列,當前例子中  itStringTable[0] = "result"

        6.1.2  直譯器通過Interpreter.interpret()迴圈執行指令

private static Object interpretLoop(Context cx, CallFrame frame, Object throwable){
    ...
    for...
    switch (op) {
        ...
    }
}

6.2 棧楨分析:

        接下來詳細講述Rhino如何迴圈執行上面的指令陣列

初始化棧楨前:

        

初始化棧楨如下所示:

        6.2.1  [0] LINE : 1

            從指令節中取2個位元組,並轉為int(行碼) 

   其處理程式碼:

    case Icode_LINE :
        frame.pcSourceLineStart = frame.pc;
        if (frame.debuggerFrame != null) {
            int line = getIndex(iCode, frame.pc);
            frame.debuggerFrame.onLineChange(cx, line);
        }
        frame.pc += 2;
        continue Loop;
當前棧楨示意圖:

   

   6.2.2 [3] REG_STR_C0

          將變量表第0個數組(result變數名)儲存到stringReg字串中

其處理程式碼:

    case Icode_REG_STR_C0:
        stringReg = strings[0];
        continue Loop;
棧楨示意圖:

6.2.3  [4] BINDNAME

從scope作用域取出名為stringReg的Scriptable,壓入棧頂stack[] 如果當前scope找不到,會到當前scope的prototype鏈上找,可見: ScriptRuntime.bind()方法;

其處理程式碼:

    case Token.BINDNAME :
        stack[++stackTop] = ScriptRuntime.bind(cx, frame.scope, stringReg);
        continue Loop;

棧楨示意圖:

6.2.4  [5] ONE

將DOUBLE_MARK標記壓入棧頂,並將值 1 存入 sDbl陣列中


6.2.5 [6] REG_STR_C0

 6.2.2


6.2.6 [7] SETNAME

包含STRICT與非STRICT模式,彈出並取出棧頂物件,如果為DOUBLE_MARK,取出並轉為數值,再從棧頂取一個物件,通過ScriptRuntime.setName()賦值給result.


6.2.7  [8] POP

從棧頂彈出一個物件


6.2.8 [9] RETURN_RESULT

作者:  阿鈿