Skip to content

Commit 318ea2c

Browse files
authored
GH-106360: Support very basic superblock introspection (#106422)
* Add len() and indexing support to uop superblocks.
1 parent 80f1c6c commit 318ea2c

File tree

8 files changed

+130
-14
lines changed

8 files changed

+130
-14
lines changed

Include/cpython/optimizer.h

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ PyAPI_FUNC(void) PyUnstable_SetOptimizer(_PyOptimizerObject* optimizer);
3838

3939
PyAPI_FUNC(_PyOptimizerObject *) PyUnstable_GetOptimizer(void);
4040

41+
PyAPI_FUNC(_PyExecutorObject *)PyUnstable_GetExecutor(PyCodeObject *code, int offset);
42+
4143
struct _PyInterpreterFrame *
4244
_PyOptimizer_BackEdge(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_CODEUNIT *dest, PyObject **stack_pointer);
4345

Include/internal/pycore_opcode.h

+6-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_capi/test_misc.py

+23
Original file line numberDiff line numberDiff line change
@@ -2415,5 +2415,28 @@ def long_loop():
24152415
self.assertEqual(opt.get_count(), 10)
24162416

24172417

2418+
class TestUops(unittest.TestCase):
2419+
2420+
def test_basic_loop(self):
2421+
2422+
def testfunc(x):
2423+
i = 0
2424+
while i < x:
2425+
i += 1
2426+
2427+
testfunc(1000)
2428+
2429+
ex = None
2430+
for offset in range(0, 100, 2):
2431+
try:
2432+
ex = _testinternalcapi.get_executor(testfunc.__code__, offset)
2433+
break
2434+
except ValueError:
2435+
pass
2436+
if ex is None:
2437+
return
2438+
self.assertIn("SAVE_IP", str(ex))
2439+
2440+
24182441
if __name__ == "__main__":
24192442
unittest.main()

Modules/_testinternalcapi.c

+21
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,26 @@ get_optimizer(PyObject *self, PyObject *Py_UNUSED(ignored))
858858
return opt;
859859
}
860860

