Object execute(Inst[] inst_list, Object[] func_arg);
由於continue和break已經被jmp指令代替了,這裏我們認爲execute的執行對應於源語言的一個函數調用,因此返回值就是源語言函數調用的返回,我們可以用null(或一個特殊的對象)來表示異常,func_arg表示對應函數的輸入參數 既然是棧虛擬機,就需要一個棧,假設用Stack對象,pop和push兩個操作,代碼依然用動態類型形式,類型統一爲Object:
int idx = 0;
Stack<Object> stk;
for (;;)
{
Inst inst = inst_list[idx]; //如果這裏越界,肯定是編譯器bug
++ idx;
switch(inst.code)
{
case CODE_LOAD:
{
stk.push(env.get(inst.arg)); //env是運行時環境的抽象
continue;
}
case CODE_STORE:
{
env.set(inst.arg, stk.pop());
continue;
}
...
case CODE_ADD:
{
Object b = stk.pop();
Object a = stk.pop();
stk.push(a.add(b)); //其實這是不好的
continue;
}
...
case CODE_DIV:
{
Object b = stk.pop();
Object a = stk.pop();
Object c = a.div(b);
if (c == null)
{
return null;
}
stk.push(c);
}
...
case CODE_JMP: //假設採用絕對地址跳轉
{
idx = inst.arg; //假設arg是個int類型,否則根據實際情況轉換
continue;
}
...
case CODE_CALL:
{
Object[] func_arg = new Object[inst.arg]; //call ARG_COUNT這種形式
for (int i = inst.arg - 1; i >= 0; -- i) //彈出參數
{
func_arg[i] = stk.pop();
}
Object func = stk.pop();
Object ret = execute(func.inst_list, func_arg); //遞歸調用解釋器解釋要call的函數
if (ret == null)
{
//異常了
return null;
}
stk.push(ret); //ret是函數調用的運算結果,壓棧繼續計算
continue;
}
...
case CODE_RETURN:
{
//棧頂是需要返回的值
return stk.pop();
}
...
default:
{
//字節碼非法,嚴重錯誤,直接退出整個解釋器
show_fatal_error("Invalid instruction");
exit(1);
}
}
//這裏不需要返回值,因爲上面是死循環
}
上面這個大結構已經能表示幾乎所有機制了,像pop_jmp_if_false之類的代碼就沒有列出來,但很容易也能想到怎麼實現的,單個字節碼的操作都是非常簡單的
需要注意上面的ADD和DIV兩個過程,ADD那個實際是錯的實現,DIV的是對的,因爲動態類型語言的運算可能是會有異常的,當然就僞代碼來說,ADD也表述清楚意思了,DIV判斷了div過程是否有異常,比如整數對象的div過程的實現可能是:
Object div(Object x)
{
if (!(x instanceof IntObj))
{
env.set_exception("Int div by non-Int");
return null;
}
IntObj i = (IntObj)x;
if (i.is_zero())
{
//除數爲0異常
env.set_exception("Zero division");
return null;
}
return new IntObj(this.value / i.value);
}
函數調用方便起見直接使用瞭解釋器本身實現語言的遞歸,也可以實現爲先壓棧再jmp代碼,這樣只需要一個execute就可以執行一個很複雜的程序,當然需要實現數據棧的壓棧操作,就像彙編一樣,兩種做法差別不算很大,後面只討論上面的遞歸做法
最後是異常機制的實現,這個上面只體現了拋出異常,而沒有捕獲的代碼,相關字節碼可能是這樣:
setup_try ON_EXC
...
@ON_EXC
...
實現的時候,需要一個try_stk保持當前函數的try棧(因爲try可以嵌套),然後在出現異常的時候:
if (c == null)
{
if (try_stk.size() > 0)
{
//有try,跳到捕獲錯誤的代碼位置
idx = try_stk.pop();
continue;
}
return null; //當前函數沒有try,拋給上一層
}
當然,如果宿主語言本身支持異常機制,如java,則可以利用throw來拋異常,不過,這個異常體系最好不要和宿主的混起來,自己設計一套比較好