翻譯自《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]:從函數返回結果在堆棧頂部