Skip to content

Commit c57aad7

Browse files
authored
pythongh-94216: add pseudo instructions to the dis/opcodes modules (pythonGH-94241)
1 parent be80db1 commit c57aad7

File tree

13 files changed

+245
-83
lines changed

13 files changed

+245
-83
lines changed

Doc/library/dis.rst

+80-2
Original file line numberDiff line numberDiff line change
@@ -1351,13 +1351,74 @@ iterations of the loop.
13511351
.. opcode:: HAVE_ARGUMENT
13521352

13531353
This is not really an opcode. It identifies the dividing line between
1354-
opcodes which don't use their argument and those that do
1355-
(``< HAVE_ARGUMENT`` and ``>= HAVE_ARGUMENT``, respectively).
1354+
opcodes in the range [0,255] which don't use their argument and those
1355+
that do (``< HAVE_ARGUMENT`` and ``>= HAVE_ARGUMENT``, respectively).
1356+
1357+
If your application uses pseudo instructions, use the :data:`hasarg`
1358+
collection instead.
13561359

13571360
.. versionchanged:: 3.6
13581361
Now every instruction has an argument, but opcodes ``< HAVE_ARGUMENT``
13591362
ignore it. Before, only opcodes ``>= HAVE_ARGUMENT`` had an argument.
13601363

1364+
.. versionchanged:: 3.12
1365+
Pseudo instructions were added to the :mod:`dis` module, and for them
1366+
it is not true that comparison with ``HAVE_ARGUMENT`` indicates whether
1367+
they use their arg.
1368+
1369+
1370+
**Pseudo-instructions**
1371+
1372+
These opcodes do not appear in python bytecode, they are used by the compiler
1373+
but are replaced by real opcodes or removed before bytecode is generated.
1374+
1375+
.. opcode:: SETUP_FINALLY (target)
1376+
1377+
Set up an exception handler for the following code block. If an exception
1378+
occurs, the value stack level is restored to its current state and control
1379+
is transferred to the exception handler at ``target``.
1380+
1381+
1382+
.. opcode:: SETUP_CLEANUP (target)
1383+
1384+
Like ``SETUP_FINALLY``, but in case of exception also pushes the last
1385+
instruction (``lasti``) to the stack so that ``RERAISE`` can restore it.
1386+
If an exception occurs, the value stack level and the last instruction on
1387+
the frame are restored to their current state, and control is transferred
1388+
to the exception handler at ``target``.
1389+
1390+
1391+
.. opcode:: SETUP_WITH (target)
1392+
1393+
Like ``SETUP_CLEANUP``, but in case of exception one more item is popped
1394+
from the stack before control is transferred to the exception handler at
1395+
``target``.
1396+
1397+
This variant is used in :keyword:`with` and :keyword:`async with`
1398+
constructs, which push the return value of the context manager's
1399+
:meth:`~object.__enter__` or :meth:`~object.__aenter__` to the stack.
1400+
1401+
1402+
.. opcode:: POP_BLOCK
1403+
1404+
Marks the end of the code block associated with the last ``SETUP_FINALLY``,
1405+
``SETUP_CLEANUP`` or ``SETUP_WITH``.
1406+
1407+
.. opcode:: JUMP
1408+
.. opcode:: JUMP_NO_INTERRUPT
1409+
.. opcode:: POP_JUMP_IF_FALSE
1410+
.. opcode:: POP_JUMP_IF_TRUE
1411+
.. opcode:: POP_JUMP_IF_NONE
1412+
.. opcode:: POP_JUMP_IF_NOT_NONE
1413+
1414+
Undirected relative jump instructions which are replaced by their
1415+
directed (forward/backward) counterparts by the assembler.
1416+
1417+
.. opcode:: LOAD_METHOD
1418+
1419+
Optimized unbound method lookup. Emitted as a ``LOAD_ATTR`` opcode
1420+
with a flag set in the arg.
1421+
13611422

13621423
.. _opcode_collections:
13631424

@@ -1367,6 +1428,10 @@ Opcode collections
13671428
These collections are provided for automatic introspection of bytecode
13681429
instructions:
13691430

1431+
.. versionchanged:: 3.12
1432+
The collections now contain pseudo instructions as well. These are
1433+
opcodes with values ``>= MIN_PSEUDO_OPCODE``.
1434+
13701435
.. data:: opname
13711436

