翻译自《Python Virtual Machine》

Python 虚拟机

每个函数对象都和以下的三个结构:

1。包含参数的局部变量名称(in .__code__.varnames)

2。全局变量名称(in .__code__.co_names)

3。常数(in .__code__.co_consts)

 

在python定义函数的时候创建这些结构,它们被定义在函数对应的__code__对象。

 

如果我们定义如下:

Def minimum(alist):
 
 
    m=None if let(alist) ==0 else Alist[0]
 
 
    for v in alist[1:]:
 
 
        if vim:
 
 
            m = v
 
 
    return m
 
 
 
 
 
我们得到
 
 
minimum.__code__.co_varnames is ('alist','m','v')
 
 
minimum.__code__.co_names is ('len','None')
 
 
minimum.__code__.co_consts is (None,0,1)

 

用于索引的数字+load 运算符(LOAD_FAST、LOAD_GLOBAL、LOAD_CONST都会在之后讨论)。

 

在PVM中主要的数据结构式“regular”栈(由一连串的push、pop组成)。对栈的主要操作就是load/push和store/pop。我们在栈顶load/push一个值,栈向上扩展,伴随着栈指针上移指向栈顶。同样,store/pop一个栈顶值时,栈指针下移。

 

还有一个次要的block栈用于从循环嵌套、try和指令中取出值。比如,一个断点指令在block栈中被编码,用于判断哪个循环块被n断下(并如何继续执行循环外的指令)。当循环,try/except和指令开始执行时,他们的信息被push到block栈上;当它们结束时从堆栈上弹出。这种块block stack对于现在来说太过麻烦,不必要去理解:所以当我们遇到有关block stack 的指令时,会指出将其忽略的原因。

 

这儿有个有关栈操作的简单例子,计算 d=a+b*c。假设a、b、c、d都是一个函数中的局部变量:co_varnames =('a','b','c','d')且这些符号对应的实际值被存放在并行元组中:(1,2,3,none)。符号在元组中的位置与其值在元组的位置是一一对应的。

 

LOAD_FAST N
 
 
load/push 将co_varnames[N]对应的值压入栈,stackp+=1,stack[stackp] = co_varnames[N]
 
 
 
 
 
STORE_FAST N
 
 
store/pop 将栈顶的值放入co_varnames[N], co_varnames[N] = stack[stackp], stackp-=1
 
 
 
 
 
BINARY_MULTIPLY
 
 
将‘*’的两个运算数压入栈,stack[stackp-1]=stack[stackp-1]*stack[stack];stackp-=1(将栈顶的两个值转化为它们的乘积)
 
 
 
 
 
BINARY_ADD
 
 
将‘+’的两个运算数压入栈,stack[stackp-1]=stack[stackp-1]+stack[stack];stackp-=1(将栈顶的两个值转化为它们的和)
 
 
 
 
 
d = a+b*c  的PVM code:
 
 
LOAD_FAST 0
 
 
LOAD_FAST 1
 
 
LOAD_FAST 2
 
 
BINARY_MULTIPLY
 
 
BINARY_ADD
 
 
STORE_FAST 3
 
 
 
 
 
 
 
 
初始状态:
 
 
co_varnames =('a','b','c','d')
 
 
values=(1,2,3,none)
 
 
     +--------------------+
 
 
3    |                    |
 
 
     +--------------------+
 
 
2    |                    | 
 
 
     +--------------------+
 
 
1    |                    | 
 
 
     +--------------------+
 
 
0    |                    |
 
 
     +--------------------+
 
 
stack (with stackp=-1,it is an empty stack)
 
 
 
 
 
LOAD_FAST 0:
 
 
     +--------------------+
 
 
3    |                    |
 
 
     +--------------------+
 
 
2    |                    | 
 
 
     +--------------------+
 
 
1    |                    | 
 
 
     +--------------------+
 
 
0    |     1: value of a  |
 
 
     +--------------------+
 
 
stack(with stackp=0)
 
 
 
 
 
LOAD_FAST 1:
 
 
     +--------------------+
 
 
3    |                    |
 
 
     +--------------------+
 
 
2    |                    | 
 
 
     +--------------------+
 
 
1    |    2: value of b  | 
 
 
     +--------------------+
 
 
0    |    1: value of a  |
 
 
     +--------------------+
 
 
stack (with stackp=1)
 
 
 
 
 
LOAD_FAST 2:
 
 
     +--------------------+
 
 
3    |                    |
 
 
     +--------------------+
 
 
2    |   3: value of c   | 
 
 
     +--------------------+
 
 
1    |    2: value of b  | 
 
 
     +--------------------+
 
 
0    |    1: value of a  |
 
 
     +--------------------+
 
 
stack (with stackp=2)
 
 
 
 
 
BINARY_MULTIPLY:
 
 
     +--------------------+
 
 
3    |                    |
 
 
     +--------------------+
 
 
2    |                    | 
 
 
     +--------------------+
 
 
1    |   6: value of b*c | 
 
 
     +--------------------+
 
 
0    |   1: value of a   |
 
 
     +--------------------+
 
 
stack (with stackp=1)
 
 
 
 
 
BINARY_ADD:
 
 
     +--------------------+
 
 
3    |                    |
 
 
     +--------------------+
 
 
2    |                    | 
 
 
     +--------------------+
 
 
1    |                    | 
 
 
     +--------------------+
 
 
0    | 7: value of a+b*c |
 
 
     +--------------------+
 
 
stack (with stackp=0)
 
 
 
 
 
STORE_FAST 3:
 
 
     +--------------------+
 
 
3    |                    |
 
 
     +--------------------+
 
 
2    |                    | 
 
 
     +--------------------+
 
 
