字节码解释执行

字节码的解释执行和AST的解释执行有类似之处,而且更简单,因为树形结构已经展开成顺序了,以栈虚拟机为例,为方便起见,假设所有的指令都在一个指令数组里,每个元素是一个指令对象,有code和arg两个属性,解释器入口:  
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来抛异常,不过,这个异常体系最好不要和宿主的混起来,自己设计一套比较好

你可能感兴趣的:(编程语言,语言,编译器,编译原理)