Python-interpreter

2024-08-22

Python interpreter

Author: 堇姬Naup

定義

先定義一下何謂python interpreter,就是用來解釋code object的

image
image

tiny interpreter

首先要建立一個解釋器,需要給他一個指令集
這邊先要求實現一件是,就是做加法
需要有以下指令

  • LOAD_VALUE -> 載入數字至stack上、參數是要載入到stack上的數字
  • ADD_TWO_VALUES -> 將stack最上層跟最上層-1 pop然後相加,將結果放到stack上、無參數
  • PRINT_ANSWER -> 印出stack最上方層並pop掉、無參數

先將要加的兩個數字載入至stack上,相加後印出

image
image

並且加入
run_code()
循環執行每條指令及參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
what_to_execute = {
"instructions": [("LOAD_VALUE", 0), # the first number
("LOAD_VALUE", 1), # the second number
("ADD_TWO_VALUES", None),
("PRINT_ANSWER", None)],
"numbers": [7, 5] }

class Interpreter:
def __init__(self):
self.stack = []

def LOAD_VALUE(self, number):
self.stack.append(number)

def PRINT_ANSWER(self):
answer = self.stack.pop()
print(answer)

def ADD_TWO_VALUES(self):
first_num = self.stack.pop()
second_num = self.stack.pop()
total = first_num + second_num
self.stack.append(total)
def run_code(self, what_to_execute):
instructions = what_to_execute["instructions"]
numbers = what_to_execute["numbers"]
for each_step in instructions:
instruction, argument = each_step
if instruction == "LOAD_VALUE":
number = numbers[argument]
self.LOAD_VALUE(number)
elif instruction == "ADD_TWO_VALUES":
self.ADD_TWO_VALUES()
elif instruction == "PRINT_ANSWER":
self.PRINT_ANSWER()

interpreter = Interpreter()
interpreter.run_code(what_to_execute)

透過這樣來實現加法

變數

接下來引入一些簡單的mmap關係
LOAD_NAME -> 將變數值放到stack上
STORE_NAME -> 將變數mmap到stack最上層的值並pop

除了const表以外,還需要加入變數表

並新增environment來建立mmap關係

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
what_to_execute = {
"instructions": [("LOAD_VALUE", 0),
("STORE_NAME", 0),
("LOAD_VALUE", 1),
("STORE_NAME", 1),
("LOAD_NAME", 0),
("LOAD_NAME", 1),
("ADD_TWO_VALUES", None),
("PRINT_ANSWER", None)],
"numbers": [1, 2], # 數值列表,用於 LOAD_VALUE 指令
"names": ["a", "b"] # 變數名稱列表,用於 STORE_NAME 和 LOAD_NAME 指令
}

class Interpreter:
def __init__(self):
self.stack = [] # 用來儲存運算過程中的數值堆疊
self.environment = {} # 用來儲存變數名稱和值的映射環境

def LOAD_VALUE(self, number):
self.stack.append(number) # 將數值推入堆疊

def PRINT_ANSWER(self):
answer = self.stack.pop() # 從堆疊中取出結果
print(answer) # 輸出結果

def ADD_TWO_VALUES(self):
first_num = self.stack.pop() # 從堆疊中取出第一個數值
second_num = self.stack.pop() # 從堆疊中取出第二個數值
total = first_num + second_num # 將兩個數值相加
self.stack.append(total) # 將總和推回堆疊

def STORE_NAME(self, name):
val = self.stack.pop() # 從堆疊中取出數值
self.environment[name] = val # 將數值儲存到指定的變數名稱中

def LOAD_NAME(self, name):
val = self.environment[name] # 從環境中取出變數的值
self.stack.append(val) # 將該值推入堆疊

def parse_argument(self, instruction, argument, what_to_execute):
"""解析指令的參數,並根據指令類型返回對應的值或名稱。"""
numbers = ["LOAD_VALUE"] # 對應 LOAD_VALUE 的指令
names = ["LOAD_NAME", "STORE_NAME"] # 對應變數名稱的指令