13721437
Sequence of operation names, indexable using the bytecode.
@@ -1382,6 +1447,13 @@ instructions:
13821447
Sequence of all compare operation names.
13831448

13841449

1450+
.. data:: hasarg
1451+
1452+
Sequence of bytecodes that use their argument.
1453+
1454+
.. versionadded:: 3.12
1455+
1456+
13851457
.. data:: hasconst
13861458

13871459
Sequence of bytecodes that access a constant.
@@ -1418,3 +1490,9 @@ instructions:
14181490
.. data:: hascompare
14191491

14201492
Sequence of bytecodes of Boolean operations.
1493+
1494+
.. data:: hasexc
1495+
1496+
Sequence of bytecodes that set an exception handler.
1497+
1498+
.. versionadded:: 3.12

Doc/whatsnew/3.12.rst

+11
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ New Modules
9393
Improved Modules
9494
================
9595

96+
dis
97+
---
98+
99+
* Pseudo instruction opcodes (which are used by the compiler but
100+
do not appear in executable bytecode) are now exposed in the
101+
:mod:`dis` module.
102+
:data:`~dis.HAVE_ARGUMENT` is still relevant to real opcodes,
103+
but it is not useful for pseudo instrcutions. Use the new
104+
:data:`~dis.hasarg` collection instead.
105+
(Contributed by Irit Katriel in :gh:`94216`.)
106+
96107
os
97108
--
98109

Include/internal/pycore_opcode.h

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

Include/opcode.h

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

Lib/dis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ def _unpack_opargs(code):
610610
op = code[i]
611611
deop = _deoptop(op)
612612
caches = _inline_cache_entries[deop]
613-
if deop >= HAVE_ARGUMENT:
613+
if deop in hasarg:
614614
arg = code[i+1] | extended_arg
615615
extended_arg = (arg << 8) if deop == EXTENDED_ARG else 0
616616
# The oparg is stored as a signed integer

Lib/opcode.py

