[Python-checkins] gh-94216: add pseudo instructions to the dis/opcodes modules (GH-94241)

iritkatriel webhook-mailer at python.org
Fri Jul 1 10:33:41 EDT 2022


https://github.com/python/cpython/commit/c57aad777afc6c0b382981ee9e4bc94c03bf5f68
commit: c57aad777afc6c0b382981ee9e4bc94c03bf5f68
branch: main
author: Irit Katriel <1055913+iritkatriel at users.noreply.github.com>
committer: iritkatriel <1055913+iritkatriel at users.noreply.github.com>
date: 2022-07-01T15:33:35+01:00
summary:

gh-94216: add pseudo instructions to the dis/opcodes modules (GH-94241)

files:
A Misc/NEWS.d/next/Library/2022-06-24-20-00-57.gh-issue-94216.hxnQPu.rst
M Doc/library/dis.rst
M Doc/whatsnew/3.12.rst
M Include/internal/pycore_opcode.h
M Include/opcode.h
M Lib/dis.py
M Lib/opcode.py
M Lib/test/test__opcode.py
M Lib/test/test_dis.py
M Modules/_opcode.c
M Python/compile.c
M Python/makeopcodetargets.py
M Tools/scripts/generate_opcode_h.py

diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst
index d2c3c1a0966d7..9173c1bcf5e7b 100644
--- a/Doc/library/dis.rst
+++ b/Doc/library/dis.rst
@@ -1351,13 +1351,74 @@ iterations of the loop.
 .. opcode:: HAVE_ARGUMENT
 
    This is not really an opcode.  It identifies the dividing line between
-   opcodes which don't use their argument and those that do
-   (``< HAVE_ARGUMENT`` and ``>= HAVE_ARGUMENT``, respectively).
+   opcodes in the range [0,255] which don't use their argument and those
+   that do (``< HAVE_ARGUMENT`` and ``>= HAVE_ARGUMENT``, respectively).
+
+   If your application uses pseudo instructions, use the :data:`hasarg`
+   collection instead.
 
    .. versionchanged:: 3.6
       Now every instruction has an argument, but opcodes ``< HAVE_ARGUMENT``
       ignore it. Before, only opcodes ``>= HAVE_ARGUMENT`` had an argument.
 
+   .. versionchanged:: 3.12
+      Pseudo instructions were added to the :mod:`dis` module, and for them
+      it is not true that comparison with ``HAVE_ARGUMENT`` indicates whether
+      they use their arg.
+
+
+**Pseudo-instructions**
+
+These opcodes do not appear in python bytecode, they are used by the compiler
+but are replaced by real opcodes or removed before bytecode is generated.
+
+.. opcode:: SETUP_FINALLY (target)
+
+   Set up an exception handler for the following code block. If an exception
+   occurs, the value stack level is restored to its current state and control
+   is transferred to the exception handler at ``target``.
+
+
+.. opcode:: SETUP_CLEANUP (target)
+
+   Like ``SETUP_FINALLY``, but in case of exception also pushes the last
+   instruction (``lasti``) to the stack so that ``RERAISE`` can restore it.
+   If an exception occurs, the value stack level and the last instruction on
+   the frame are restored to their current state, and control is transferred
+   to the exception handler at ``target``.
+
+
+.. opcode:: SETUP_WITH (target)
+
+   Like ``SETUP_CLEANUP``, but in case of exception one more item is popped
+   from the stack before control is transferred to the exception handler at
+   ``target``.
+
+   This variant is used in :keyword:`with` and :keyword:`async with`
+   constructs, which push the return value of the context manager's
+   :meth:`~object.__enter__` or :meth:`~object.__aenter__` to the stack.
+
+
+.. opcode:: POP_BLOCK
+
+   Marks the end of the code block associated with the last ``SETUP_FINALLY``,
+   ``SETUP_CLEANUP`` or ``SETUP_WITH``.
+
+.. opcode:: JUMP
+.. opcode:: JUMP_NO_INTERRUPT
+.. opcode:: POP_JUMP_IF_FALSE
+.. opcode:: POP_JUMP_IF_TRUE
+.. opcode:: POP_JUMP_IF_NONE
+.. opcode:: POP_JUMP_IF_NOT_NONE
+
+   Undirected relative jump instructions which are replaced by their
+   directed (forward/backward) counterparts by the assembler.
+
+.. opcode:: LOAD_METHOD
+
+   Optimized unbound method lookup. Emitted as a ``LOAD_ATTR`` opcode
+   with a flag set in the arg.
+
 
 .. _opcode_collections:
 