if instruction in numbers:
argument = what_to_execute["numbers"][argument] # 根據索引返回對應的數值
elif instruction in names:
argument = what_to_execute["names"][argument] # 根據索引返回對應的變數名稱

return argument

def run_code(self, what_to_execute):
instructions = what_to_execute["instructions"] # 獲取執行指令的列表
for each_step in instructions:
instruction, argument = each_step
argument = self.parse_argument(instruction, argument, what_to_execute) # 解析指令參數

# 根據指令類型執行相應的操作
if instruction == "LOAD_VALUE":
self.LOAD_VALUE(argument)
elif instruction == "ADD_TWO_VALUES":
self.ADD_TWO_VALUES()
elif instruction == "PRINT_ANSWER":
self.PRINT_ANSWER()
elif instruction == "STORE_NAME":
self.STORE_NAME(argument)
elif instruction == "LOAD_NAME":
self.LOAD_NAME(argument)

# 建立解釋器並執行代碼
interpreter = Interpreter()
interpreter.run_code(what_to_execute)

優化run_code

這邊可以觀察到,使用if分支來建立指令集已經開始變得冗長,這邊我們可以使用getattr來優化動態查找

可以參考這篇
https://www.runoob.com/python/python-func-getattr.html

1
2
3
4
5
6
7
8
9
10
11
def run_code(self, what_to_execute):
instructions = what_to_execute["instructions"]
for each_step in instructions:
instruction, argument = each_step
argument = self.parse_argument(instruction, argument, what_to_execute)
# 用getattr優化
bytecode_method = getattr(self, instruction)
if argument is None:
bytecode_method()
else:
bytecode_method(argument)

Real Python Bytecode

以上簡單的了解了指令集的建立及該如何跑他,現在回歸原本的python bytecode來看看

1
2
3
4
5
6
def f():
x = 3
if x < 5:
return 'yes'
else:
return 'no'

可以用__code__看一下

code

1
2
3
4
5
6
7
8
9
10
def f():
x = 3
if x < 5:
return 'yes'
else:
return 'no'

for attr in dir(f.__code__):
if attr.startswith('co_'):
print(f"{attr}:\t{getattr(f.__code__, attr)}")

觀察一下它裡面到底存了甚麼資訊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
co_argcount:    0
co_cellvars: ()
co_code: b'd\x01}\x00|\x00d\x02k\x00r\x08d\x03S\x00d\x04S\x00'
co_consts: (None, 3, 5, 'yes', 'no')
co_filename: d:\Users\user\Downloads\dist (10)\tst.py
co_firstlineno: 1
co_flags: 67
co_freevars: ()
co_kwonlyargcount: 0
co_lines: <built-in method co_lines of code object at 0x000001CCAFC9FCB0>
co_linetable: b'\x04\x01\x08\x01\x04\x01\x04\x02'
co_lnotab: b'\x00\x01\x04\x01\x08\x01\x04\x02'
co_name: f
co_names: ()
co_nlocals: 1
co_posonlyargcount: 0
co_stacksize: 2
co_varnames: ('x',)

包含了許多函數相關bytecode以及執行資訊

.co_code下的就是他的bytecode

1
2
3
4
5
6
7
8
9
10
11
12
13
b'd\x01}\x00|\x00d\x02k\x00r\x08d\x03S\x00d\x04S\x00'
```

可以把他轉成list來看
```python
def f():
x = 3
if x < 5:
return 'yes'
else:
return 'no'

print(list(f.__code__.co_code))
1
[100, 1, 125, 0, 124, 0, 100, 2, 107, 0, 114, 8, 100, 3, 83, 0, 100, 4, 83, 0]

這邊搭配dis來看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  3           0 LOAD_CONST               1 (3)
2 STORE_FAST 0 (x)

4 4 LOAD_FAST 0 (x)
6 LOAD_CONST 2 (5)
8 COMPARE_OP 0 (<)
10 POP_JUMP_IF_FALSE 8 (to 16)

