-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathopcodes.py
More file actions
159 lines (119 loc) · 5.02 KB
/
opcodes.py
File metadata and controls
159 lines (119 loc) · 5.02 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
from __future__ import annotations
import collections
import dataclasses
import re
import typing
import warnings
import utils
from cpython import SKIP_PROPERTIES, Family, Properties, get_analysis, get_stack_effect
from utils import SKIP_OVERRIDE, Override, OverrideConfs, StackEffect, to_pascal_case
if typing.TYPE_CHECKING:
from collections.abc import Iterable
@dataclasses.dataclass(frozen=True, slots=True)
class OpcodeInfo:
enum_name: str
size: str
opcodes: tuple[Opcode, ...]
@property
def deopts(self) -> dict[str, list[str]]:
analysis = get_analysis()
names = {opcode.rust_name for opcode in self}
res = collections.defaultdict(list)
for family in analysis.families.values():
family_name = to_pascal_case(family.name)
if family_name not in names:
continue
for member in family.members:
member_name = to_pascal_case(member.name)
if member.name == family_name:
continue
res[family_name].append(member_name)
return dict(res)
def __iter__(self):
yield from self.opcodes
@classmethod
def iter_infos(
cls, text: str, override_confs: OverrideConfs
) -> Iterable[typing.Self]:
for block_match in re.finditer(
r"define_opcodes!\s*\((.+?)\);", text, re.DOTALL
):
block = block_match.group(1).strip()
size = re.search(r"#\[repr\((\w+)\)\]", block).group(1)
enum_name = re.search(
r"#\[repr\(\w+\)\]\s*pub\s+enum\s+(\w+)\s*;", block
).group(1)
second_enum_match = re.search(r"pub\s+enum\s+(\w+)\s*\{", block, re.DOTALL)
entries = utils.extract_enum_body(block, second_enum_match.end() - 1)
opcodes = tuple(sorted(iter_opcodes(entries, override_confs)))
yield cls(enum_name, size, opcodes)
def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]:
analysis = get_analysis()
# Split on commas that are followed by a newline + an uppercase letter (new entry)
entries = map(str.strip, re.split(r",\s*\n\s*(?=[A-Z])", text))
for entry in entries:
if not entry:
continue
opcode = Opcode.from_str(entry)
rust_name = opcode.rust_name
override = override_confs.get(rust_name, SKIP_OVERRIDE)
cpython_name = opcode.cpython_name
kwargs = {}
if instr := analysis.instructions.get(cpython_name):
kwargs["properties"] = instr.properties
kwargs["family"] = getattr(instr, "family", None)
kwargs["cache_entry"] = getattr(instr, "size", -1)
stack = get_stack_effect(instr)
popped = (-stack.base_offset).to_c()
pushed = (stack.logical_sp - stack.base_offset).to_c()
kwargs["stack_effect"] = StackEffect(popped=popped, pushed=pushed)
elif override == SKIP_OVERRIDE:
warnings.warn(
f"Could not get instruction metadata for {rust_name}"
" from CPython or override conf"
)
yield dataclasses.replace(opcode, override=override, **kwargs)
@dataclasses.dataclass(frozen=True, slots=True)
class Opcode:
rust_name: str
id: int
have_argument: bool = False
cache_entry: int = 0
stack_effect: StackEffect | None = None
properties: Properties = dataclasses.field(default_factory=lambda: SKIP_PROPERTIES)
family: Family | None = None
override: Override = dataclasses.field(default_factory=Override)
@property
def is_instrumented(self) -> bool:
if (res := self.override.is_instrumented) is not None:
return res
return self.cpython_name.startswith("INSTRUMENTED_")
@property
def cpython_name(self):
return utils.to_upper_snake_case(self.rust_name)
@property
def cpy_popped(self) -> str | None:
return getattr(self.stack_effect, "popped", None)
@property
def cpy_pushed(self) -> str | None:
return getattr(self.stack_effect, "pushed", None)
@property
def stack_effect_popped(self) -> str:
ove_popped = self.override.stack_effect.popped
if (ove_popped is None) and (self.cpy_popped is None):
raise ValueError(f"{self.rust_name} is missing popped stack_effect")
return ove_popped or self.cpy_popped
@property
def stack_effect_pushed(self) -> str:
ove_pushed = self.override.stack_effect.pushed
if (ove_pushed is None) and (self.cpy_pushed is None):
raise ValueError(f"{self.rust_name} is missing pushed stack_effect")
return ove_pushed or self.cpy_pushed
@classmethod
def from_str(cls, entry: str) -> typing.Self:
rust_name = re.match(r"(\w+)", entry).group(1)
id_num = re.findall(r"= (\d+)", entry)[0]
have_argument = "Arg<" in entry
return cls(rust_name, int(id_num), have_argument=have_argument)
def __lt__(self, other: typing.Self) -> bool:
return self.id < other.id