Skip to content

Commit 82a662e

Browse files
authored
bpo-44511: Improve the bytecode for class and mapping patterns (GH-26922)
* Refactor mapping patterns and speed up class patterns. * Simplify MATCH_KEYS and MATCH_CLASS. * Add COPY opcode.
1 parent 19a6c41 commit 82a662e

File tree

10 files changed

+130
-116
lines changed

10 files changed

+130
-116
lines changed

Doc/library/dis.rst

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -755,15 +755,6 @@ iterations of the loop.
755755
.. versionadded:: 3.11
756756

757757

758-
.. opcode:: COPY_DICT_WITHOUT_KEYS
759-
760-
TOS is a tuple of mapping keys, and TOS1 is the match subject. Replace TOS
761-
with a :class:`dict` formed from the items of TOS1, but without any of the
762-
keys in TOS.
763-
764-
.. versionadded:: 3.10
765-
766-
767758
.. opcode:: GET_LEN
768759

769760
Push ``len(TOS)`` onto the stack.
@@ -795,11 +786,14 @@ iterations of the loop.
795786

796787
TOS is a tuple of mapping keys, and TOS1 is the match subject. If TOS1
797788
contains all of the keys in TOS, push a :class:`tuple` containing the
798-
corresponding values, followed by ``True``. Otherwise, push ``None``,
799-
followed by ``False``.
789+
corresponding values. Otherwise, push ``None``.
800790

801791
.. versionadded:: 3.10
802792

793+
.. versionchanged:: 3.11
794+
Previously, this instruction also pushed a boolean value indicating
795+
success (``True``) or failure (``False``).
796+
803797

804798
All of the following opcodes use their arguments.
805799

@@ -1277,12 +1271,16 @@ All of the following opcodes use their arguments.
12771271
against, and TOS2 is the match subject. *count* is the number of positional
12781272
sub-patterns.
12791273

1280-
Pop TOS. If TOS2 is an instance of TOS1 and has the positional and keyword
1281-
attributes required by *count* and TOS, set TOS to ``True`` and TOS1 to a
1282-
tuple of extracted attributes. Otherwise, set TOS to ``False``.
1274+
Pop TOS, TOS1, and TOS2. If TOS2 is an instance of TOS1 and has the
1275+
positional and keyword attributes required by *count* and TOS, push a tuple
1276+
of extracted attributes. Otherwise, push ``None``.
12831277

12841278
.. versionadded:: 3.10
12851279

1280+
.. versionchanged:: 3.11
1281+
Previously, this instruction also pushed a boolean value indicating
1282+
success (``True``) or failure (``False``).
1283+
12861284
.. opcode:: GEN_START (kind)
12871285

12881286
Pops TOS. If TOS was not ``None``, raises an exception. The ``kind``
@@ -1301,6 +1299,14 @@ All of the following opcodes use their arguments.
13011299
.. versionadded:: 3.10
13021300

13031301

1302+
.. opcode:: COPY (i)
1303+
1304+
Push the *i*-th item to the top of the stack. The item is not removed from its
1305+
original location.
1306+
1307+
.. versionadded:: 3.11
1308+
1309+
13041310
.. opcode:: HAVE_ARGUMENT
13051311

13061312
This is not really an opcode. It identifies the dividing line between

Doc/whatsnew/3.11.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,16 @@ CPython bytecode changes
308308
fashion as :opcode:`CALL_METHOD`, but also supports keyword arguments. Works
309309
in tandem with :opcode:`LOAD_METHOD`.
310310

311+
* Removed ``COPY_DICT_WITHOUT_KEYS``.
312+
313+
* :opcode:`MATCH_CLASS` and :opcode:`MATCH_KEYS` no longer push an additional
314+
boolean value indicating whether the match succeeded or failed. Instead, they
315+
indicate failure with :const:`None` (where a tuple of extracted values would
316+
otherwise be).
317+
318+
* Added :opcode:`COPY`, which pushes the *i*-th item to the top of the stack.
319+
The item is not removed from its original location.
320+
311321

312322
Deprecated
313323
==========

Include/opcode.h

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

Lib/importlib/_bootstrap_external.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,8 @@ def _write_atomic(path, data, mode=0o666):
364364
# Python 3.11a1 3459 (PEP 657: add end line numbers and column offsets for instructions)
365365
# Python 3.11a1 3460 (Add co_qualname field to PyCodeObject bpo-44530)
366366
# Python 3.11a1 3461 (JUMP_ABSOLUTE must jump backwards)
367+
# Python 3.11a2 3462 (bpo-44511: remove COPY_DICT_WITHOUT_KEYS, change
368+
# MATCH_CLASS and MATCH_KEYS, and add COPY)
367369

368370
#
369371
# MAGIC must change whenever the bytecode emitted by the compiler may no
@@ -373,7 +375,7 @@ def _write_atomic(path, data, mode=0o666):
373375
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
374376
# in PC/launcher.c must also be updated.
375377

376-
MAGIC_NUMBER = (3461).to_bytes(2, 'little') + b'\r\n'
378+
MAGIC_NUMBER = (3462).to_bytes(2, 'little') + b'\r\n'
377379
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
378380

379381
_PYCACHE = '__pycache__'

Lib/opcode.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def jabs_op(name, op):
8585
def_op('MATCH_MAPPING', 31)
8686
def_op('MATCH_SEQUENCE', 32)
8787
def_op('MATCH_KEYS', 33)
88-
def_op('COPY_DICT_WITHOUT_KEYS', 34)
88+
8989
def_op('PUSH_EXC_INFO', 35)
9090