5 12 LOAD_CONST 3 ('yes')
14 RETURN_VALUE

7 >> 16 LOAD_CONST 4 ('no')
18 RETURN_VALUE
None

一個bytecode對應一個指令

1
2
for i in list(f.__code__.co_code):
print(i,"=",dis.opname[i])

對應關係

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
100 = LOAD_CONST
1
125 = STORE_FAST
0
124 = LOAD_FAST
0
100 = LOAD_CONST
2
107 = COMPARE_OP
0
114 = POP_JUMP_IF_FALSE
8
100 = LOAD_CONST
3
83 = RETURN_VALUE
0
100 = LOAD_CONST
4
83 = RETURN_VALUE
0

LOAD_CONST 就相當於上方的 LOAD_FAST
LOAD_VALUE 就相當於上方的 LOAD_NAME

比較和循環

通常一個程式當然不可能只有單純的一行行執行,一定有條件判斷跟迴圈,而python bytecode也有類似的goto可以做跳轉

1
if x < 5

被編譯成了

1
2
3
4
4           4 LOAD_FAST                0 (x)
6 LOAD_CONST 2 (5)
8 COMPARE_OP 0 (<)
10 POP_JUMP_IF_FALSE 8 (to 16)

如果是false就跳到16行的no
對的話往下執行

POP_JUMP_IF_FALSE -> 將stack頂部的值pop掉看是true or false

1
2
3
4
5
def f():
x = 1
while x < 5:
x = x + 1
return x

迴圈也是一樣的概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3           0 LOAD_CONST               1 (1)
2 STORE_FAST 0 (x)

4 4 LOAD_FAST 0 (x)
6 LOAD_CONST 2 (5)
8 COMPARE_OP 0 (<)
10 POP_JUMP_IF_FALSE 14 (to 28)

5 >> 12 LOAD_FAST 0 (x)
14 LOAD_CONST 1 (1)
16 BINARY_ADD
18 STORE_FAST 0 (x)

4 20 LOAD_FAST 0 (x)
22 LOAD_CONST 2 (5)
24 COMPARE_OP 0 (<)
26 POP_JUMP_IF_TRUE 6 (to 12)

6 >> 28 LOAD_FAST 0 (x)
30 RETURN_VALUE

看有沒有達成compare條件,並進行跳轉

Frame

接下來要介紹的是Frame,觀察一下可以發現 RETURN_VALUE會return一個值,那他究竟return到哪裡呢?

可以想像,一個bytecode object有很多的Frame,return的value會被return到main frame,可以簡單想像他是作用域的概念

如果一個遞迴函式調用自己十次,會有十一個frame

1
2
3
4
5
6
7
8
9
10
def bar(y):
z = y + 3
return z

def foo():
a = 1
b = 2
return a + bar(b)

foo()

現在共有三個frame,一但bar return就會把bar frame 從call stack pop掉

image
image

由於python在frame被pop掉幾乎都會清掉data stack,所以其實所有frame共用一個data stack也是可以的(非生成器的情況)

  • 生成器的特殊性: 生成器的特性在於它可以暫停一個frame的執行,並在之後恢復到相同的狀態繼續執行,這要求每個frame必須有獨立的data stack

https://www.runoob.com/python3/python3-iterator-generator.html

Byterun

上述就是基礎的python interpreter的知識,開始看byterun吧
byterun有四種對象

  • VirtualMachine
    • 負責整體結構管理(frame call stack、指令到操作的映射)
    • 處理指令到操作的映射
    • 較複雜的管理邏輯
  • Frame
    • 關聯 code object
    • 管理全局、局部命名空間
    • 跟踪call stack和執行狀態
  • Function
    • 調用函數時,會創建一個新的 Frame
    • 控制新 Frame 的創建和管理
  • Block

https://github.com/nedbat/byterun

VirtualMachine

