从零开始的python解释器(1)

从零开始的python解释器(1)

June 17, 2020
从零开始的python解释器
Python, Python Interpreter

单条字节码的解释运行 #

反编译字节码 #

python 字节码形式可以用dis.dis()来反编译code object来获得,先考虑一段简单的python代码:

a = 1
b = a + 1

反编译之后是: dis.dis(code)

可见字节码拥有2Byte长度,前半部分用来标识字节码,后半是参数。其中字节码的参数仅用来标识字节码参数的位置,而不是参数本身。

字节码来源 #

code object是由compile()函数编译代码字符串生成的。在当前要进行的部分暂时关注它的一部分内容:

code.co_code = b'd\x00Z\x00e\x00d\x00\x17\x00Z\x01d\x01S\x00'
code.co_consts = (1, None)
code.co_names = ('a', 'b')

以上则是解释运行暂时需要的全部材料,其中co_code则是python字节码,co_consts用来存储需要的常数,co_names存储需要的变量。

举例:LOAD_CONST的解释 print(co_code[0])则会返回100,即是字节码, co_code[1]= 0则是字节码参数 通过dis.opname[100]则可知道字节码opname = LOAD_CONST,参数则是co_consts[0] = 1。这样第一条字节码就可以翻译为LOAD_CONST(1)

字节码执行 #

由上知道了字节码本身被翻译为LOAD_CONST(1),接下来就需要执行该函数 python vm与JVM相同,都是基于操作数栈的解释器,函数在执行后将返回值压入操作数栈 因此,LOAD_CONST()函数可以被写成以下形式

def LOAD_CONST(num):
stack.push(num)

这样,第一条字节码便执行完成


多条字节码顺序执行 #

这样可以写出字节码解释器主体的循环:

pc = 0
stack = []
def run():
    while True:
        byteName, arguments = prase_byte_code_and_argument()
        dispatch(byteName, arguments)
        if byteName = "RETURE_VALUE":
            return

此处,鉴于将来要对于循环和分支结构的支持,考虑通过判断RETURE_VALUE来返回退出循环。

prase_byte_code_and_argument()函数 #

本函数用来解析code.co_code的字节码,转换为字节码函数名和参数。

def prase_byte_code_and_argument():
    byteCode = code.co_code[pc]
    byteName = dis.opname[byteCode]
    arguments = []
    pc += 2

    if byteCode >= dis.HAVE_ARGUMENT:
        arg = code.co_code[pc-1]

        if byteCode in dis.hasconst:   # Look up a constant
            arg = code.co_consts[arg]
        elif byteCode in dis.hasname:  # Look up a name
            arg = code.co_names[arg]
        elif byteCode in dis.haslocal:  # Look up a local name
            arg = code.co_varnames[arg]
        elif byteCode in dis.hasjrel:  # Calculate a relative jump
            arg = pc + arg
        else:
            arg = arg
        arguments = [arg]
    else:
        arguments = []

return byteName, arguments

dispatch(byteName, arguments)函数 #

获取到byteName之后要得到对应的函数对象,此时使用getattr(byteName)来获取对应的函数对象 然后调用byteName(*arguments)完成执行

def dispatch(byteName, arguments):
    func = getattr(vm, byteName)
    func(*arguments)

RETURE_VALUE #

return value暂时仅作为字节码的终止,因此判断运行到此处时退出主循环。


总结 #

最简单的字节码求值器的主题就这样写成了,剩余的其他工作即是填充上未写过的字节码函数定义,这样字节码的解释求值即可以完成了。