[Python-checkins] gh-105481: expose opcode metadata via the _opcode module (#106688)

iritkatriel webhook-mailer at python.org
Fri Jul 14 13:41:56 EDT 2023


https://github.com/python/cpython/commit/6a70edf24ca217c5ed4a556d0df5748fc775c762
commit: 6a70edf24ca217c5ed4a556d0df5748fc775c762
branch: main
author: Irit Katriel <1055913+iritkatriel at users.noreply.github.com>
committer: iritkatriel <1055913+iritkatriel at users.noreply.github.com>
date: 2023-07-14T18:41:52+01:00
summary:

gh-105481: expose opcode metadata via the _opcode module (#106688)

files:
A Misc/NEWS.d/next/Library/2023-07-13-16-04-15.gh-issue-105481.pYSwMj.rst
M Include/cpython/compile.h
M Include/internal/pycore_opcode_metadata.h
M Lib/test/test__opcode.py
M Modules/_opcode.c
M Modules/clinic/_opcode.c.h
M Python/compile.c
M Python/flowgraph.c
M Tools/cases_generator/generate_cases.py

diff --git a/Include/cpython/compile.h b/Include/cpython/compile.h
index f5a62a8ec6dd0..cd7fd7bd37766 100644
--- a/Include/cpython/compile.h
+++ b/Include/cpython/compile.h
@@ -67,3 +67,9 @@ typedef struct {
 #define PY_INVALID_STACK_EFFECT INT_MAX
 PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg);
 PyAPI_FUNC(int) PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump);
+
+PyAPI_FUNC(int) PyUnstable_OpcodeIsValid(int opcode);
+PyAPI_FUNC(int) PyUnstable_OpcodeHasArg(int opcode);
+PyAPI_FUNC(int) PyUnstable_OpcodeHasConst(int opcode);
+PyAPI_FUNC(int) PyUnstable_OpcodeHasName(int opcode);
+PyAPI_FUNC(int) PyUnstable_OpcodeHasJump(int opcode);
diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h
index e94732b64384b..8373f56653b1c 100644
--- a/Include/internal/pycore_opcode_metadata.h
+++ b/Include/internal/pycore_opcode_metadata.h
@@ -3,6 +3,8 @@
 //   Python/bytecodes.c
 // Do not edit!
 
+#include <stdbool.h>
+
 
 #define IS_PSEUDO_INSTR(OP)  ( \
     ((OP) == LOAD_CLOSURE) || \
@@ -941,14 +943,20 @@ _PyOpcode_num_pushed(int opcode, int oparg, bool jump) {
 #endif
 
 enum InstructionFormat { INSTR_FMT_IB, INSTR_FMT_IBC, INSTR_FMT_IBC00, INSTR_FMT_IBC000, INSTR_FMT_IBC00000, INSTR_FMT_IBC00000000, INSTR_FMT_IX, INSTR_FMT_IXC, INSTR_FMT_IXC0, INSTR_FMT_IXC00, INSTR_FMT_IXC000 };
+
+#define IS_VALID_OPCODE(OP) \
+    (((OP) >= 0) && ((OP) < OPCODE_METADATA_SIZE) && \
+     (_PyOpcode_opcode_metadata[(OP)].valid_entry))
+
 #define HAS_ARG_FLAG (1)
 #define HAS_CONST_FLAG (2)
 #define HAS_NAME_FLAG (4)
 #define HAS_JUMP_FLAG (8)
-#define OPCODE_HAS_ARG(OP) (_PyOpcode_opcode_metadata[(OP)].flags & (HAS_ARG_FLAG))
-#define OPCODE_HAS_CONST(OP) (_PyOpcode_opcode_metadata[(OP)].flags & (HAS_CONST_FLAG))
-#define OPCODE_HAS_NAME(OP) (_PyOpcode_opcode_metadata[(OP)].flags & (HAS_NAME_FLAG))
-#define OPCODE_HAS_JUMP(OP) (_PyOpcode_opcode_metadata[(OP)].flags & (HAS_JUMP_FLAG))
+#define OPCODE_HAS_ARG(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_ARG_FLAG))
+#define OPCODE_HAS_CONST(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_CONST_FLAG))
+#define OPCODE_HAS_NAME(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_NAME_FLAG))
+#define OPCODE_HAS_JUMP(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_JUMP_FLAG))
+
 struct opcode_metadata {
     bool valid_entry;
     enum InstructionFormat instr_format;
@@ -971,12 +979,16 @@ struct opcode_macro_expansion {
 #define SAME_OPCODE_METADATA(OP1, OP2) \
         (OPCODE_METADATA_FMT(OP1) == OPCODE_METADATA_FMT(OP2))
 
+#define OPCODE_METADATA_SIZE 512
+#define OPCODE_UOP_NAME_SIZE 512
+#define OPCODE_MACRO_EXPANSION_SIZE 256
+
 #ifndef NEED_OPCODE_METADATA
-extern const struct opcode_metadata _PyOpcode_opcode_metadata[512];
-extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[256];
-extern const char * const _PyOpcode_uop_name[512];
+extern const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE];
+extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE];
+extern const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE];
 #else // if NEED_OPCODE_METADATA
-const struct opcode_metadata _PyOpcode_opcode_metadata[512] = {
+const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = {
     [NOP] = { true, INSTR_FMT_IX, 0 },
     [RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG },
     [INSTRUMENTED_RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG },
@@ -1194,7 +1206,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[512] = {
     [CACHE] = { true, INSTR_FMT_IX, 0 },
     [RESERVED] = { true, INSTR_FMT_IX, 0 },
 };
-const struct opcode_macro_expansion _PyOpcode_macro_expansion[256] = {
+const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE] = {
     [NOP] = { .nuops = 1, .uops = { { NOP, 0, 0 } } },
     [LOAD_FAST_CHECK] = { .nuops = 1, .uops = { { LOAD_FAST_CHECK, 0, 0 } } },
     [LOAD_FAST] = { .nuops = 1, .uops = { { LOAD_FAST, 0, 0 } } },
@@ -1308,7 +1320,7 @@ const struct opcode_macro_expansion _PyOpcode_macro_expansion[256] = {
     [BINARY_OP] = { .nuops = 1, .uops = { { BINARY_OP, 0, 0 } } },
     [SWAP] = { .nuops = 1, .uops = { { SWAP, 0, 0 } } },
 };
-const char * const _PyOpcode_uop_name[512] = {
+const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE] = {
     [EXIT_TRACE] = "EXIT_TRACE",
     [SAVE_IP] = "SAVE_IP",
     [_GUARD_BOTH_INT] = "_GUARD_BOTH_INT",
diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py
index 3e084928844d2..7d9553d9e383a 100644
--- a/Lib/test/test__opcode.py
+++ b/Lib/test/test__opcode.py
@@ -9,6 +9,70 @@
 
 class OpcodeTests(unittest.TestCase):
 
+    def check_bool_function_result(self, func, ops, expected):
+        for op in ops:
+            if isinstance(op, str):
+                op = dis.opmap[op]
+            with self.subTest(opcode=op, func=func):
+                self.assertIsInstance(func(op), bool)
+                self.assertEqual(func(op), expected)
+
+    def test_invalid_opcodes(self):
+        invalid = [-100, -1, 255, 512, 513, 1000]
+        self.check_bool_function_result(_opcode.is_valid, invalid, False)
+        self.check_bool_function_result(_opcode.has_arg, invalid, False)
+        self.check_bool_function_result(_opcode.has_const, invalid, False)
+        self.check_bool_function_result(_opcode.has_name, invalid, False)
+        self.check_bool_function_result(_opcode.has_jump, invalid, False)
+
+    def test_is_valid(self):
+        names = [
+            'CACHE',
+            'POP_TOP',
+            'IMPORT_NAME',
+            'JUMP',
+            'INSTRUMENTED_RETURN_VALUE',
+        ]
+        opcodes = [dis.opmap[opname] for opname in names]
+        self.check_bool_function_result(_opcode.is_valid, opcodes, True)
+
+    def test_has_arg(self):
+        has_arg = ['SWAP', 'LOAD_FAST', 'INSTRUMENTED_POP_JUMP_IF_TRUE', 'JUMP']
+        no_arg = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
+        self.check_bool_function_result(_opcode.has_arg, has_arg, True)
+        self.check_bool_function_result(_opcode.has_arg, no_arg, False)
+
+    def test_has_const(self):
+        has_const = ['LOAD_CONST', 'RETURN_CONST', 'KW_NAMES']
+        no_const = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
+        self.check_bool_function_result(_opcode.has_const, has_const, True)
+        self.check_bool_function_result(_opcode.has_const, no_const, False)
+
+    def test_has_name(self):
+        has_name = ['STORE_NAME', 'DELETE_ATTR', 'STORE_GLOBAL', 'IMPORT_FROM',
+                    'LOAD_FROM_DICT_OR_GLOBALS']
+        no_name = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
+        self.check_bool_function_result(_opcode.has_name, has_name, True)
+        self.check_bool_function_result(_opcode.has_name, no_name, False)
+
+    def test_has_jump(self):
+        has_jump = ['FOR_ITER', 'JUMP_FORWARD', 'JUMP', 'POP_JUMP_IF_TRUE', 'SEND']
+        no_jump = ['SETUP_WITH', 'POP_TOP', 'NOP', 'CACHE']
+        self.check_bool_function_result(_opcode.has_jump, has_jump, True)
+        self.check_bool_function_result(_opcode.has_jump, no_jump, False)
+
+    # the following test is part of the refactor, it will be removed soon
+    def test_against_legacy_bool_values(self):
+        # limiting to ops up to ENTER_EXECUTOR, because everything after that
+        # is not currently categorized correctly in opcode.py.
+        for op in range(0, opcode.opmap['ENTER_EXECUTOR']):
+            with self.subTest(op=op):
+                if opcode.opname[op] != f'<{op}>':
+                    self.assertEqual(op in dis.hasarg, _opcode.has_arg(op))
+                    self.assertEqual(op in dis.hasconst, _opcode.has_const(op))
+                    self.assertEqual(op in dis.hasname, _opcode.has_name(op))
+                    self.assertEqual(op in dis.hasjrel, _opcode.has_jump(op))
+
     def test_stack_effect(self):
         self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1)
         self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
diff --git a/Misc/NEWS.d/next/Library/2023-07-13-16-04-15.gh-issue-105481.pYSwMj.rst b/Misc/NEWS.d/next/Library/2023-07-13-16-04-15.gh-issue-105481.pYSwMj.rst
new file mode 100644
index 0000000000000..bc2ba51d31aa9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-07-13-16-04-15.gh-issue-105481.pYSwMj.rst
@@ -0,0 +1 @@
+Expose opcode metadata through :mod:`_opcode`.
diff --git a/Modules/_opcode.c b/Modules/_opcode.c
index 3c0fce48770ac..b3b9873d21a5b 100644
--- a/Modules/_opcode.c
+++ b/Modules/_opcode.c
@@ -1,4 +1,5 @@
 #include "Python.h"
+#include "compile.h"
 #include "opcode.h"
 #include "internal/pycore_code.h"
 
@@ -61,6 +62,91 @@ _opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg,
 
 /*[clinic input]
 
+_opcode.is_valid -> bool
+
+  opcode: int
+
+Return True if opcode is valid, False otherwise.
+[clinic start generated code]*/
+
+static int
+_opcode_is_valid_impl(PyObject *module, int opcode)
+/*[clinic end generated code: output=b0d918ea1d073f65 input=fe23e0aa194ddae0]*/
+{
+    return PyUnstable_OpcodeIsValid(opcode);
+}
+
+/*[clinic input]
+
+_opcode.has_arg -> bool
+
+  opcode: int
+
+Return True if the opcode uses its oparg, False otherwise.
+[clinic start generated code]*/
+
+static int
+_opcode_has_arg_impl(PyObject *module, int opcode)
+/*[clinic end generated code: output=7a062d3b2dcc0815 input=93d878ba6361db5f]*/
+{
+    return PyUnstable_OpcodeIsValid(opcode) &&
+           PyUnstable_OpcodeHasArg(opcode);
+}
+
+/*[clinic input]
+
+_opcode.has_const -> bool
+
+  opcode: int
+
+Return True if the opcode accesses a constant, False otherwise.
+[clinic start generated code]*/
+
+static int
+_opcode_has_const_impl(PyObject *module, int opcode)
+/*[clinic end generated code: output=c646d5027c634120 input=a6999e4cf13f9410]*/
+{
+    return PyUnstable_OpcodeIsValid(opcode) &&
+           PyUnstable_OpcodeHasConst(opcode);
+}
+
+/*[clinic input]
+
+_opcode.has_name -> bool
+
+  opcode: int
+
+Return True if the opcode accesses an attribute by name, False otherwise.
+[clinic start generated code]*/
+
+static int
+_opcode_has_name_impl(PyObject *module, int opcode)
+/*[clinic end generated code: output=b49a83555c2fa517 input=448aa5e4bcc947ba]*/
+{
+    return PyUnstable_OpcodeIsValid(opcode) &&
+           PyUnstable_OpcodeHasName(opcode);
+}
+
+/*[clinic input]
+
+_opcode.has_jump -> bool
+
+  opcode: int
+
+Return True if the opcode has a jump target, False otherwise.
+[clinic start generated code]*/
+
+static int
+_opcode_has_jump_impl(PyObject *module, int opcode)
+/*[clinic end generated code: output=e9c583c669f1c46a input=35f711274357a0c3]*/
+{
+    return PyUnstable_OpcodeIsValid(opcode) &&
+           PyUnstable_OpcodeHasJump(opcode);
+
+}
+
+/*[clinic input]
+
 _opcode.get_specialization_stats
 
 Return the specialization stats
@@ -80,6 +166,11 @@ _opcode_get_specialization_stats_impl(PyObject *module)
 static PyMethodDef
 opcode_functions[] =  {
     _OPCODE_STACK_EFFECT_METHODDEF
+    _OPCODE_IS_VALID_METHODDEF
+    _OPCODE_HAS_ARG_METHODDEF
+    _OPCODE_HAS_CONST_METHODDEF
+    _OPCODE_HAS_NAME_METHODDEF
+    _OPCODE_HAS_JUMP_METHODDEF
     _OPCODE_GET_SPECIALIZATION_STATS_METHODDEF
     {NULL, NULL, 0, NULL}
 };
diff --git a/Modules/clinic/_opcode.c.h b/Modules/clinic/_opcode.c.h
index 3bd3ba0238743..3eb050e470c34 100644
--- a/Modules/clinic/_opcode.c.h
+++ b/Modules/clinic/_opcode.c.h
@@ -86,6 +86,321 @@ _opcode_stack_effect(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
     return return_value;
 }
 
+PyDoc_STRVAR(_opcode_is_valid__doc__,
+"is_valid($module, /, opcode)\n"
+"--\n"
+"\n"
+"Return True if opcode is valid, False otherwise.");
+
+#define _OPCODE_IS_VALID_METHODDEF    \
+    {"is_valid", _PyCFunction_CAST(_opcode_is_valid), METH_FASTCALL|METH_KEYWORDS, _opcode_is_valid__doc__},
+
+static int
+_opcode_is_valid_impl(PyObject *module, int opcode);
+
+static PyObject *
+_opcode_is_valid(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 1
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(opcode), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"opcode", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "is_valid",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[1];
+    int opcode;
+    int _return_value;
+
+    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+    if (!args) {
+        goto exit;
+    }
+    opcode = _PyLong_AsInt(args[0]);
+    if (opcode == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    _return_value = _opcode_is_valid_impl(module, opcode);
+    if ((_return_value == -1) && PyErr_Occurred()) {
+        goto exit;
+    }
+    return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_opcode_has_arg__doc__,
+"has_arg($module, /, opcode)\n"
+"--\n"
+"\n"
+"Return True if the opcode uses its oparg, False otherwise.");
+
+#define _OPCODE_HAS_ARG_METHODDEF    \
+    {"has_arg", _PyCFunction_CAST(_opcode_has_arg), METH_FASTCALL|METH_KEYWORDS, _opcode_has_arg__doc__},
+
+static int
+_opcode_has_arg_impl(PyObject *module, int opcode);
+
+static PyObject *
+_opcode_has_arg(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 1
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(opcode), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"opcode", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "has_arg",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[1];
+    int opcode;
+    int _return_value;
+
+    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+    if (!args) {
+        goto exit;
+    }
+    opcode = _PyLong_AsInt(args[0]);
+    if (opcode == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    _return_value = _opcode_has_arg_impl(module, opcode);
+    if ((_return_value == -1) && PyErr_Occurred()) {
+        goto exit;
+    }
+    return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_opcode_has_const__doc__,
+"has_const($module, /, opcode)\n"
+"--\n"
+"\n"
+"Return True if the opcode accesses a constant, False otherwise.");
+
+#define _OPCODE_HAS_CONST_METHODDEF    \
+    {"has_const", _PyCFunction_CAST(_opcode_has_const), METH_FASTCALL|METH_KEYWORDS, _opcode_has_const__doc__},
+
+static int
+_opcode_has_const_impl(PyObject *module, int opcode);
+
+static PyObject *
+_opcode_has_const(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 1
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(opcode), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"opcode", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "has_const",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[1];
+    int opcode;
+    int _return_value;
+
+    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+    if (!args) {
+        goto exit;
+    }
+    opcode = _PyLong_AsInt(args[0]);
+    if (opcode == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    _return_value = _opcode_has_const_impl(module, opcode);
+    if ((_return_value == -1) && PyErr_Occurred()) {
+        goto exit;
+    }
+    return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_opcode_has_name__doc__,
+"has_name($module, /, opcode)\n"
+"--\n"
+"\n"
+"Return True if the opcode accesses an attribute by name, False otherwise.");
+
+#define _OPCODE_HAS_NAME_METHODDEF    \
+    {"has_name", _PyCFunction_CAST(_opcode_has_name), METH_FASTCALL|METH_KEYWORDS, _opcode_has_name__doc__},
+
+static int
+_opcode_has_name_impl(PyObject *module, int opcode);
+
+static PyObject *
+_opcode_has_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 1
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(opcode), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"opcode", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "has_name",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[1];
+    int opcode;
+    int _return_value;
+
+    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+    if (!args) {
+        goto exit;
+    }
+    opcode = _PyLong_AsInt(args[0]);
+    if (opcode == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    _return_value = _opcode_has_name_impl(module, opcode);
+    if ((_return_value == -1) && PyErr_Occurred()) {
+        goto exit;
+    }
+    return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_opcode_has_jump__doc__,
+"has_jump($module, /, opcode)\n"
+"--\n"
+"\n"
+"Return True if the opcode has a jump target, False otherwise.");
+
+#define _OPCODE_HAS_JUMP_METHODDEF    \
+    {"has_jump", _PyCFunction_CAST(_opcode_has_jump), METH_FASTCALL|METH_KEYWORDS, _opcode_has_jump__doc__},
+
+static int
+_opcode_has_jump_impl(PyObject *module, int opcode);
+
+static PyObject *
+_opcode_has_jump(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 1
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(opcode), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"opcode", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "has_jump",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[1];
+    int opcode;
+    int _return_value;
+
+    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+    if (!args) {
+        goto exit;
+    }
+    opcode = _PyLong_AsInt(args[0]);
+    if (opcode == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    _return_value = _opcode_has_jump_impl(module, opcode);
+    if ((_return_value == -1) && PyErr_Occurred()) {
+        goto exit;
+    }
+    return_value = PyBool_FromLong((long)_return_value);
+
+exit:
+    return return_value;
+}
+
 PyDoc_STRVAR(_opcode_get_specialization_stats__doc__,
 "get_specialization_stats($module, /)\n"
 "--\n"
@@ -103,4 +418,4 @@ _opcode_get_specialization_stats(PyObject *module, PyObject *Py_UNUSED(ignored))
 {
     return _opcode_get_specialization_stats_impl(module);
 }
-/*[clinic end generated code: output=21e3d53a659c651a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=ae2b2ef56d582180 input=a9049054013a1b77]*/
diff --git a/Python/compile.c b/Python/compile.c
index d9e38cfdefaf2..9e86e06777ffa 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -36,7 +36,9 @@
 #include "pycore_pystate.h"       // _Py_GetConfig()
 #include "pycore_symtable.h"      // PySTEntryObject, _PyFuture_FromAST()
 
+#define NEED_OPCODE_METADATA
 #include "pycore_opcode_metadata.h" // _PyOpcode_opcode_metadata, _PyOpcode_num_popped/pushed
+#undef NEED_OPCODE_METADATA
 
 #define COMP_GENEXP   0
 #define COMP_LISTCOMP 1
@@ -864,6 +866,36 @@ PyCompile_OpcodeStackEffect(int opcode, int oparg)
     return stack_effect(opcode, oparg, -1);
 }
 
+int
+PyUnstable_OpcodeIsValid(int opcode)
+{
+    return IS_VALID_OPCODE(opcode);
+}
+
+int
+PyUnstable_OpcodeHasArg(int opcode)
+{
+    return OPCODE_HAS_ARG(opcode);
+}
+
+int
+PyUnstable_OpcodeHasConst(int opcode)
+{
+    return OPCODE_HAS_CONST(opcode);
+}
+
+int
+PyUnstable_OpcodeHasName(int opcode)
+{
+    return OPCODE_HAS_NAME(opcode);
+}
+
+int
+PyUnstable_OpcodeHasJump(int opcode)
+{
+    return OPCODE_HAS_JUMP(opcode);
+}
+
 static int
 codegen_addop_noarg(instr_sequence *seq, int opcode, location loc)
 {
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index 04f269c585383..e485ed103147a 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -7,9 +7,7 @@
 #include "pycore_pymem.h"         // _PyMem_IsPtrFreed()
 
 #include "pycore_opcode_utils.h"
-#define NEED_OPCODE_METADATA
-#include "pycore_opcode_metadata.h" // _PyOpcode_opcode_metadata, _PyOpcode_num_popped/pushed
-#undef NEED_OPCODE_METADATA
+#include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc
 
 
 #undef SUCCESS
diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py
index ce16271097b95..6589289121863 100644
--- a/Tools/cases_generator/generate_cases.py
+++ b/Tools/cases_generator/generate_cases.py
@@ -308,7 +308,7 @@ def emit_macros(cls, out: Formatter):
         for name, value in flags.bitmask.items():
             out.emit(
                 f"#define OPCODE_{name[:-len('_FLAG')]}(OP) "
-                f"(_PyOpcode_opcode_metadata[(OP)].flags & ({name}))")
+                f"(_PyOpcode_opcode_metadata[OP].flags & ({name}))")
 
 
 @dataclasses.dataclass
@@ -1192,6 +1192,8 @@ def write_metadata(self) -> None:
 
             self.write_provenance_header()
 
+            self.out.emit("\n#include <stdbool.h>")
+
             self.write_pseudo_instrs()
 
             self.out.emit("")
@@ -1202,8 +1204,16 @@ def write_metadata(self) -> None:
             # Write type definitions
             self.out.emit(f"enum InstructionFormat {{ {', '.join(format_enums)} }};")
 
+            self.out.emit("")
+            self.out.emit(
+                "#define IS_VALID_OPCODE(OP) \\\n"
+                "    (((OP) >= 0) && ((OP) < OPCODE_METADATA_SIZE) && \\\n"
+                "     (_PyOpcode_opcode_metadata[(OP)].valid_entry))")
+
+            self.out.emit("")
             InstructionFlags.emit_macros(self.out)
 
+            self.out.emit("")
             with self.out.block("struct opcode_metadata", ";"):
                 self.out.emit("bool valid_entry;")
                 self.out.emit("enum InstructionFormat instr_format;")
@@ -1226,13 +1236,20 @@ def write_metadata(self) -> None:
             self.out.emit("")
 
             # Write metadata array declaration
+            self.out.emit("#define OPCODE_METADATA_SIZE 512")
+            self.out.emit("#define OPCODE_UOP_NAME_SIZE 512")
+            self.out.emit("#define OPCODE_MACRO_EXPANSION_SIZE 256")
+            self.out.emit("")
             self.out.emit("#ifndef NEED_OPCODE_METADATA")
-            self.out.emit("extern const struct opcode_metadata _PyOpcode_opcode_metadata[512];")
-            self.out.emit("extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[256];")
-            self.out.emit("extern const char * const _PyOpcode_uop_name[512];")
+            self.out.emit("extern const struct opcode_metadata "
+                          "_PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE];")
+            self.out.emit("extern const struct opcode_macro_expansion "
+                          "_PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE];")
+            self.out.emit("extern const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE];")
             self.out.emit("#else // if NEED_OPCODE_METADATA")
 
-            self.out.emit("const struct opcode_metadata _PyOpcode_opcode_metadata[512] = {")
+            self.out.emit("const struct opcode_metadata "
+                          "_PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = {")
 
             # Write metadata for each instruction
             for thing in self.everything:
@@ -1253,7 +1270,8 @@ def write_metadata(self) -> None:
             self.out.emit("};")
 
             with self.out.block(
-                "const struct opcode_macro_expansion _PyOpcode_macro_expansion[256] =",
+                "const struct opcode_macro_expansion "
+                "_PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE] =",
                 ";",
             ):
                 # Write macro expansion for each non-pseudo instruction
@@ -1279,7 +1297,7 @@ def write_metadata(self) -> None:
                         case _:
                             typing.assert_never(thing)
 
-            with self.out.block("const char * const _PyOpcode_uop_name[512] =", ";"):
+            with self.out.block("const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE] =", ";"):
                 self.write_uop_items(lambda name, counter: f"[{name}] = \"{name}\",")
 
             self.out.emit("#endif // NEED_OPCODE_METADATA")



More information about the Python-checkins mailing list