python interpreter就是一個VirtualMachine
VirtualMachine 負責保存call stack、異常狀態,以及在 frame 中傳遞的返回值

image
image

編譯後的code object為參數,並且創建一個frame,這個frame可能會在增加或刪減更多的frame,call stack也隨之伸縮,當第一個frame return時就結束(跟之前學的幾乎一樣)

1
2
3
4
5
6
7
8
9
10
11
12
class VirtualMachine(object):
def __init__(self):
self.frames = [] # 儲存調用棧的列表。
self.frame = None # 當前正在執行的 frame。
self.return_value = None # 儲存當前 frame 的返回值。
self.last_exception = None # 儲存最後一個異常狀態。

def run_code(self, code, global_names=None, local_names=None):
""" An entry point to execute code using the virtual machine."""
frame = self.make_frame(code, global_names=global_names,
local_names=local_names)
self.run_frame(frame)

frame

就是一個屬性的集合,包含了code object、global name、local name、內建命名空間、前一個frame、data stack、block stack

https://docs.python.org/zh-tw/3/library/builtins.html

https://www.cnblogs.com/goldsunshine/p/15085333.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Frame(object):
def __init__(self, code_obj, global_names, local_names, prev_frame):
self.code_obj = code_obj # 儲存當前的程式碼物件
self.global_names = global_names # 儲存全域名稱空間
self.local_names = local_names # 儲存區域名稱空間
self.prev_frame = prev_frame # 儲存前一個執行框架(如果有的話)
self.stack = [] # 初始化操作堆疊為空

if prev_frame:
# 如果有前一個框架,繼承其內建函式名稱空間
self.builtin_names = prev_frame.builtin_names
else:
# 否則,使用當前區域名稱空間中的 '__builtins__' 來設置內建函式名稱空間
self.builtin_names = local_names['__builtins__']
if hasattr(self.builtin_names, '__dict__'):
# 如果內建函式名稱有 __dict__ 屬性,則使用其字典
self.builtin_names = self.builtin_names.__dict__

self.last_instruction = 0 # 記錄最後執行的指令索引,初始為 0
self.block_stack = [] # 初始化區塊堆疊為空,用於處理控制流(如 try/except 等)

剛剛的virtual machine並沒有實現對於frame的操作,這邊加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class VirtualMachine(object):
[... snip ...]

# 處理執行框架的相關方法
def make_frame(self, code, callargs={}, global_names=None, local_names=None):
# 建立新的執行框架
if global_names is not None and local_names is not None:
# 如果提供了全域名稱和區域名稱,將區域名稱設為全域名稱
local_names = global_names
elif self.frames:
# 如果已經有現存的框架,繼承目前框架的全域名稱空間,並初始化一個空的區域名稱空間
global_names = self.frame.global_names
local_names = {}
else:
# 如果沒有現存的框架,初始化全域和區域名稱空間
global_names = local_names = {
'__builtins__': __builtins__, # 預設的內建函式集合
'__name__': '__main__', # 預設的模組名稱
'__doc__': None, # 預設的文件字串
'__package__': None, # 預設的包名稱
}
local_names.update(callargs) # 將函式的引數更新至區域名稱空間
frame = Frame(code, global_names, local_names, self.frame) # 創建新的 Frame 物件
return frame # 返回新的執行框架

def push_frame(self, frame):
# 將新的執行框架推入堆疊並設為當前框架
self.frames.append(frame)
self.frame = frame

def pop_frame(self):
# 將當前框架從堆疊中彈出,並更新當前框架為堆疊頂端的框架
self.frames.pop()
if self.frames:
self.frame = self.frames[-1]
else:
self.frame = None

def run_frame(self):
# 執行當前框架的程式邏輯 (此部分尚未實作)
pass

Function

重要的只有創建一個frame並執行他的__call__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Function(object):
"""
創建一個現實的函式物件,定義了解譯器所需的功能。
"""
__slots__ = [
'func_code', 'func_name', 'func_defaults', 'func_globals',
'func_locals', 'func_dict', 'func_closure',
'__name__', '__dict__', '__doc__',
'_vm', '_func',
]