9191
def_op('POP_EXCEPT_AND_RERAISE', 37)
@@ -165,7 +165,7 @@ def jabs_op(name, op):
165165
def_op('IS_OP', 117)
166166
def_op('CONTAINS_OP', 118)
167167
def_op('RERAISE', 119)
168-
168+
def_op('COPY', 120)
169169
jabs_op('JUMP_IF_NOT_EXC_MATCH', 121)
170170

171171
def_op('LOAD_FAST', 124) # Local variable number
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve the generated bytecode for class and mapping patterns.

Programs/_freeze_importlib

22.1 MB
Binary file not shown.

Python/ceval.c

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4143,25 +4143,30 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
41434143
}
41444144

41454145
TARGET(MATCH_CLASS) {
4146-
// Pop TOS. On success, set TOS to True and TOS1 to a tuple of
4147-
// attributes. On failure, set TOS to False.
4146+
// Pop TOS and TOS1. Set TOS to a tuple of attributes on success, or
4147+
// None on failure.
41484148
PyObject *names = POP();
4149-
PyObject *type = TOP();
4150-
PyObject *subject = SECOND();
4149+
PyObject *type = POP();
4150+
PyObject *subject = TOP();
41514151
assert(PyTuple_CheckExact(names));
41524152
PyObject *attrs = match_class(tstate, subject, type, oparg, names);
41534153
Py_DECREF(names);
4154+
Py_DECREF(type);
41544155
if (attrs) {
41554156
// Success!
41564157
assert(PyTuple_CheckExact(attrs));
4157-
Py_DECREF(subject);
4158-
SET_SECOND(attrs);
4158+
SET_TOP(attrs);
41594159
}
41604160
else if (_PyErr_Occurred(tstate)) {
4161+
// Error!
41614162
goto error;
41624163
}
4163-
Py_DECREF(type);
4164-
SET_TOP(PyBool_FromLong(!!attrs));
4164+
else {
4165+
// Failure!
4166+
Py_INCREF(Py_None);
4167+
SET_TOP(Py_None);
4168+
}
4169+
Py_DECREF(subject);
41654170
DISPATCH();
41664171
}
41674172

@@ -4171,6 +4176,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
41714176
PyObject *res = match ? Py_True : Py_False;
41724177
Py_INCREF(res);
41734178
PUSH(res);
4179+
PREDICT(POP_JUMP_IF_FALSE);
41744180
DISPATCH();
41754181
}
41764182

@@ -4180,53 +4186,19 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
41804186
PyObject *res = match ? Py_True : Py_False;
41814187
Py_INCREF(res);
41824188
PUSH(res);
4189+
PREDICT(POP_JUMP_IF_FALSE);
41834190
DISPATCH();
41844191
}
41854192

41864193
TARGET(MATCH_KEYS) {
4187-
// On successful match for all keys, PUSH(values) and PUSH(True).
4188-
// Otherwise, PUSH(None) and PUSH(False).
4194+
// On successful match, PUSH(values). Otherwise, PUSH(None).
41894195
PyObject *keys = TOP();
41904196
PyObject *subject = SECOND();
41914197
PyObject *values_or_none = match_keys(tstate, subject, keys);
41924198
if (values_or_none == NULL) {
41934199
goto error;
41944200
}
41954201
PUSH(values_or_none);
4196-
if (Py_IsNone(values_or_none)) {
4197-
Py_INCREF(Py_False);
4198-
PUSH(Py_False);
4199-
DISPATCH();
4200-
}
4201-
assert(PyTuple_CheckExact(values_or_none));
4202-
Py_INCREF(Py_True);
4203-
PUSH(Py_True);
4204-
DISPATCH();
4205-
}
4206-
4207-
TARGET(COPY_DICT_WITHOUT_KEYS) {
4208-
// rest = dict(TOS1)
4209-
// for key in TOS:
4210-
// del rest[key]
4211-
// SET_TOP(rest)
4212-
PyObject *keys = TOP();
4213-
PyObject *subject = SECOND();
4214-
PyObject *rest = PyDict_New();
4215-
if (rest == NULL || PyDict_Update(rest, subject)) {
4216-
Py_XDECREF(rest);
4217-
goto error;
4218-
}
4219-
// This may seem a bit inefficient, but keys is rarely big enough to
4220-
// actually impact runtime.
4221-
assert(PyTuple_CheckExact(keys));
4222-
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(keys); i++) {
4223-
if (PyDict_DelItem(rest, PyTuple_GET_ITEM(keys, i))) {
4224-
Py_DECREF(rest);
4225-
goto error;
4226-
}
4227-
}
4228-
Py_DECREF(keys);
4229-
SET_TOP(rest);
42304202
DISPATCH();
42314203
}
42324204

@@ -5027,6 +4999,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
50274999
DISPATCH();
50285000
}
50295001

5002+
TARGET(COPY) {
5003+
assert(oparg != 0);
5004+
PyObject *peek = PEEK(oparg);
5005+
Py_INCREF(peek);
5006+
PUSH(peek);
5007+
DISPATCH();
5008+
}
5009+
50305010
TARGET(EXTENDED_ARG) {
50315011
int oldoparg = oparg;
50325012
NEXTOPARG();

0 commit comments

Comments
 (0)