前面我们已经实现了一个简单的虚拟机,但是它的功能非常的弱,甚至连最基本的函数调用的功能都不具备。
在接下来的几章中,我们将会实现函数调用的功能,并探索优化函数调用性能的方法
Python作为一个非常灵活和优雅的动态语言,在函数调用方面提供了非常多的特性,包括:
在Python中,我们可以通过给末尾的参数设定默认值的方式来实现可选参数的功能,例如:
def func_with_optional_parameter(a, b = 2): print a, bfunc_with_optional_parameter(1, 2)func_with_optional_parameter(1)# 这两行代码具有相同的效果,都会在屏幕上打印“1 2”
在实现类似c语言中的printf(或者说python中的str.format函数)之类的函数的时候,能够传入可变长的参数将会非常的必要。
而在python中也提供了非常丰富的语言特性来支持这样的代码,示例如下:
def func_with_varargs(a, *args): print a, argsfunc_with_varargs(1) # 这行代码会打印“1 ()”func_with_varargs(1, 2, 3, 4) # 这行代码会打印“1 (2, 3, 4)”
可以看到,这里args是一个tuple
当我们的可选参数非常多,而我们只想要指定其中特定的几个的值,其余的保留其默认值的情况下,我们可以使用命名的可选参数,示例如下:
def func_with_multiargs(a, b = 1, c = 2, d = 3): print a, b, c, dfunc_with_multiargs(1, c=3)# 这行代码的输出是“1 1 3 3”
我也不知道为何python要把这个语言特性命名成这样,但是这确实是个非常好用的功能,它可以为函数传入一些未知的命名参数,示例如下:
def func_with_kwds(a, **kwds): print a, kwdsfunc_with_kwds(1, hello='world')将会打印“1 {'hello': 'world'}”
这是一个很抽象的名词,什么叫做实参呢?和实参相对的是形参。所谓实参就是实际的参数值,而形参指的实形式上的参数。这还是很抽象,我们举个例子大家就明了了:
def hello(a, b): print a, bhello(1, 2)
在这段代码里面,hello的2个参数a、b就是形参,而下面调用函数时,传递进来的参数“1,2”就是实参,这样大家就理解了吧:)
那么什么叫做可变长的实参呢?其实就是我们在调用某个函数时,也无法知晓需要用几个参数来调用它的时候,就要用到这个可变长实参特性了,示例如下:
def func(a, b, c, d): print a, b, c, dargs = (2, 3)func(0, 1, *args)# 输出结果为"0, 1, 2, 3"
这里的效果就是args的值被展开成了调用func函数时的后2个参数
我们可以看到Python提供的语言特性非常丰富,而这丰富的语言特性也给性能的优化带来了非常大的压力。
为了实现如此丰富的语言特性,我们需要修改虚拟机的很多设计,包括数据类型和指令集。
在我们之前的虚拟机中,我们只有一种数据类型,那就是int32型,现在我们加入几种新的数据:
类型 | 说明 |
func | 函数。用以保存对某个函数的引用。 |
tuple | 元组。用以保存函数调用的参数。 |
dict | 字典。用以保存函数调用的关键词和命名可选参数。 |
这些数据类型的简单实现会随着系列文章的推进逐渐优化,所以在此之前,我们会用最简单的方式代替之。
我们回顾一下我们的虚拟机的指令集:
指令助记符 | 指令opcode | 指令说明 |
load r c | 0 | 将常量c的值赋给第r号寄存器 |
mov r0 r1 | 1 | r0 = r1 |
add r0 r1 r2 | 2 | 将寄存器r1和r2中值的和存入寄存器r0 (r0 = r1+r2) |
sub r0 r1 r2 | 3 | r0 = r1 - r2 |
inc r | 4 | 将寄存器r中的值加一(r = r + 1) |
dec r | 5 | 将寄存器r中的值减一(r = r - 1) |
cmp r0 r1 r2 | 6 | 比较r1和r2寄存器中的值,并将比较结果存入r0中(如果r1>r2则r0=1, 如果r1 |
b addr | 7 | 直接跳转到相对地址addr执行 |
bnl r addr | 8 | 如果寄存器r的值不为-1则跳转到相对地址addr执行 |
我们现在需要增加几条新的指令以实现函数调用,但是本着逐渐深入的原则,我们第一步只增加1条指令,就是call指令,同时把上一个话题中的OP_EXIT指令重命名成OP_RETNONE,作为函数返回的指令,那么指令集就变成了:
指令助记符 | 指令opcode | 指令说明 | |
load r c | 0 | 将常量c的值赋给第r号寄存器 | |
mov r0 r1 | 1 | r0 = r1 | |
add r0 r1 r2 | 2 | 将寄存器r1和r2中值的和存入寄存器r0 (r0 = r1+r2) | |
sub r0 r1 r2 | 3 | r0 = r1 - r2 | |
inc r | 4 | 将寄存器r中的值加一(r = r + 1) | |
dec r | 5 | 将寄存器r中的值减一(r = r - 1) | |
cmp r0 r1 r2 | 6 | 比较r1和r2寄存器中的值,并将比较结果存入r0中(如果r1>r2则r0=1, 如果r1 | |
b addr | 7 | 直接跳转到相对地址addr执行 | |
bnl r addr | 8 | 如果寄存器r的值不为-1则跳转到相对地址addr执行 | |
retnone | 9 | 退出函数的执行,并返回None作为函数的执行结果 | |
call func argc arg0 arg1... | 10 | 调用函数,有argc个参数,其值分别在后面的那些寄存器中 |
从下一节开始我们会先着手实现一个简单的函数调用功能。
联系客服