def __init__(self, name, code, globs, defaults, closure, vm):
"""此部分細節對於理解解譯器來說並不是特別重要。"""
self._vm = vm # 儲存虛擬機器實例
self.func_code = code # 儲存函式的程式碼物件
self.func_name = self.__name__ = name or code.co_name # 儲存函式名稱
self.func_defaults = tuple(defaults) # 儲存函式的預設引數
self.func_globals = globs # 儲存全域名稱空間
self.func_locals = self._vm.frame.f_locals # 儲存當前區域名稱空間
self.__dict__ = {} # 初始化函式的屬性字典
self.func_closure = closure # 儲存閉包(如果有的話)
self.__doc__ = code.co_consts[0] if code.co_consts else None # 儲存函式的文檔字串

# 有時需要一個真正的 Python 函式,這裡是為了這個目的
kw = {
'argdefs': self.func_defaults, # 設置預設引數
}
if closure:
# 如果有閉包,創建對應的 cell
kw['closure'] = tuple(make_cell(0) for _ in closure)
self._func = types.FunctionType(code, globs, **kw) # 創建真正的 Python 函式物件

def __call__(self, *args, **kwargs):
"""當調用函式時,創建一個新的框架並執行它。"""
callargs = inspect.getcallargs(self._func, *args, **kwargs)
# 使用 callargs 來提供引數與其值的映射,以便傳遞到新的框架中
frame = self._vm.make_frame(
self.func_code, callargs, self.func_globals, {}
)
return self._vm.run_frame(frame) # 執行新的框架並返回結果

def make_cell(value):
"""創建一個真正的 Python 閉包並抓取一個 cell。"""
# 感謝 Alex Gaynor 這部分的幫助
fn = (lambda x: lambda: x)(value)
return fn.__closure__[0] # 返回閉包中的 cell

virtual machine也加入了管理stack更多方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class VirtualMachine(object):
[... snip ...]

# 資料堆疊操作
def top(self):
"""返回堆疊頂端的值。"""
return self.frame.stack[-1]

def pop(self):
"""彈出並返回堆疊頂端的值。"""
return self.frame.stack.pop()

def push(self, *vals):
"""將一個或多個值推入堆疊。"""
self.frame.stack.extend(vals)

def popn(self, n):
"""從堆疊中彈出 `n` 個值。
返回一個 `n` 個值的列表,最深的值在前。
"""
if n:
ret = self.frame.stack[-n:] # 取出最上面的 `n` 個值
self.frame.stack[-n:] = [] # 清空這些值
return ret
else:
return [] # 如果 `n` 為 0,返回空列表

並且要加入解析指令是否有參數跟更新最後一行執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class VirtualMachine(object):
[... snip ...]

def parse_byte_and_args(self):
f = self.frame
opoffset = f.last_instruction
byteCode = f.code_obj.co_code[opoffset]
f.last_instruction += 1
byte_name = dis.opname[byteCode]
if byteCode >= dis.HAVE_ARGUMENT:
# index into the bytecode
arg = f.code_obj.co_code[f.last_instruction:f.last_instruction+2]
f.last_instruction += 2 # advance the instruction pointer
arg_val = arg[0] + (arg[1] * 256)
if byteCode in dis.hasconst: # Look up a constant
arg = f.code_obj.co_consts[arg_val]
elif byteCode in dis.hasname: # Look up a name
arg = f.code_obj.co_names[arg_val]
elif byteCode in dis.haslocal: # Look up a local name
arg = f.code_obj.co_varnames[arg_val]
elif byteCode in dis.hasjrel: # Calculate a relative jump
arg = f.last_instruction + arg_val
else:
arg = arg_val
argument = [arg]
else:
argument = []

return byte_name, argument

block(沒有很重要,看看就好了)

