打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
高效动态语言虚拟机的设计(十一) – 函数调用(一)

前面我们已经实现了一个简单的虚拟机,但是它的功能非常的弱,甚至连最基本的函数调用的功能都不具备。
在接下来的几章中,我们将会实现函数调用的功能,并探索优化函数调用性能的方法

Python的语言特性

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 c0将常量c的值赋给第r号寄存器
mov r0 r11r0 = r1
add r0 r1 r22将寄存器r1和r2中值的和存入寄存器r0 (r0 = r1+r2)
sub r0 r1 r23r0 = r1 - r2
inc r4将寄存器r中的值加一(r = r + 1)
dec r5将寄存器r中的值减一(r = r - 1)
cmp r0 r1 r26比较r1和r2寄存器中的值,并将比较结果存入r0中(如果r1>r2则r0=1, 如果r1
b addr7直接跳转到相对地址addr执行
bnl r addr8如果寄存器r的值不为-1则跳转到相对地址addr执行

我们现在需要增加几条新的指令以实现函数调用,但是本着逐渐深入的原则,我们第一步只增加1条指令,就是call指令,同时把上一个话题中的OP_EXIT指令重命名成OP_RETNONE,作为函数返回的指令,那么指令集就变成了:

指令助记符指令opcode指令说明
load r c0将常量c的值赋给第r号寄存器
mov r0 r11r0 = r1
add r0 r1 r22将寄存器r1和r2中值的和存入寄存器r0 (r0 = r1+r2)
sub r0 r1 r23r0 = r1 - r2
inc r4将寄存器r中的值加一(r = r + 1)
dec r5将寄存器r中的值减一(r = r - 1)
cmp r0 r1 r26比较r1和r2寄存器中的值,并将比较结果存入r0中(如果r1>r2则r0=1, 如果r1
b addr7直接跳转到相对地址addr执行
bnl r addr8如果寄存器r的值不为-1则跳转到相对地址addr执行
retnone9退出函数的执行,并返回None作为函数的执行结果
call func argc arg0 arg1...10调用函数,有argc个参数,其值分别在后面的那些寄存器中

从下一节开始我们会先着手实现一个简单的函数调用功能。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
C语言函数调用栈(三)
X86-64寄存器和栈帧
C语言过程(函数)的机器级表示
[C/C++] 函数调用的栈分配
彻底搞懂系统调用
译|Python幕后(1):CPython VM 原理
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服