861+
static PyObject *
862+
get_executor(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
863+
{
864+
865+
if (!_PyArg_CheckPositional("get_executor", nargs, 2, 2)) {
866+
return NULL;
867+
}
868+
PyObject *code = args[0];
869+
PyObject *offset = args[1];
870+
long ioffset = PyLong_AsLong(offset);
871+
if (ioffset == -1 && PyErr_Occurred()) {
872+
return NULL;
873+
}
874+
if (!PyCode_Check(code)) {
875+
PyErr_SetString(PyExc_TypeError, "first argument must be a code object");
876+
return NULL;
877+
}
878+
return (PyObject *)PyUnstable_GetExecutor((PyCodeObject *)code, ioffset);
879+
}
880+
861881
static int _pending_callback(void *arg)
862882
{
863883
/* we assume the argument is callable object to which we own a reference */
@@ -1326,6 +1346,7 @@ static PyMethodDef module_functions[] = {
13261346
{"iframe_getlasti", iframe_getlasti, METH_O, NULL},
13271347
{"get_optimizer", get_optimizer, METH_NOARGS, NULL},
13281348
{"set_optimizer", set_optimizer, METH_O, NULL},
1349+
{"get_executor", _PyCFunction_CAST(get_executor), METH_FASTCALL, NULL},
13291350
{"get_counter_optimizer", get_counter_optimizer, METH_NOARGS, NULL},
13301351
{"get_uop_optimizer", get_uop_optimizer, METH_NOARGS, NULL},
13311352
{"pending_threadfunc", _PyCFunction_CAST(pending_threadfunc),

Python/opcode_metadata.h

+2-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/optimizer.c

+70
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,23 @@ _PyOptimizer_BackEdge(_PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_CODEUNI
188188
return frame;
189189
}
190190

191+
_PyExecutorObject *
192+
PyUnstable_GetExecutor(PyCodeObject *code, int offset)
193+
{
194+
int code_len = (int)Py_SIZE(code);
195+
for (int i = 0 ; i < code_len;) {
196+
if (_PyCode_CODE(code)[i].op.code == ENTER_EXECUTOR && i*2 == offset) {
197+
int oparg = _PyCode_CODE(code)[i].op.arg;
198+
_PyExecutorObject *res = code->co_executors->executors[oparg];
199+
Py_INCREF(res);
200+
return res;
201+
}
202+
i += _PyInstruction_GetLength(code, i);
203+
}
204+
PyErr_SetString(PyExc_ValueError, "no executor at given offset");
205+
return NULL;
206+
}
207+
191208
/** Test support **/
192209

193210

@@ -287,13 +304,66 @@ uop_dealloc(_PyUOpExecutorObject *self) {
287304
PyObject_Free(self);
288305
}
289306

307+
static const char *
308+
uop_name(int index) {
309+
if (index < EXIT_TRACE) {
310+
return _PyOpcode_OpName[index];
311+
}
312+
return _PyOpcode_uop_name[index];
313+
}
314+
315+
static Py_ssize_t
316+
uop_len(_PyUOpExecutorObject *self)
317+
{
318+
int count = 1;
319+
for (; count < _Py_UOP_MAX_TRACE_LENGTH; count++) {
320+
if (self->trace[count-1].opcode == EXIT_TRACE) {
321+
break;
322+
}
323+
}
324+
return count;
325+
}
326+
327+
static PyObject *
328+
uop_item(_PyUOpExecutorObject *self, Py_ssize_t index)
329+
{
330+
for (int i = 0; i < _Py_UOP_MAX_TRACE_LENGTH; i++) {
331+
if (self->trace[i].opcode == EXIT_TRACE) {
332+
break;
333+
}
334+
if (i != index) {
335+
continue;
336+
}
337+
const char *name = uop_name(self->trace[i].opcode);
338+
PyObject *oname = _PyUnicode_FromASCII(name, strlen(name));
339+
if (oname == NULL) {
340+
return NULL;
341+
}
342+
PyObject *operand = PyLong_FromUnsignedLongLong(self->trace[i].operand);
343+
if (operand == NULL) {
344+
Py_DECREF(oname);
345+
return NULL;
346+
}
347+
PyObject *args[2] = { oname, operand };
348+
return _PyTuple_FromArraySteal(args, 2);
349+
}
350+
PyErr_SetNone(PyExc_IndexError);
351+
return NULL;
352+
}
353+
354+
PySequenceMethods uop_as_sequence = {
355+
.sq_length = (lenfunc)uop_len,
356+
.sq_item = (ssizeargfunc)uop_item,
357+
};
358+
290359
static PyTypeObject UOpExecutor_Type = {
291360
PyVarObject_HEAD_INIT(&PyType_Type, 0)
292361
.tp_name = "uop_executor",
293362
.tp_basicsize = sizeof(_PyUOpExecutorObject),
294363
.tp_itemsize = 0,
295364
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
296365
.tp_dealloc = (destructor)uop_dealloc,
366+
.tp_as_sequence = &uop_as_sequence,
297367
};
298368

299369
static int

Tools/build/generate_opcode_h.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,15 @@ def main(opcode_py,
184184
fobj.write(f"#define ENABLE_SPECIALIZATION {int(ENABLE_SPECIALIZATION)}")
185185

186186
iobj.write("\n")
187-
iobj.write("#ifdef Py_DEBUG\n")
188-
iobj.write(f"static const char *const _PyOpcode_OpName[{NUM_OPCODES}] = {{\n")
187+
iobj.write(f"\nextern const char *const _PyOpcode_OpName[{NUM_OPCODES}];\n")
188+
iobj.write("\n#ifdef NEED_OPCODE_TABLES\n")
189+
iobj.write(f"const char *const _PyOpcode_OpName[{NUM_OPCODES}] = {{\n")
189190
for op, name in enumerate(opname_including_specialized):
190191
if name[0] != "<":
191192
op = name
192193
iobj.write(f''' [{op}] = "{name}",\n''')
193194
iobj.write("};\n")
194-
iobj.write("#endif\n")
195+
iobj.write("#endif // NEED_OPCODE_TABLES\n")
195196

196197
iobj.write("\n")
197198
iobj.write("#define EXTRA_CASES \\\n")

Tools/cases_generator/generate_cases.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1224,9 +1224,7 @@ def write_metadata(self) -> None:
12241224
self.out.emit("#ifndef NEED_OPCODE_METADATA")
12251225
self.out.emit("extern const struct opcode_metadata _PyOpcode_opcode_metadata[512];")
12261226
self.out.emit("extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[256];")
1227-
self.out.emit("#ifdef Py_DEBUG")
12281227
self.out.emit("extern const char * const _PyOpcode_uop_name[512];")
1229-
self.out.emit("#endif")
12301228
self.out.emit("#else")
12311229

12321230
self.out.emit("const struct opcode_metadata _PyOpcode_opcode_metadata[512] = {")
@@ -1273,10 +1271,10 @@ def write_metadata(self) -> None:
12731271
case _:
12741272
typing.assert_never(thing)
12751273

1276-
self.out.emit("#ifdef Py_DEBUG")
1274+
self.out.emit("#ifdef NEED_OPCODE_METADATA")
12771275
with self.out.block("const char * const _PyOpcode_uop_name[512] =", ";"):
12781276
self.write_uop_items(lambda name, counter: f"[{counter}] = \"{name}\",")
1279-
self.out.emit("#endif")
1277+
self.out.emit("#endif // NEED_OPCODE_METADATA")
12801278

12811279
self.out.emit("#endif")
12821280

0 commit comments

Comments
 (0)