block在控制流程中扮演重要角色,尤其是在處理異常和迴圈時。它確保在操作完成後,數據堆疊(data stack)的狀態是正確

在迴圈中,一個特殊的迭代器會存在於堆疊中,當迴圈完成時,它會從堆疊中彈出。解釋器需要檢查迴圈是否仍在進行中,或者已經停止

為了跟蹤這些額外的信息,解釋器設置了一個標誌來指示它的狀態。我們用一個變量 why 來實現這個標誌,它可以是 None 或以下幾個字符串之一:”continue”、”break”、”exception”、”return”。這些標誌指示如何對區塊堆疊和數據堆疊進行操作。例如,在迴圈的例子中,如果區塊堆疊的堆疊頂部是 loop 區塊,並且 why 是 “continue”,那麼迭代器應該保留在數據堆疊上;如果 why 是 “break”,那麼迭代器會被彈出。

(原文解釋得很好了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import collections

# 定義區塊的數據結構
Block = collections.namedtuple("Block", "type, handler, stack_height")

class VirtualMachine(object):
[... snip ...]

# 推入區塊到區塊堆疊
def push_block(self, b_type, handler=None):
level = len(self.frame.stack) # 獲取當前堆疊的深度
self.frame.block_stack.append(Block(b_type, handler, level)) # 將區塊推入堆疊

# 彈出區塊
def pop_block(self):
return self.frame.block_stack.pop()

# 清理與特定區塊相關的數據堆疊值
def unwind_block(self, block):
""" 根據給定的區塊清理數據堆疊上的值。 """
if block.type == 'except-handler':
# 異常本身在堆疊中,包括類型、值和回溯
offset = 3
else:
offset = 0

while len(self.frame.stack) > block.stack_height + offset:
self.pop() # 彈出堆疊中的值

if block.type == 'except-handler':
traceback, value, exctype = self.popn(3) # 彈出異常相關的三個值
self.last_exception = exctype, value, traceback # 記錄最後的異常

# 根據給定的標誌處理區塊堆疊
def manage_block_stack(self, why):
""" 管理區塊堆疊。 """
frame = self.frame
block = frame.block_stack[-1] # 獲取區塊堆疊的最上層區塊
if block.type == 'loop' and why == 'continue':
self.jump(self.return_value) # 繼續執行循環
why = None
return why

self.pop_block() # 彈出區塊
self.unwind_block(block) # 清理數據堆疊中的值

if block.type == 'loop' and why == 'break':
why = None
self.jump(block.handler) # 跳轉到處理器
return why

if (block.type in ['setup-except', 'finally'] and why == 'exception'):
self.push_block('except-handler') # 推入異常處理區塊
exctype, value, tb = self.last_exception # 獲取最後的異常信息
self.push(tb, value, exctype) # 推入異常信息
self.push(tb, value, exctype) # 再次推入異常信息
why = None
self.jump(block.handler) # 跳轉到異常處理器
return why

elif block.type == 'finally':
if why in ('return', 'continue'):
self.push(self.return_value) # 推入返回值

self.push(why) # 推入標誌

why = None
self.jump(block.handler) # 跳轉到最終處理器
return why
return why

實現指令集

基本上跟上面都差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
class VirtualMachine(object):
[... snip ...]

## Stack manipulation

def byte_LOAD_CONST(self, const):
self.push(const)

def byte_POP_TOP(self):
self.pop()

## Names
def byte_LOAD_NAME(self, name):
frame = self.frame
if name in frame.f_locals:
val = frame.f_locals[name]
elif name in frame.f_globals:
val = frame.f_globals[name]
elif name in frame.f_builtins:
val = frame.f_builtins[name]
else:
raise NameError("name '%s' is not defined" % name)
self.push(val)

def byte_STORE_NAME(self, name):
self.frame.f_locals[name] = self.pop()

def byte_LOAD_FAST(self, name):
if name in self.frame.f_locals:
val = self.frame.f_locals[name]
else:
raise UnboundLocalError(
"local variable '%s' referenced before assignment" % name
)
self.push(val)

def byte_STORE_FAST(self, name):
self.frame.f_locals[name] = self.pop()

def byte_LOAD_GLOBAL(self, name):
f = self.frame
if name in f.f_globals:
val = f.f_globals[name]
elif name in f.f_builtins:
val = f.f_builtins[name]
else:
raise NameError("global name '%s' is not defined" % name)
self.push(val)

## Operators

BINARY_OPERATORS = {
'POWER': pow,
'MULTIPLY': operator.mul,
'FLOOR_DIVIDE': operator.floordiv,
'TRUE_DIVIDE': operator.truediv,
'MODULO': operator.mod,
'ADD': operator.add,
'SUBTRACT': operator.sub,
'SUBSCR': operator.getitem,
'LSHIFT': operator.lshift,
'RSHIFT': operator.rshift,
'AND': operator.and_,
'XOR': operator.xor,
'OR': operator.or_,
}

def binaryOperator(self, op):
x, y = self.popn(2)
self.push(self.BINARY_OPERATORS[op](x, y))

COMPARE_OPERATORS = [
operator.lt,
operator.le,
operator.eq,
operator.ne,
operator.gt,
operator.ge,
lambda x, y: x in y,
lambda x, y: x not in y,
lambda x, y: x is y,
lambda x, y: x is not y,
lambda x, y: issubclass(x, Exception) and issubclass(x, y),
]

def byte_COMPARE_OP(self, opnum):
x, y = self.popn(2)
self.push(self.COMPARE_OPERATORS[opnum](x, y))

## Attributes and indexing

def byte_LOAD_ATTR(self, attr):
obj = self.pop()
val = getattr(obj, attr)
self.push(val)

def byte_STORE_ATTR(self, name):
val, obj = self.popn(2)
setattr(obj, name, val)

## Building

def byte_BUILD_LIST(self, count):
elts = self.popn(count)
self.push(elts)

def byte_BUILD_MAP(self, size):
self.push({})

def byte_STORE_MAP(self):
the_map, val, key = self.popn(3)
the_map[key] = val
self.push(the_map)

def byte_LIST_APPEND(self, count):
val = self.pop()
the_list = self.frame.stack[-count] # peek
the_list.append(val)

## Jumps

def byte_JUMP_FORWARD(self, jump):
self.jump(jump)

def byte_JUMP_ABSOLUTE(self, jump):
self.jump(jump)

def byte_POP_JUMP_IF_TRUE(self, jump):
val = self.pop()
if val:
self.jump(jump)

def byte_POP_JUMP_IF_FALSE(self, jump):
val = self.pop()
if not val:
self.jump(jump)

## Blocks

def byte_SETUP_LOOP(self, dest):
self.push_block('loop', dest)

def byte_GET_ITER(self):
self.push(iter(self.pop()))

def byte_FOR_ITER(self, jump):
iterobj = self.top()
try:
v = next(iterobj)
self.push(v)
except StopIteration:
self.pop()
self.jump(jump)

def byte_BREAK_LOOP(self):
return 'break'

def byte_POP_BLOCK(self):
self.pop_block()

## Functions

def byte_MAKE_FUNCTION(self, argc):
name = self.pop()
code = self.pop()
defaults = self.popn(argc)
globs = self.frame.f_globals
fn = Function(name, code, globs, defaults, None, self)
self.push(fn)

def byte_CALL_FUNCTION(self, arg):
lenKw, lenPos = divmod(arg, 256) # KWargs not supported here
posargs = self.popn(lenPos)

func = self.pop()
frame = self.frame
retval = func(*posargs)
self.push(retval)

def byte_RETURN_VALUE(self):
self.return_value = self.pop()
return "return"

詳細實現參考

https://github.com/nedbat/byterun

ref

https://bbs.kanxue.com/thread-258353.htm#stack

https://docs.python.org/3/library/dis.html

https://qingyunha.github.io/taotao/