+54-8
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
operate on bytecodes (e.g. peephole optimizers).
55
"""
66

7-
__all__ = ["cmp_op", "hasconst", "hasname", "hasjrel", "hasjabs",
8-
"haslocal", "hascompare", "hasfree", "opname", "opmap",
9-
"HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"]
7+
__all__ = ["cmp_op", "hasarg", "hasconst", "hasname", "hasjrel", "hasjabs",
8+
"haslocal", "hascompare", "hasfree", "hasexc", "opname", "opmap",
9+
"HAVE_ARGUMENT", "EXTENDED_ARG"]
1010

1111
# It's a chicken-and-egg I'm afraid:
1212
# We're imported before _opcode's made.
@@ -23,20 +23,29 @@
2323

2424
cmp_op = ('<', '<=', '==', '!=', '>', '>=')
2525

26+
hasarg = []
2627
hasconst = []
2728
hasname = []
2829
hasjrel = []
2930
hasjabs = []
3031
haslocal = []
3132
hascompare = []
3233
hasfree = []
33-
hasnargs = [] # unused
34+
hasexc = []
35+
36+
def is_pseudo(op):
37+
return op >= MIN_PSEUDO_OPCODE and op <= MAX_PSEUDO_OPCODE
38+
39+
oplists = [hasarg, hasconst, hasname, hasjrel, hasjabs,
40+
haslocal, hascompare, hasfree, hasexc]
3441

3542
opmap = {}
36-
opname = ['<%r>' % (op,) for op in range(256)]
43+
44+
## pseudo opcodes (used in the compiler) mapped to the values
45+
## they can become in the actual code.
46+
_pseudo_ops = {}
3747

3848
def def_op(name, op):
39-
opname[op] = name
4049
opmap[name] = op
4150

4251
def name_op(name, op):
@@ -51,6 +60,17 @@ def jabs_op(name, op):
5160
def_op(name, op)
5261
hasjabs.append(op)
5362

63+
def pseudo_op(name, op, real_ops):
64+
def_op(name, op)
65+
_pseudo_ops[name] = real_ops
66+
# add the pseudo opcode to the lists its targets are in
67+
for oplist in oplists:
68+
res = [opmap[rop] in oplist for rop in real_ops]
69+
if any(res):
70+
assert all(res)
71+
oplist.append(op)
72+
73+
5474
# Instruction opcodes for compiled code
5575
# Blank lines correspond to available opcodes
5676

@@ -105,7 +125,7 @@ def jabs_op(name, op):
105125
def_op('PREP_RERAISE_STAR', 88)
106126
def_op('POP_EXCEPT', 89)
107127

108-
HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
128+
HAVE_ARGUMENT = 90 # real opcodes from here have an argument:
109129

110130
name_op('STORE_NAME', 90) # Index in name list
111131
name_op('DELETE_NAME', 91) # ""
@@ -200,8 +220,34 @@ def jabs_op(name, op):
200220
jrel_op('POP_JUMP_BACKWARD_IF_FALSE', 175)
201221
jrel_op('POP_JUMP_BACKWARD_IF_TRUE', 176)
202222

223+
hasarg.extend([op for op in opmap.values() if op >= HAVE_ARGUMENT])
224+
225+
MIN_PSEUDO_OPCODE = 256
226+
227+
pseudo_op('SETUP_FINALLY', 256, ['NOP'])
228+
hasexc.append(256)
229+
pseudo_op('SETUP_CLEANUP', 257, ['NOP'])
230+
hasexc.append(257)
231+
pseudo_op('SETUP_WITH', 258, ['NOP'])
232+
hasexc.append(258)
233+
pseudo_op('POP_BLOCK', 259, ['NOP'])
234+
235+
pseudo_op('JUMP', 260, ['JUMP_FORWARD', 'JUMP_BACKWARD'])
236+
pseudo_op('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT'])
237+
pseudo_op('POP_JUMP_IF_FALSE', 262, ['POP_JUMP_FORWARD_IF_FALSE', 'POP_JUMP_BACKWARD_IF_FALSE'])
238+
pseudo_op('POP_JUMP_IF_TRUE', 263, ['POP_JUMP_FORWARD_IF_TRUE', 'POP_JUMP_BACKWARD_IF_TRUE'])
239+
pseudo_op('POP_JUMP_IF_NONE', 264, ['POP_JUMP_FORWARD_IF_NONE', 'POP_JUMP_BACKWARD_IF_NONE'])
240+
pseudo_op('POP_JUMP_IF_NOT_NONE', 265, ['POP_JUMP_FORWARD_IF_NOT_NONE', 'POP_JUMP_BACKWARD_IF_NOT_NONE'])
241+
pseudo_op('LOAD_METHOD', 266, ['LOAD_ATTR'])
242+
243+
MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1
244+
245+
del def_op, name_op, jrel_op, jabs_op, pseudo_op
246+
247+
opname = ['<%r>' % (op,) for op in range(MAX_PSEUDO_OPCODE + 1)]
248+
for op, i in opmap.items():
249+
opname[i] = op
203250

204-
del def_op, name_op, jrel_op, jabs_op
205251

206252
_nb_ops = [
207253
("NB_ADD", "+"),

Lib/test/test__opcode.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ def test_stack_effect(self):
1818
self.assertRaises(ValueError, stack_effect, dis.opmap['BUILD_SLICE'])
1919
self.assertRaises(ValueError, stack_effect, dis.opmap['POP_TOP'], 0)
2020
# All defined opcodes
21+
has_arg = dis.hasarg
2122
for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
2223
with self.subTest(opname=name):
23-
if code < dis.HAVE_ARGUMENT:
24+
if code not in has_arg:
2425
stack_effect(code)
2526
self.assertRaises(ValueError, stack_effect, code, 0)
2627
else:
@@ -46,18 +47,20 @@ def test_stack_effect_jump(self):
4647
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=True), 0)
4748
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0)
4849
# All defined opcodes
50+
has_arg = dis.hasarg
51+
has_exc = dis.hasexc
4952
has_jump = dis.hasjabs + dis.hasjrel
5053
for name, code in filter(lambda item: item[0] not in dis.deoptmap, dis.opmap.items()):
5154
with self.subTest(opname=name):
52-
if code < dis.HAVE_ARGUMENT:
55+
if code not in has_arg:
5356
common = stack_effect(code)
5457
jump = stack_effect(code, jump=True)
5558
nojump = stack_effect(code, jump=False)
5659
else:
5760
common = stack_effect(code, 0)
5861
jump = stack_effect(code, 0, jump=True)
5962
nojump = stack_effect(code, 0, jump=False)
60-
if code in has_jump:
63+
if code in has_jump or code in has_exc:
6164
self.assertEqual(common, max(jump, nojump))
6265
else:
6366
self.assertEqual(jump, common)

0 commit comments

Comments
 (0)