8000 gh-105481: expose opcode metadata via the _opcode module (#106688) · python/cpython@6a70edf · GitHub
[go: up one dir, main page]

Skip to content

Commit 6a70edf

Browse files
authored
gh-105481: expose opcode metadata via the _opcode module (#106688)
1 parent 243fdcb commit 6a70edf

File tree

9 files changed

+558
-21
lines changed

9 files changed

+558
-21
lines changed

Include/cpython/compile.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,9 @@ typedef struct {
6767
#define PY_INVALID_STACK_EFFECT INT_MAX
6868
PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg);
6969
PyAPI_FUNC(int) PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump);
70+
71+
PyAPI_FUNC(int) PyUnstable_OpcodeIsValid(int opcode);
72+
PyAPI_FUNC(int) PyUnstable_OpcodeHasArg(int opcode);
73+
PyAPI_FUNC(int) PyUnstable_OpcodeHasConst(int opcode);
74+
PyAPI_FUNC(int) PyUnstable_OpcodeHasName(int opcode);
75+
PyAPI_FUNC(int) PyUnstable_OpcodeHasJump(int opcode);

Include/internal/pycore_opcode_metadata.h

Lines changed: 22 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test__opcode.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,70 @@
99

1010
class OpcodeTests(unittest.TestCase):
1111

12+
def check_bool_function_result(self, func, ops, expected):
13+
for op in ops:
14+
if isinstance(op, str):
15+
op = dis.opmap[op]
16+
with self.subTest(opcode=op, func=func):
17+
self.assertIsInstance(func(op), bool)
18+
self.assertEqual(func(op), expected)
19+
20+
def test_invalid_opcodes(self):
21+
invalid = [-100, -1, 255, 512, 513, 1000]
22+
self.check_bool_function_result(_opcode.is_valid, invalid, False)
23+
self.check_bool_function_result(_opcode.has_arg, invalid, False)
24+
self.check_bool_function_result(_opcode.has_const, invalid, False)
25+
self.check_bool_function_result(_opcode.has_name, invalid, False)
26+
self.check_bool_function_result(_opcode.has_jump, invalid, False)
27+
28+
def test_is_valid(self):
29+
names = [
30+
'CACHE',
31+
'POP_TOP',
32+
'IMPORT_NAME',
33+
'JUMP',
34+
'INSTRUMENTED_RETURN_VALUE',
35+
]
36+
opcodes = [dis.opmap[opname] for opname in names]
37+
self.check_bool_function_result(_opcode.is_valid, opcodes, True)
38+
39+
def test_has_arg(self):
40+
has_arg = ['SWAP', 'LOAD_FAST', 'INSTRUMENTED_POP_JUMP_IF_TRUE', 'JUMP']
41+
no_arg = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
42+
self.check_bool_function_result(_opcode.has_arg, has_arg, True)
43+
self.check_bool_function_result(_opcode.has_arg, no_arg, False)
44+
45+
def test_has_const(self):
46+
has_const = ['LOAD_CONST', 'RETURN_CONST', 'KW_NAMES']
47+
no_const = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
48+
self.check_bool_function_result(_opcode.has_const, has_const, True)
49+
self.check_bool_function_result(_opcode.has_const, no_const, False)
50+
51+
def test_has_name(self):
52+
has_name = ['STORE_NAME', 'DELETE_ATTR', 'STORE_GLOBAL', 'IMPORT_FROM',
53+
'LOAD_FROM_DICT_OR_GLOBALS']
54+
no_name = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
55+
self.check_bool_function_result(_opcode.has_name, has_name, True)
56+
self.check_bool_function_result(_opcode.has_name, no_name, False)
57+
58+
def test_has_jump(self):
59+
has_jump = ['FOR_ITER', 'JUMP_FORWARD', 'JUMP', 'POP_JUMP_IF_TRUE', 'SEND']
60+
no_jump = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
61+
self.check_bool_function_result(_opcode.has_jump, has_jump, True)
62+
self.check_bool_function_result(_opcode.has_jump, no_jump, False)
63+
64+
# the following test is part of the refactor, it will be removed soon
65+
def test_against_legacy_bool_values(self):
66+
# limiting to ops up to ENTER_EXECUTOR, because everything after that
67+
# is not currently categorized correctly in opcode.py.
68+
for op in range(0, opcode.opmap['ENTER_EXECUTOR']):
69+
with self.subTest(op=op):
70+
if opcode.opname[op] != f'<{op}>':
71+
self.assertEqual(op in dis.hasarg, _opcode.has_arg(op))
72+
self.assertEqual(op in dis.hasconst, _opcode.has_const(op))
73+
self.assertEqual(op in dis.hasname, _opcode.has_name(op))
74+
self.assertEqual(op in dis.hasjrel, _opcode.has_jump(op))
75+
1276
def test_stack_effect(self):
1377
self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1)
1478
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose opcode metadata through :mod:`_opcode`.

