Skip to content

Commit 8a4cd70

Browse files
authored
bpo-39320: Handle unpacking of **values in compiler (GH-18141)
* Add DICT_UPDATE and DICT_MERGE bytecodes. Use them for ** unpacking. * Remove BUILD_MAP_UNPACK and BUILD_MAP_UNPACK_WITH_CALL, as they are now unused. * Update magic number for ** unpacking opcodes. * Update dis.rst to incorporate new bytecodes. * Add blurb entry.
1 parent 72b1004 commit 8a4cd70

File tree

10 files changed

+3670
-3662
lines changed

10 files changed

+3670
-3662
lines changed

Doc/library/dis.rst

+7-14
Original file line numberDiff line numberDiff line change
@@ -873,32 +873,25 @@ All of the following opcodes use their arguments.
873873
.. versionadded:: 3.9
874874

875875

876-
.. opcode:: SET_UPDATE
876+
.. opcode:: SET_UPDATE (i)
877877

878878
Calls ``set.update(TOS1[-i], TOS)``. Used to build sets.
879879

880880
.. versionadded:: 3.9
881881

882882

883-
.. opcode:: BUILD_MAP_UNPACK (count)
883+
.. opcode:: DICT_UPDATE (i)
884884

885-
Pops *count* mappings from the stack, merges them into a single dictionary,
886-
and pushes the result. Implements dictionary unpacking in dictionary
887-
displays ``{**x, **y, **z}``.
885+
Calls ``dict.update(TOS1[-i], TOS)``. Used to build dicts.
888886

889-
.. versionadded:: 3.5
887+
.. versionadded:: 3.9
890888

891889

892-
.. opcode:: BUILD_MAP_UNPACK_WITH_CALL (count)
890+
.. opcode:: DICT_MERGE
893891

894-
This is similar to :opcode:`BUILD_MAP_UNPACK`,
895-
but is used for ``f(**x, **y, **z)`` call syntax. The stack item at
896-
position ``count + 2`` should be the corresponding callable ``f``.
892+
Like :opcode:`DICT_UPDATE` but raises an exception for duplicate keys.
897893

898-
.. versionadded:: 3.5
899-
.. versionchanged:: 3.6
900-
The position of the callable is determined by adding 2 to the opcode
901-
argument instead of encoding it in the second byte of the argument.
894+
.. versionadded:: 3.9
902895

903896

904897
.. opcode:: LOAD_ATTR (namei)

Include/opcode.h

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

Lib/importlib/_bootstrap_external.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ def _write_atomic(path, data, mode=0o666):
276276
# Python 3.9a0 3422 (remove BEGIN_FINALLY, END_FINALLY, CALL_FINALLY, POP_FINALLY bytecodes #33387)
277277
# Python 3.9a2 3423 (add IS_OP, CONTAINS_OP and JUMP_IF_NOT_EXC_MATCH bytecodes #39156)
278278
# Python 3.9a2 3424 (simplify bytecodes for *value unpacking)
279+
# Python 3.9a2 3425 (simplify bytecodes for **value unpacking)
279280

280281
#
281282
# MAGIC must change whenever the bytecode emitted by the compiler may no
@@ -285,7 +286,7 @@ def _write_atomic(path, data, mode=0o666):
285286
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
286287
# in PC/launcher.c must also be updated.
287288

288-
MAGIC_NUMBER = (3424).to_bytes(2, 'little') + b'\r\n'
289+
MAGIC_NUMBER = (3425).to_bytes(2, 'little') + b'\r\n'
289290
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
290291

291292
_PYCACHE = '__pycache__'

Lib/opcode.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,6 @@ def jabs_op(name, op):
200200
def_op('EXTENDED_ARG', 144)
201201
EXTENDED_ARG = 144
202202

203-
def_op('BUILD_MAP_UNPACK', 150)
204-
def_op('BUILD_MAP_UNPACK_WITH_CALL', 151)
205-
206203
jrel_op('SETUP_ASYNC_WITH', 154)
207204

208205
def_op('FORMAT_VALUE', 155)
@@ -214,5 +211,7 @@ def jabs_op(name, op):
214211

215212
def_op('LIST_EXTEND', 162)
216213
def_op('SET_UPDATE', 163)
214+
def_op('DICT_MERGE', 164)
215+
def_op('DICT_UPDATE', 165)
217216

218217
del def_op, name_op, jrel_op, jabs_op
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
Replace two complex bytecodes for building dicts with two simpler ones.
3+
The new bytecodes ``DICT_MERGE`` and ``DICT_UPDATE`` have been added
4+
The old bytecodes ``BUILD_MAP_UNPACK`` and ``BUILD_MAP_UNPACK_WITH_CALL`` have been removed.