@@ -1367,6 +1428,10 @@ Opcode collections
 These collections are provided for automatic introspection of bytecode
 instructions:
 
+   .. versionchanged:: 3.12
+      The collections now contain pseudo instructions as well. These are
+      opcodes with values ``>= MIN_PSEUDO_OPCODE``.
+
 .. data:: opname
 
    Sequence of operation names, indexable using the bytecode.
@@ -1382,6 +1447,13 @@ instructions:
    Sequence of all compare operation names.
 
 
+.. data:: hasarg
+
+   Sequence of bytecodes that use their argument.
+
+    .. versionadded:: 3.12
+
+
 .. data:: hasconst
 
    Sequence of bytecodes that access a constant.
@@ -1418,3 +1490,9 @@ instructions:
 .. data:: hascompare
 
    Sequence of bytecodes of Boolean operations.
+
+.. data:: hasexc
+
+   Sequence of bytecodes that set an exception handler.
+
+    .. versionadded:: 3.12
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 11e8b87f02881..620aa91f6da22 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -93,6 +93,17 @@ New Modules
 Improved Modules
 ================
 
+dis
+---
+
+* Pseudo instruction opcodes (which are used by the compiler but
+  do not appear in executable bytecode) are now exposed in the
+  :mod:`dis` module.
+  :data:`~dis.HAVE_ARGUMENT` is still relevant to real opcodes,
+  but it is not useful for pseudo instrcutions. Use the new
+  :data:`~dis.hasarg` collection instead.
+  (Contributed by Irit Katriel in :gh:`94216`.)
+
 os
 --
 
diff --git a/Include/internal/pycore_opcode.h b/Include/internal/pycore_opcode.h
index 7e3c6420b0317..3ca678d3dd85b 100644
--- a/Include/internal/pycore_opcode.h
+++ b/Include/internal/pycore_opcode.h
@@ -19,7 +19,7 @@ extern const uint8_t _PyOpcode_Deopt[256];
 extern const uint8_t _PyOpcode_Original[256];
 
 #ifdef NEED_OPCODE_TABLES
