2. javascript 引擎Rhino原始碼分析 簡單程式碼分析
- 1. 簡介
- 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
無
作者: 阿鈿