1    |                    | 
 
 
     +--------------------+
 
 
0    |                    |
 
 
     +--------------------+
 
 
stack (with stackp=-1)
 
 
co_varnames =('a','b','c','d')
 
 
values=(1,2,3,7)

PVM的控制流

 

在PVM的每个指令都包含了1~3字节的信息。第一个字节是操作标识或字节码,后面的两字节是字节码的操作数(但并不是所有的字节码都需要操作数:BINARY_ADD就不需要)。两字节能够表示0~65536:所以python的函数中不能有超过65536个不同的局部变量。

 

指令被储存在内存中:把内存也看作一种储存有次序的数据的列表结构。

Memory          Instruction
 
 
Location  
 
 
0               LOAD_FAST 0
 
 
3               LOAD_FAST 1
 
 
6               LOAD_FAST 2
 
 
9               BINARY_MULTIPLY
 
 
10              BINARY_ADD
 
 
11              STORE_FAST 3

把内存列表命名为m

第一条指令被存储在m[0],后一指令存储在高3或高1的位置处(占3字节:有些指令有明确操作数的:load/store。有些指令有隐含的操作数:stack 、pc。占1字节:没有操作数的指令:binary运算)

 

一旦这些指令被加载进内存后,PVM按照一个简单的规则执行他们。执行周期赋予了计算机生命,这是计算机科学的基础。

(1)从m [pc]开始获取操作及其操作数(如果存在)

(2)pc + = 3(如果操作数存在)或pc + = 1(如果没有操作数存在)

(3)执行操作码(可能更改其操作数,堆栈,堆栈或pc)

(4)转到步骤1

 

一些运算会操作stack/stackp和存变量值的元组,一些会改变pc(比如jump指令)。

所以pc初始时0,PVM执行上述代码以以下流程:

  1.获取操作m [0],操作数m [1]和m [2]

  2.将pc递增至3

  3.操纵堆栈(见上文)

  4.回到步骤1

 

  1.取m [3]的操作,m [4]和m [5]

  2.将pc增加到6

  3.操纵堆栈(见上文)

  4.回到步骤1

 

  1.取m [6]和m [7]和m [8]的操作数,

  2.将pc增加到9

  3.操纵堆栈(见上文)

  4.回到步骤1

 

  1.获取操作a m [9]:它没有操作数

  2.将pc增加到10

  3.操纵堆栈(见上文)

  4.回到步骤1

 

  1.获取操作m [10]:它没有操作数

  2.将pc增加到11

  3.操纵堆栈(见上文)

  4.回到步骤1

 

内存中指向此处时,没有代码可以执行。在下一个例子中我们可以看到PVM如何执行一个更复杂的代码。

 

如简要介绍的那样,我们可以用dis.py模块中使用dis函数打印任何Python函数(和模块/类也可以)的注释描述;这里我们打印函数。 

 

def addup(alist):
     sum=0
     for v in alist:
          sum = sum + v
     return sum

  

 

这个例子用来显示一般函数对象的有用的信息(它的名称,它的三个元组,和反编译信息)

 

def func_obj(fo):
     print(fo.__name__)
     print('  co_varnames:',fo.__code__.co_varnames)
     print('  co_names   :',fo.__code__.co_names)
     print('  co_consts  :',fo.__code__.co_consts,'\n')
     print('Source Line m operation/byte-code   operand (useful name/number)\n'+69*'-')
     dis.dis(fo)
 
calling func_obj(addup) prints
 
addup
co_varnames: ('alist', 'sum', 'v')
co_names : ()
co_consts : (None, 0)
Source Line m      op/byte-code  operand (useful name/number)
---------------------------------------------------------------------
2           0      LOAD_CONST    1 (0)
            3      STORE_FAST    1 (sum)
 
3           6      SETUP_LOOP    24 (to 33)
            9      LOAD_FAST     0 (alist)
            12     GET_ITER
         >> 13     FOR_ITER      16 (to 32)
            16     STORE_FAST    2 (v)
 
4           19     LOAD_FAST     1 (sum)
            22     LOAD_FAST     2 (v)
            25     BINARY_ADD
            26     STORE_FAST    1 (sum)
            29     JUMP_ABSOLUTE 13
         >> 32     POP_BLOCK
 
5        >> 33     LOAD_FAST     1 (sum)
            36     RETURN_VALUE

  

 

有>>标识的行说明有其他指令会jump到此行。

 

更详细的描述:

第2行:

 m [0]:在堆栈上加载值0(co_consts [1])

 m [3]:将值0存入sum(co_varnames [1])

 

第3行:

 m [6]:通过将循环块的大小压入栈来设置循环

 m [9]:从栈中加载alist(co_varnames [0])的值

 m [12]:通过迭代器替换堆栈上的值(通过弹出和推送)

 m [13]:在堆栈中加载下一个迭代器值,如果StopIteration引起,则跳转到m [32]

           (m [29]中的代码跳回此位置进行循环)

 m [16]:将下一个值存储到v(co_varnames [2])中,将其从堆栈中弹出

 

第4行:

 m [19]:在栈中加载sum(co_varnames [1])的值

 m [22]:将v(co_varnames [2])的值加载到栈上

 m [25]:将栈顶的两个值进行相加操作后,将结果加载到栈上

 m [26]:栈顶弹出值,存储在sum(co_varnames [1])中

 m [29]:将pc设置为13,因此在m [13]中执行的下一条指令

             (跳回到前一个位置使循环循环)

 m [32]:弹出m[6]对循环块的设置而压入栈的值

             (m [13]中的代码在这里跳转到StopIteration,终止循环)

 

第5行:

 m [33]:将sum(co_varnames [1])的值压入栈,用于返回

 m [36]:从函数返回结果在堆栈顶部