-static const uint32_t _PyOpcode_RelativeJump[8] = {
+static const uint32_t _PyOpcode_RelativeJump[9] = {
     0U,
     0U,
     536870912U,
@@ -28,8 +28,9 @@ static const uint32_t _PyOpcode_RelativeJump[8] = {
     122880U,
     0U,
     0U,
+    1008U,
 };
-static const uint32_t _PyOpcode_Jump[8] = {
+static const uint32_t _PyOpcode_Jump[9] = {
     0U,
     0U,
     536870912U,
@@ -38,6 +39,7 @@ static const uint32_t _PyOpcode_Jump[8] = {
     122880U,
     0U,
     0U,
+    1008U,
 };
 
 const uint8_t _PyOpcode_Caches[256] = {
@@ -427,7 +429,7 @@ const uint8_t _PyOpcode_Original[256] = {
 #endif   // NEED_OPCODE_TABLES
 
 #ifdef Py_DEBUG
-static const char *const _PyOpcode_OpName[256] = {
+static const char *const _PyOpcode_OpName[267] = {
     [CACHE] = "CACHE",
     [POP_TOP] = "POP_TOP",
     [PUSH_NULL] = "PUSH_NULL",
@@ -684,6 +686,17 @@ static const char *const _PyOpcode_OpName[256] = {
     [253] = "<253>",
     [254] = "<254>",
     [DO_TRACING] = "DO_TRACING",
+    [SETUP_FINALLY] = "SETUP_FINALLY",
+    [SETUP_CLEANUP] = "SETUP_CLEANUP",
+    [SETUP_WITH] = "SETUP_WITH",
+    [POP_BLOCK] = "POP_BLOCK",
+    [JUMP] = "JUMP",
+    [JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT",
+    [POP_JUMP_IF_FALSE] = "POP_JUMP_IF_FALSE",
+    [POP_JUMP_IF_TRUE] = "POP_JUMP_IF_TRUE",
+    [POP_JUMP_IF_NONE] = "POP_JUMP_IF_NONE",
+    [POP_JUMP_IF_NOT_NONE] = "POP_JUMP_IF_NOT_NONE",
+    [LOAD_METHOD] = "LOAD_METHOD",
 };
 #endif
 
diff --git a/Include/opcode.h b/Include/opcode.h
index 66e8c9e8cf82c..f90ebb1d6e01c 100644
--- a/Include/opcode.h
+++ b/Include/opcode.h
@@ -120,6 +120,19 @@ extern "C" {
 #define POP_JUMP_BACKWARD_IF_NONE              174
 #define POP_JUMP_BACKWARD_IF_FALSE             175
 #define POP_JUMP_BACKWARD_IF_TRUE              176
+#define MIN_PSEUDO_OPCODE                      256
+#define SETUP_FINALLY                          256
+#define SETUP_CLEANUP                          257
+#define SETUP_WITH                             258
+#define POP_BLOCK                              259
+#define JUMP                                   260
+#define JUMP_NO_INTERRUPT                      261
+#define POP_JUMP_IF_FALSE                      262
+#define POP_JUMP_IF_TRUE                       263
+#define POP_JUMP_IF_NONE                       264
+#define POP_JUMP_IF_NOT_NONE                   265
+#define LOAD_METHOD                            266
+#define MAX_PSEUDO_OPCODE                      266
 #define BINARY_OP_ADAPTIVE                       3
 #define BINARY_OP_ADD_FLOAT                      4
 #define BINARY_OP_ADD_INT                        5
@@ -194,9 +207,19 @@ extern "C" {
 #define UNPACK_SEQUENCE_TWO_TUPLE              182
 #define DO_TRACING                             255
 
+#define HAS_ARG(op) ((((op) >= HAVE_ARGUMENT) && (!IS_PSEUDO_OPCODE(op)))\
+    || ((op) == JUMP) \
+    || ((op) == JUMP_NO_INTERRUPT) \
+    || ((op) == POP_JUMP_IF_FALSE) \
+    || ((op) == POP_JUMP_IF_TRUE) \
+    || ((op) == POP_JUMP_IF_NONE) \
+    || ((op) == POP_JUMP_IF_NOT_NONE) \
+    || ((op) == LOAD_METHOD) \
+    )
+
 #define HAS_CONST(op) (false\
-    || ((op) == 100) \
-    || ((op) == 172) \
+    || ((op) == LOAD_CONST) \
+    || ((op) == KW_NAMES) \
     )
 
 #define NB_ADD                                   0
@@ -226,11 +249,8 @@ extern "C" {
 #define NB_INPLACE_TRUE_DIVIDE                  24
 #define NB_INPLACE_XOR                          25
 
-#define HAS_ARG(op) ((op) >= HAVE_ARGUMENT)
 
-/* Reserve some bytecodes for internal use in the compiler.
- * The value of 240 is arbitrary. */
-#define IS_ARTIFICIAL(op) ((op) > 240)
+#define IS_PSEUDO_OPCODE(op) (((op) >= MIN_PSEUDO_OPCODE) && ((op) <= MAX_PSEUDO_OPCODE))
 
 #ifdef __cplusplus
 }
diff --git a/Lib/dis.py b/Lib/dis.py
index bd87de97fc9ce..d63f35a42b246 100644
--- a/Lib/dis.py
+++ b/Lib/dis.py
@@ -610,7 +610,7 @@ def _unpack_opargs(code):
         op = code[i]
         deop = _deoptop(op)
         caches = _inline_cache_entries[deop]
-        if deop >= HAVE_ARGUMENT:
+        if deop in hasarg:
             arg = code[i+1] | extended_arg
             extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0
             # The oparg is stored as a signed integer
diff --git a/Lib/opcode.py b/Lib/opcode.py
index c7bc12a1fbacc..8a07fb75b2e97 100644
--- a/Lib/opcode.py
+++ b/Lib/opcode.py
@@ -4,9 +4,9 @@
 operate on bytecodes (e.g. peephole optimizers).
 """
 
-__all__ = ["cmp_op", "hasconst", "hasname", "hasjrel", "hasjabs",
-           "haslocal", "hascompare", "hasfree", "opname", "opmap",
-           "HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"]
+__all__ = ["cmp_op", "hasarg", "hasconst", "hasname", "hasjrel", "hasjabs",
+           "haslocal", "hascompare", "hasfree", "hasexc", "opname", "opmap",
+           "HAVE_ARGUMENT", "EXTENDED_ARG"]
 
 # It's a chicken-and-egg I'm afraid:
 # We're imported before _opcode's made.
@@ -23,6 +23,7 @@
 
 cmp_op = ('<', '<=', '==', '!=', '>', '>=')
 
+hasarg = []
 hasconst = []
 hasname = []
 hasjrel = []
@@ -30,13 +31,21 @@
 haslocal = []
 hascompare = []
 hasfree = []
-hasnargs = [] # unused
+hasexc = []
+
+def is_pseudo(op):
+    return op >= MIN_PSEUDO_OPCODE and op <= MAX_PSEUDO_OPCODE
+
+oplists = [hasarg, hasconst, hasname, hasjrel, hasjabs,
+           haslocal, hascompare, hasfree, hasexc]
 
 opmap = {}
-opname = ['<%r>' % (op,) for op in range(256)]
+
+## pseudo opcodes (used in the compiler) mapped to the values
+## they can become in the actual code.
+_pseudo_ops = {}
 
 def def_op(name, op):
-    opname[op] = name
     opmap[name] = op
 
 def name_op(name, op):
@@ -51,6 +60,17 @@ def jabs_op(name, op):
     def_op(name, op)
     hasjabs.append(op)
 
+def pseudo_op(name, op, real_ops):
+    def_op(name, op)
+    _pseudo_ops[name] = real_ops
+    # add the pseudo opcode to the lists its targets are in
+    for oplist in oplists:
+        res = [opmap[rop] in oplist for rop in real_ops]
+        if any(res):
+            assert all(res)
+            oplist.append(op)
+
+
 # Instruction opcodes for compiled code
 # Blank lines correspond to available opcodes
 
@@ -105,7 +125,7 @@ def jabs_op(name, op):
 def_op('PREP_RERAISE_STAR', 88)
 def_op('POP_EXCEPT', 89)
 
-HAVE_ARGUMENT = 90              # Opcodes from here have an argument:
+HAVE_ARGUMENT = 90             # real opcodes from here have an argument:
 
 name_op('STORE_NAME', 90)       # Index in name list
 name_op('DELETE_NAME', 91)      # ""
@@ -200,8 +220,34 @@ def jabs_op(name, op):
 jrel_op('POP_JUMP_BACKWARD_IF_FALSE', 175)
 jrel_op('POP_JUMP_BACKWARD_IF_TRUE', 176)
 
+hasarg.extend([op for op in opmap.values() if op >= HAVE_ARGUMENT])
+
+MIN_PSEUDO_OPCODE = 256
+
+pseudo_op('SETUP_FINALLY', 256, ['NOP'])
+hasexc.append(256)
+pseudo_op('SETUP_CLEANUP', 257, ['NOP'])
+hasexc.append(257)
+pseudo_op('SETUP_WITH', 258, ['NOP'])
+hasexc.append(258)
+pseudo_op('POP_BLOCK', 259, ['NOP'])
+
+pseudo_op('JUMP', 260, ['JUMP_FORWARD', 'JUMP_BACKWARD'])
+pseudo_op('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT'])
+pseudo_op('POP_JUMP_IF_FALSE', 262, ['POP_JUMP_FORWARD_IF_FALSE', 'POP_JUMP_BACKWARD_IF_FALSE'])
+pseudo_op('POP_JUMP_IF_TRUE', 263, ['POP_JUMP_FORWARD_IF_TRUE', 'POP_JUMP_BACKWARD_IF_TRUE'])
+pseudo_op('POP_JUMP_IF_NONE', 264, ['POP_JUMP_FORWARD_IF_NONE', 'POP_JUMP_BACKWARD_IF_NONE'])
+pseudo_op('POP_JUMP_IF_NOT_NONE', 265, ['POP_JUMP_FORWARD_IF_NOT_NONE', 'POP_JUMP_BACKWARD_IF_NOT_NONE'])
+pseudo_op('LOAD_METHOD', 266, ['LOAD_ATTR'])
+
+MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1
+
+del def_op, name_op, jrel_op, jabs_op, pseudo_op
+
+opname = ['<%r>' % (op,) for op in range(MAX_PSEUDO_OPCODE + 1)]
+for op, i in opmap.items():
+    opname[i] = op
 
-del def_op, name_op, jrel_op, jabs_op
 
 _nb_ops = [
     ("NB_ADD", "+"),
diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py
index 2a4c0d2eeb656..f548e3647b31c 100644
--- a/Lib/test/test__opcode.py
+++ b/Lib/test/test__opcode.py
@@ -18,9 +18,10 @@ def test_stack_effect(self):
         self.assertRaises(ValueError, stack_effect, dis.opmap['BUILD_SLICE'])
         self.assertRaises(ValueError, stack_effect, dis.opmap['POP_TOP'], 0)
         # All defined opcodes
+        has_arg = dis.hasarg
         for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
             with self.subTest(opname=name):
-                if code < dis.HAVE_ARGUMENT:
+                if code not in has_arg:
                     stack_effect(code)
                     self.assertRaises(ValueError, stack_effect, code, 0)
                 else:
@@ -46,10 +47,12 @@ def test_stack_effect_jump(self):
         self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=True), 0)
         self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0)
         # All defined opcodes
+        has_arg = dis.hasarg
+        has_exc = dis.hasexc
         has_jump = dis.hasjabs + dis.hasjrel
         for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
             with self.subTest(opname=name):
-                if code < dis.HAVE_ARGUMENT:
+                if code not in has_arg:
                     common = stack_effect(code)
                     jump = stack_effect(code, jump=True)
                     nojump = stack_effect(code, jump=False)
@@ -57,7 +60,7 @@ def test_stack_effect_jump(self):
                     common = stack_effect(code, 0)
                     jump = stack_effect(code, 0, jump=True)
                     nojump = stack_effect(code, 0, jump=False)
-                if code in has_jump:
+                if code in has_jump or code in has_exc:
                     self.assertEqual(common, max(jump, nojump))
                 else:
                     self.assertEqual(jump, common)
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index 255425302f428..07f1203ff0688 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -897,7 +897,7 @@ def test_widths(self):
                 continue
             with self.subTest(opname=opname):
                 width = dis._OPNAME_WIDTH
-                if opcode < dis.HAVE_ARGUMENT:
+                if opcode in dis.hasarg:
                     width += 1 + dis._OPARG_WIDTH
                 self.assertLessEqual(len(opname), width)
 
diff --git a/Misc/NEWS.d/next/Library/2022-06-24-20-00-57.gh-issue-94216.hxnQPu.rst b/Misc/NEWS.d/next/Library/2022-06-24-20-00-57.gh-issue-94216.hxnQPu.rst
new file mode 100644
index 0000000000000..ae3c2e7e71f35
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-06-24-20-00-57.gh-issue-94216.hxnQPu.rst
@@ -0,0 +1 @@
+The :mod:`dis` module now has the opcodes for pseudo instructions (those which are used by the compiler during code generation but then removed or replaced by real opcodes before the final bytecode is emitted).
diff --git a/Modules/_opcode.c b/Modules/_opcode.c
index 4812716c67271..99be977417743 100644
--- a/Modules/_opcode.c
+++ b/Modules/_opcode.c
@@ -60,12 +60,7 @@ _opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg,
                 "stack_effect: jump must be False, True or None");
         return -1;
     }
-    if (IS_ARTIFICIAL(opcode)) {
-        effect = PY_INVALID_STACK_EFFECT;
-    }
-    else {
-        effect = PyCompile_OpcodeStackEffectWithJump(opcode, oparg_int, jump_int);
-    }
+    effect = PyCompile_OpcodeStackEffectWithJump(opcode, oparg_int, jump_int);
     if (effect == PY_INVALID_STACK_EFFECT) {
             PyErr_SetString(PyExc_ValueError,
                     "invalid opcode or oparg");
diff --git a/Python/compile.c b/Python/compile.c
index 2d3b5717eeab5..5ae1e345201ce 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -71,40 +71,14 @@
 #define MAX_ALLOWED_STACK_USE (STACK_USE_GUIDELINE * 100)
 
 
-/* Pseudo-instructions used in the compiler,
- * but turned into NOPs or other instructions
- * by the assembler. */
-#define SETUP_FINALLY -1
-#define SETUP_CLEANUP -2
-#define SETUP_WITH -3
-#define POP_BLOCK -4
-#define JUMP -5
-#define JUMP_NO_INTERRUPT -6
-#define POP_JUMP_IF_FALSE -7
-#define POP_JUMP_IF_TRUE -8
-#define POP_JUMP_IF_NONE -9
-#define POP_JUMP_IF_NOT_NONE -10
-#define LOAD_METHOD -11
-
-#define MIN_VIRTUAL_OPCODE -11
-#define MAX_ALLOWED_OPCODE 254
+#define MAX_REAL_OPCODE 254
 
 #define IS_WITHIN_OPCODE_RANGE(opcode) \
-        ((opcode) >= MIN_VIRTUAL_OPCODE && (opcode) <= MAX_ALLOWED_OPCODE)
-
-#define IS_VIRTUAL_OPCODE(opcode) ((opcode) < 0)
-
-#define IS_VIRTUAL_JUMP_OPCODE(opcode) \
-        ((opcode) == JUMP || \
-         (opcode) == JUMP_NO_INTERRUPT || \
-         (opcode) == POP_JUMP_IF_NONE || \
-         (opcode) == POP_JUMP_IF_NOT_NONE || \
-         (opcode) == POP_JUMP_IF_FALSE || \
-         (opcode) == POP_JUMP_IF_TRUE)
+        (((opcode) >= 0 && (opcode) <= MAX_REAL_OPCODE) || \
+         IS_PSEUDO_OPCODE(opcode))
 
 #define IS_JUMP_OPCODE(opcode) \
-        (IS_VIRTUAL_JUMP_OPCODE(opcode) || \
-         is_bit_set_in_table(_PyOpcode_Jump, opcode))
+         is_bit_set_in_table(_PyOpcode_Jump, opcode)
 
 #define IS_BLOCK_PUSH_OPCODE(opcode) \
         ((opcode) == SETUP_FINALLY || \
@@ -125,7 +99,6 @@
          (opcode) == POP_JUMP_FORWARD_IF_FALSE || \
          (opcode) == POP_JUMP_BACKWARD_IF_FALSE)
 
-
 #define IS_BACKWARDS_JUMP_OPCODE(opcode) \
         ((opcode) == JUMP_BACKWARD || \
          (opcode) == JUMP_BACKWARD_NO_INTERRUPT || \
@@ -183,11 +156,11 @@ typedef struct exceptstack {
 static inline int
 is_bit_set_in_table(const uint32_t *table, int bitindex) {
     /* Is the relevant bit set in the relevant word? */
-    /* 256 bits fit into 8 32-bits words.
+    /* 512 bits fit into 9 32-bits words.
      * Word is indexed by (bitindex>>ln(size of int in bits)).
      * Bit within word is the low bits of bitindex.
      */
-    if (bitindex >= 0 && bitindex < 256) {
+    if (bitindex >= 0 && bitindex < 512) {
         uint32_t word = table[bitindex >> LOG_BITS_PER_INT];
         return (word >> (bitindex & MASK_LOW_LOG_BITS)) & 1;
     }
@@ -218,7 +191,7 @@ static int
 instr_size(struct instr *instruction)
 {
     int opcode = instruction->i_opcode;
-    assert(!IS_VIRTUAL_OPCODE(opcode));
+    assert(!IS_PSEUDO_OPCODE(opcode));
     int oparg = HAS_ARG(opcode) ? instruction->i_oparg : 0;
     int extended_args = (0xFFFFFF < oparg) + (0xFFFF < oparg) + (0xFF < oparg);
     int caches = _PyOpcode_Caches[opcode];
@@ -229,7 +202,7 @@ static void
 write_instr(_Py_CODEUNIT *codestr, struct instr *instruction, int ilen)
 {
     int opcode = instruction->i_opcode;
-    assert(!IS_VIRTUAL_OPCODE(opcode));
+    assert(!IS_PSEUDO_OPCODE(opcode));
     int oparg = HAS_ARG(opcode) ? instruction->i_oparg : 0;
     int caches = _PyOpcode_Caches[opcode];
     switch (ilen - caches) {
@@ -1274,7 +1247,7 @@ static int
 is_end_of_basic_block(struct instr *instr)
 {
     int opcode = instr->i_opcode;
-    return is_jump(instr) || IS_SCOPE_EXIT_OPCODE(opcode);
+    return IS_JUMP_OPCODE(opcode) || IS_SCOPE_EXIT_OPCODE(opcode);
 }
 
 static int
@@ -1324,7 +1297,7 @@ basicblock_addop(basicblock *b, int opcode, int oparg,
 static int
 compiler_addop(struct compiler *c, int opcode, bool line)
 {
-    assert(!HAS_ARG(opcode) || IS_ARTIFICIAL(opcode));
+    assert(!HAS_ARG(opcode));
     if (compiler_use_new_implicit_block_if_needed(c) < 0) {
         return -1;
     }
@@ -8990,7 +8963,6 @@ apply_static_swaps(basicblock *block, int i)
 static bool
 jump_thread(struct instr *inst, struct instr *target, int opcode)
 {
-    assert(!IS_VIRTUAL_OPCODE(opcode) || IS_VIRTUAL_JUMP_OPCODE(opcode));
     assert(is_jump(inst));
     assert(is_jump(target));
     // bpo-45773: If inst->i_target == target->i_target, then nothing actually
diff --git a/Python/makeopcodetargets.py b/Python/makeopcodetargets.py
index 3bf2e35ccb6da..33a4b4a76a125 100755
--- a/Python/makeopcodetargets.py
+++ b/Python/makeopcodetargets.py
@@ -34,7 +34,8 @@ def write_contents(f):
     targets = ['_unknown_opcode'] * 256
     targets[255] = "TARGET_DO_TRACING"
     for opname, op in opcode.opmap.items():
-        targets[op] = "TARGET_%s" % opname
+        if not opcode.is_pseudo(op):
+            targets[op] = "TARGET_%s" % opname
     next_op = 1
     for opname in opcode._specialized_instructions:
         while targets[next_op] != '_unknown_opcode':
diff --git a/Tools/scripts/generate_opcode_h.py b/Tools/scripts/generate_opcode_h.py
index e1f4f01ae1de0..1d21af8cac281 100644
--- a/Tools/scripts/generate_opcode_h.py
+++ b/Tools/scripts/generate_opcode_h.py
@@ -20,11 +20,8 @@
 """.lstrip()
 
 footer = """
-#define HAS_ARG(op) ((op) >= HAVE_ARGUMENT)
 
-/* Reserve some bytecodes for internal use in the compiler.
- * The value of 240 is arbitrary. */
-#define IS_ARTIFICIAL(op) ((op) > 240)
+#define IS_PSEUDO_OPCODE(op) (((op) >= MIN_PSEUDO_OPCODE) && ((op) <= MAX_PSEUDO_OPCODE))
 
 #ifdef __cplusplus
 }
@@ -63,8 +60,8 @@ def write_int_array_from_ops(name, ops, out):
     bits = 0
     for op in ops:
         bits |= 1<<op
-    out.write(f"static const uint32_t {name}[8] = {{\n")
-    for i in range(8):
+    out.write(f"static const uint32_t {name}[9] = {{\n")
+    for i in range(9):
         out.write(f"    {bits & UINT32_MASK}U,\n")
         bits >>= 32
     assert bits == 0
@@ -81,10 +78,19 @@ def main(opcode_py, outfile='Include/opcode.h', internaloutfile='Include/interna
     exec(code, opcode)
     opmap = opcode['opmap']
     opname = opcode['opname']
+    hasarg = opcode['hasarg']
     hasconst = opcode['hasconst']
     hasjrel = opcode['hasjrel']
     hasjabs = opcode['hasjabs']
-    used = [ False ] * 256
+    is_pseudo = opcode['is_pseudo']
+    _pseudo_ops = opcode['_pseudo_ops']
+
+    HAVE_ARGUMENT = opcode["HAVE_ARGUMENT"]
+    MIN_PSEUDO_OPCODE = opcode["MIN_PSEUDO_OPCODE"]
+    MAX_PSEUDO_OPCODE = opcode["MAX_PSEUDO_OPCODE"]
+
+    NUM_OPCODES = len(opname)
+    used = [ False ] * len(opname)
     next_op = 1
 
     for name, op in opmap.items():
@@ -108,9 +114,17 @@ def main(opcode_py, outfile='Include/opcode.h', internaloutfile='Include/interna
 
         for name in opname:
             if name in opmap:
-                fobj.write(DEFINE.format(name, opmap[name]))
-            if name == 'POP_EXCEPT': # Special entry for HAVE_ARGUMENT
-                fobj.write(DEFINE.format("HAVE_ARGUMENT", opcode["HAVE_ARGUMENT"]))
+                op = opmap[name]
+                if op == HAVE_ARGUMENT:
+                    fobj.write(DEFINE.format("HAVE_ARGUMENT", HAVE_ARGUMENT))
+                if op == MIN_PSEUDO_OPCODE:
+                    fobj.write(DEFINE.format("MIN_PSEUDO_OPCODE", MIN_PSEUDO_OPCODE))
+
+                fobj.write(DEFINE.format(name, op))
+
+                if op == MAX_PSEUDO_OPCODE:
+                    fobj.write(DEFINE.format("MAX_PSEUDO_OPCODE", MAX_PSEUDO_OPCODE))
+
 
         for name, op in specialized_opmap.items():
             fobj.write(DEFINE.format(name, op))
@@ -129,8 +143,9 @@ def main(opcode_py, outfile='Include/opcode.h', internaloutfile='Include/interna
         iobj.write("};\n")
 
         deoptcodes = {}
-        for basic in opmap:
-            deoptcodes[basic] = basic
+        for basic, op in opmap.items():
+            if not is_pseudo(op):
+                deoptcodes[basic] = basic
         for basic, family in opcode["_specializations"].items():
             for specialized in family:
                 deoptcodes[specialized] = basic
@@ -146,10 +161,17 @@ def main(opcode_py, outfile='Include/opcode.h', internaloutfile='Include/interna
         iobj.write("};\n")
         iobj.write("#endif   // NEED_OPCODE_TABLES\n")
 
+        fobj.write("\n")
+        fobj.write("#define HAS_ARG(op) ((((op) >= HAVE_ARGUMENT) && (!IS_PSEUDO_OPCODE(op)))\\")
+        for op in _pseudo_ops:
+            if opmap[op] in hasarg:
+                fobj.write(f"\n    || ((op) == {op}) \\")
+        fobj.write("\n    )\n")
+
         fobj.write("\n")
         fobj.write("#define HAS_CONST(op) (false\\")
         for op in hasconst:
-            fobj.write(f"\n    || ((op) == {op}) \\")
+            fobj.write(f"\n    || ((op) == {opname[op]}) \\")
         fobj.write("\n    )\n")
 
         fobj.write("\n")
@@ -158,7 +180,7 @@ def main(opcode_py, outfile='Include/opcode.h', internaloutfile='Include/interna
 
         iobj.write("\n")
         iobj.write("#ifdef Py_DEBUG\n")
-        iobj.write("static const char *const _PyOpcode_OpName[256] = {\n")
+        iobj.write(f"static const char *const _PyOpcode_OpName[{NUM_OPCODES}] = {{\n")
         for op, name in enumerate(opname_including_specialized):
             if name[0] != "<":
                 op = name



More information about the Python-checkins mailing list