This repository was archived by the owner on Jan 24, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathopcode_executor.py
More file actions
230 lines (193 loc) · 6.81 KB
/
Copy pathopcode_executor.py
File metadata and controls
230 lines (193 loc) · 6.81 KB
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
from __future__ import annotations
import dis
import types
from typing import Callable, List, Tuple
from ...utils import (
InnerError,
Singleton,
UnsupportError,
is_strict_mode,
log,
log_do,
)
from ..instruction_utils import get_instructions
from .function_graph import FunctionGraph
from .source import LocalSource
from .variables import (
ConstantVariable,
ListVariable,
TupleVariable,
VariableTrackerFactory,
)
Guard = Callable[[types.FrameType], bool]
GuardedFunction = Tuple[types.CodeType, Guard]
GuardedFunctions = List[GuardedFunction]
CacheGetter = Callable[[types.FrameType, GuardedFunctions], types.CodeType]
dummy_guard: Guard = lambda frame: True
@Singleton
class InstructionTranslatorCache:
cache: dict[types.CodeType, tuple[CacheGetter, GuardedFunctions]]
def __init__(self):
self.cache = {}
def clear(self):
self.cache.clear()
def __call__(self, frame) -> types.CodeType:
code: types.CodeType = frame.f_code
if code not in self.cache:
cache_getter, (new_code, guard_fn) = self.translate(frame)
self.cache[code] = (cache_getter, [(new_code, guard_fn)])
return new_code
cache_getter, guarded_fns = self.cache[code]
return cache_getter(frame, guarded_fns)
def lookup(
self, frame: types.FrameType, guarded_fns: GuardedFunctions
) -> types.CodeType:
for code, guard_fn in guarded_fns:
if guard_fn(frame):
log(3, "[Cache]: Cache hit\n")
return code
cache_getter, (new_code, guard_fn) = self.translate(frame)
guarded_fns.append((new_code, guard_fn))
return new_code
def skip(
self, frame: types.FrameType, guarded_fns: GuardedFunctions
) -> types.CodeType:
log(3, f"[Cache]: Skip frame {frame.f_code.co_name}\n")
return frame.f_code
def translate(
self, frame: types.FrameType
) -> tuple[CacheGetter, GuardedFunction]:
code: types.CodeType = frame.f_code
log(3, "[Cache]: Cache miss\n")
result = start_translate(frame)
if result is None:
return self.skip, (code, dummy_guard)
new_code, guard_fn = result
return self.lookup, (new_code, guard_fn)
def start_translate(frame) -> GuardedFunction | None:
simulator = OpcodeExecutor(frame)
try:
new_code, guard_fn = simulator.run()
log_do(3, lambda: dis.dis(new_code))
return new_code, guard_fn
except InnerError as e:
raise
except UnsupportError as e:
if is_strict_mode():
raise
log(2, f"Unsupport Frame is {frame.f_code.co_name}")
return None
except Exception as e:
raise
class OpcodeExecutor:
def __init__(self, frame: types.FrameType):
self._frame = frame
self._stack = []
self._code = frame.f_code
# fake env for run, new env should be gened by PyCodeGen
self._co_consts = self._code.co_consts
self._locals = {}
self._globals = {}
self._lasti = 0 # idx of instruction list
self.graph = FunctionGraph(frame.f_globals, frame.f_code)
self.new_code = None
self._instructions = get_instructions(self._code)
self._prepare_locals_and_globals()
def _prepare_locals_and_globals(self):
for name, value in self._frame.f_locals.items():
self._locals[name] = VariableTrackerFactory.from_value(
value, self.graph
)
for name, value in self._frame.f_globals.items():
self._globals[name] = VariableTrackerFactory.from_value(
value, self.graph
)
def run(self):
log(3, f"start execute opcode: {self._code}\n")
self._lasti = 0
while True:
if self._lasti >= len(self._instructions):
raise InnerError("lasti out of range, InnerError.")
cur_instr = self._instructions[self._lasti]
self._lasti += 1
is_stop = self.step(cur_instr)
if is_stop:
break
if self.new_code is None:
raise InnerError("OpExecutor return a emtpy new_code.")
return self.new_code, self.guard_fn
def step(self, instr):
if not hasattr(self, instr.opname):
raise UnsupportError(f"opcode: {instr.opname} is not supported.")
log(3, f"[TraceExecution]: {instr.opname}, stack is {self._stack}\n")
getattr(self, instr.opname)(instr) # run single step.
if instr.opname == "RETURN_VALUE":
return True
return False
def pop(self):
return self._stack.pop()
def push(self, val):
self._stack.append(val)
def LOAD_ATTR(self, instr):
TODO # noqa: F821
def LOAD_FAST(self, instr):
varname = instr.argval
var = self._locals[varname]
var.try_set_source(LocalSource(instr.arg, varname))
self.push(var)
def LOAD_METHOD(self, instr):
TODO # noqa: F821
def STORE_FAST(self, instr):
"""
TODO: side effect may happen
"""
var = self.pop()
self._locals[instr.argval] = var
def LOAD_GLOBAL(self, instr):
TODO # noqa: F821
def LOAD_CONST(self, instr):
var = ConstantVariable(instr.argval)
self.push(var)
def BINARY_MULTIPLY(self, instr):
b = self.pop()
a = self.pop()
self.push(a * b)
def BINARY_ADD(self, instr):
b = self.pop()
a = self.pop()
self.push(a + b)
def BINARY_SUBSCR(self, instr):
b = self.pop()
a = self.pop()
self.push(a[b])
def INPLACE_ADD(self, instr):
b = self.pop()
a = self.pop()
a += b
self.push(a)
def CALL_METHOD(self, instr):
TODO # noqa: F821
def RETURN_VALUE(self, instr):
assert len(self._stack) == 1, "Stack must have one element."
ret_val = self.pop()
self.new_code, self.guard_fn = self.graph.start_compile(ret_val)
def BUILD_LIST(self, instr):
list_size = instr.arg
if list_size <= len(self._stack):
val_list = self._stack[-list_size:]
self._stack[-list_size:] = []
self.push(ListVariable(val_list))
else:
raise InnerError(
f"OpExecutor want BUILD_LIST with size {list_size}, but current stack do not have enough elems."
)
def BUILD_TUPLE(self, instr):
tuple_size = instr.arg
if tuple_size <= len(self._stack):
val_tuple = self._stack[-tuple_size:]
self._stack[-tuple_size:] = []
self.push(TupleVariable(val_tuple))
else:
raise InnerError(
f"OpExecutor want BUILD_TUPLE with size {tuple_size}, but current stack do not have enough elems."
)