Python/ceval.c

+19-36
Original file line numberDiff line numberDiff line change
@@ -2801,49 +2801,32 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
28012801
DISPATCH();
28022802
}
28032803

2804-
case TARGET(BUILD_MAP_UNPACK): {
2805-
Py_ssize_t i;
2806-
PyObject *sum = PyDict_New();
2807-
if (sum == NULL)
2808-
goto error;
2809-
2810-
for (i = oparg; i > 0; i--) {
2811-
PyObject *arg = PEEK(i);
2812-
if (PyDict_Update(sum, arg) < 0) {
2813-
if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) {
2814-
_PyErr_Format(tstate, PyExc_TypeError,
2815-
"'%.200s' object is not a mapping",
2816-
arg->ob_type->tp_name);
2817-
}
2818-
Py_DECREF(sum);
2819-
goto error;
2804+
case TARGET(DICT_UPDATE): {
2805+
PyObject *update = POP();
2806+
PyObject *dict = PEEK(oparg);
2807+
if (PyDict_Update(dict, update) < 0) {
2808+
if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) {
2809+
_PyErr_Format(tstate, PyExc_TypeError,
2810+
"'%.200s' object is not a mapping",
2811+
update->ob_type->tp_name);
28202812
}
2813+
Py_DECREF(update);
2814+
goto error;
28212815
}
2822-
2823-
while (oparg--)
2824-
Py_DECREF(POP());
2825-
PUSH(sum);
2816+
Py_DECREF(update);
28262817
DISPATCH();
28272818
}
28282819

2829-
case TARGET(BUILD_MAP_UNPACK_WITH_CALL): {
2830-
Py_ssize_t i;
2831-
PyObject *sum = PyDict_New();
2832-
if (sum == NULL)
2833-
goto error;
2820+
case TARGET(DICT_MERGE): {
2821+
PyObject *update = POP();
2822+
PyObject *dict = PEEK(oparg);
28342823

2835-
for (i = oparg; i > 0; i--) {
2836-
PyObject *arg = PEEK(i);
2837-
if (_PyDict_MergeEx(sum, arg, 2) < 0) {
2838-
Py_DECREF(sum);
2839-
format_kwargs_error(tstate, PEEK(2 + oparg), arg);
2840-
goto error;
2841-
}
2824+
if (_PyDict_MergeEx(dict, update, 2) < 0) {
2825+
format_kwargs_error(tstate, PEEK(2 + oparg), update);
2826+
Py_DECREF(update);
2827+
goto error;
28422828
}
2843-
2844-
while (oparg--)
2845-
Py_DECREF(POP());
2846-
PUSH(sum);
2829+
Py_DECREF(update);
28472830
PREDICT(CALL_FUNCTION_EX);
28482831
DISPATCH();
28492832
}

Python/compile.c

+58-32
Original file line numberDiff line numberDiff line change
@@ -1007,9 +1007,6 @@ stack_effect(int opcode, int oparg, int jump)
10071007
case BUILD_SET:
10081008
case BUILD_STRING:
10091009
return 1-oparg;
1010-
case BUILD_MAP_UNPACK:
1011-
case BUILD_MAP_UNPACK_WITH_CALL:
1012-
return 1 - oparg;
10131010
case BUILD_MAP:
10141011
return 1 - 2*oparg;
10151012
case BUILD_CONST_KEY_MAP:
@@ -1125,6 +1122,8 @@ stack_effect(int opcode, int oparg, int jump)
11251122
return 0;
11261123
case LIST_EXTEND:
11271124
case SET_UPDATE:
1125+
case DICT_MERGE:
1126+
case DICT_UPDATE:
11281127
return -1;
11291128
default:
11301129
return PY_INVALID_STACK_EFFECT;
@@ -3868,37 +3867,58 @@ static int
38683867
compiler_dict(struct compiler *c, expr_ty e)
38693868
{
38703869
Py_ssize_t i, n, elements;
3871-
int containers;
3870+
int have_dict;
38723871
int is_unpacking = 0;
38733872
n = asdl_seq_LEN(e->v.Dict.values);
3874-
containers = 0;
3873+
have_dict = 0;
38753874
elements = 0;
38763875
for (i = 0; i < n; i++) {
38773876
is_unpacking = (expr_ty)asdl_seq_GET(e->v.Dict.keys, i) == NULL;
3878-
if (elements == 0xFFFF || (elements && is_unpacking)) {
3879-
if (!compiler_subdict(c, e, i - elements, i))
3880-
return 0;
3881-
containers++;
3882-
elements = 0;
3883-
}
38843877
if (is_unpacking) {
3878+
if (elements) {
3879+
if (!compiler_subdict(c, e, i - elements, i)) {
3880+
return 0;
3881+
}
3882+
if (have_dict) {
3883+
ADDOP_I(c, DICT_UPDATE, 1);
3884+
}
3885+
have_dict = 1;
3886+
elements = 0;
3887+
}
3888+
if (have_dict == 0) {
3889+
ADDOP_I(c, BUILD_MAP, 0);
3890+
have_dict = 1;
3891+
}
38853892
VISIT(c, expr, (expr_ty)asdl_seq_GET(e->v.Dict.values, i));
3886-
containers++;
3893+
ADDOP_I(c, DICT_UPDATE, 1);
38873894
}
38883895
else {
3889-
elements++;
3896+
if (elements == 0xFFFF) {
3897+
if (!compiler_subdict(c, e, i - elements, i)) {
3898+
return 0;
3899+
}
3900+
if (have_dict) {
3901+
ADDOP_I(c, DICT_UPDATE, 1);
3902+
}
3903+
have_dict = 1;
3904+
elements = 0;
3905+
}
3906+
else {
3907+
elements++;
3908+
}
38903909
}
38913910
}
3892-
if (elements || containers == 0) {
3893-
if (!compiler_subdict(c, e, n - elements, n))
3911+
if (elements) {
3912+
if (!compiler_subdict(c, e, n - elements, n)) {
38943913
return 0;
3895-
containers++;
3914+
}
3915+
if (have_dict) {
3916+
ADDOP_I(c, DICT_UPDATE, 1);
3917+
}
3918+
have_dict = 1;
38963919
}
3897-
/* If there is more than one dict, they need to be merged into a new
3898-
* dict. If there is one dict and it's an unpacking, then it needs
3899-
* to be copied into a new dict." */
3900-
if (containers > 1 || is_unpacking) {
3901-
ADDOP_I(c, BUILD_MAP_UNPACK, containers);
3920+
if (!have_dict) {
3921+
ADDOP_I(c, BUILD_MAP, 0);
39023922
}
39033923
return 1;
39043924
}
@@ -4263,37 +4283,43 @@ compiler_call_helper(struct compiler *c,
42634283
}
42644284
/* Then keyword arguments */
42654285
if (nkwelts) {
4266-
/* the number of dictionaries on the stack */
4267-
Py_ssize_t nsubkwargs = 0;
4286+
/* Has a new dict been pushed */
4287+
int have_dict = 0;
42684288

42694289
nseen = 0; /* the number of keyword arguments on the stack following */
42704290
for (i = 0; i < nkwelts; i++) {
42714291
keyword_ty kw = asdl_seq_GET(keywords, i);
42724292
if (kw->arg == NULL) {
42734293
/* A keyword argument unpacking. */
42744294
if (nseen) {
4275-
if (!compiler_subkwargs(c, keywords, i - nseen, i))
4295+
if (!compiler_subkwargs(c, keywords, i - nseen, i)) {
42764296
return 0;
4277-
nsubkwargs++;
4297+
}
4298+
have_dict = 1;
42784299
nseen = 0;
42794300
}
4301+
if (!have_dict) {
4302+
ADDOP_I(c, BUILD_MAP, 0);
4303+
have_dict = 1;
4304+
}
42804305
VISIT(c, expr, kw->value);
4281-
nsubkwargs++;
4306+
ADDOP_I(c, DICT_MERGE, 1);
42824307
}
42834308
else {
42844309
nseen++;
42854310
}
42864311
}
42874312
if (nseen) {
42884313
/* Pack up any trailing keyword arguments. */
4289-
if (!compiler_subkwargs(c, keywords, nkwelts - nseen, nkwelts))
4314+
if (!compiler_subkwargs(c, keywords, nkwelts - nseen, nkwelts)) {
42904315
return 0;
4291-
nsubkwargs++;
4292-
}
4293-
if (nsubkwargs > 1) {
4294-
/* Pack it all up */
4295-
ADDOP_I(c, BUILD_MAP_UNPACK_WITH_CALL, nsubkwargs);
4316+
}
4317+
if (have_dict) {
4318+
ADDOP_I(c, DICT_MERGE, 1);
4319+
}
4320+
have_dict = 1;
42964321
}
4322+
assert(have_dict);
42974323
}
42984324
ADDOP_I(c, CALL_FUNCTION_EX, nkwelts > 0);
42994325
return 1;

0 commit comments

Comments
 (0)