Modules/_opcode.c

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "Python.h"
2+
#include "compile.h"
23
#include "opcode.h"
34
#include "internal/pycore_code.h"
45

@@ -61,6 +62,91 @@ _opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg,
6162

6263
/*[clinic input]
6364
65+
_opcode.is_valid -> bool
66+
67+
opcode: int
68+
69+
Return True if opcode is valid, False otherwise.
70+
[clinic start generated code]*/
71+
72+
static int
73+
_opcode_is_valid_impl(PyObject *module, int opcode)
74+
/*[clinic end generated code: output=b0d918ea1d073f65 input=fe23e0aa194ddae0]*/
75+
{
76+
return PyUnstable_OpcodeIsValid(opcode);
77+
}
78+
79+
/*[clinic input]
80+
81+
_opcode.has_arg -> bool
82+
83+
opcode: int
84+
85+
Return True if the opcode uses its oparg, False otherwise.
86+
[clinic start generated code]*/
87+
88+
static int
89+
_opcode_has_arg_impl(PyObject *module, int opcode)
90+
/*[clinic end generated code: output=7a062d3b2dcc0815 input=93d878ba6361db5f]*/
91+
{
92+
return PyUnstable_OpcodeIsValid(opcode) &&
93+
PyUnstable_OpcodeHasArg(opcode);
94+
}
95+
96+
/*[clinic input]
97+
98+
_opcode.has_const -> bool
99+
100+
opcode: int
101+
102+
Return True if the opcode accesses a constant, False otherwise.
103+
[clinic start generated code]*/
104+
105+
static int
106+
_opcode_has_const_impl(PyObject *module, int opcode)
107+
/*[clinic end generated code: output=c646d5027c634120 input=a6999e4cf13f9410]*/
108+
{
109+
return PyUnstable_OpcodeIsValid(opcode) &&
110+
PyUnstable_OpcodeHasConst(opcode);
111+
}
112+
113+
/*[clinic input]
114+
115+
_opcode.has_name -> bool
116+
117+
opcode: int
118+
119+
Return True if the opcode accesses an attribute by name, False otherwise.
120+
[clinic start generated code]*/
121+
122+
static int
123+
_opcode_has_name_impl(PyObject *module, int opcode)
124+
/*[clinic end generated code: output=b49a83555c2fa517 input=448aa5e4bcc947ba]*/
125+
{
126+
return PyUnstable_OpcodeIsValid(opcode) &&
127+
PyUnstable_OpcodeHasName(opcode);
128+
}
129+
130+
/*[clinic input]
131+
132+
_opcode.has_jump -> bool
133+
134+
opcode: int
135+
136+
Return True if the opcode has a jump target, False otherwise.
137+
[clinic start generated code]*/
138+
139+
static int
140+
_opcode_has_jump_impl(PyObject *module, int opcode)
141+
/*[clinic end generated code: output=e9c583c669f1c46a input=35f711274357a0c3]*/
142+
{
143+
return PyUnstable_OpcodeIsValid(opcode) &&
144+
PyUnstable_OpcodeHasJump(opcode);
145+
146+
}
147+
148+
/*[clinic input]
149+
64150
_opcode.get_specialization_stats
65151
66152
Return the specialization stats
@@ -80,6 +166,11 @@ _opcode_get_specialization_stats_impl(PyObject *module)
80166
static PyMethodDef
81167
opcode_functions[] = {
82168
_OPCODE_STACK_EFFECT_METHODDEF
169+
_OPCODE_IS_VALID_METHODDEF
170+
_OPCODE_HAS_ARG_METHODDEF
171+
_OPCODE_HAS_CONST_METHODDEF
172+
_OPCODE_HAS_NAME_METHODDEF
173+
_OPCODE_HAS_JUMP_METHODDEF
83174
_OPCODE_GET_SPECIALIZATION_STATS_METHODDEF
84175
{NULL, NULL, 0, NULL}
85176
};

0 commit comments

Comments
 (0)
0