从零开始的python解释器(1)
June 17, 2020
单条字节码的解释运行 #
反编译字节码 #
python 字节码形式可以用dis.dis()来反编译code object来获得,先考虑一段简单的python代码:
a = 1
b = a + 1
反编译之后是:

可见字节码拥有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暂时仅作为字节码的终止,因此判断运行到此处时退出主循环。
总结 #
最简单的字节码求值器的主题就这样写成了,剩余的其他工作即是填充上未写过的字节码函数定义,这样字节码的解释求值即可以完成了。