From 96258c6cb063706a7496fcbf91c6665c73eaf7b9 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 10:40:05 +1000 Subject: [PATCH 01/12] Add a new BUILD_LIST_PREALLOC opcode --- Include/opcode.h | 1 + Lib/opcode.py | 1 + Python/compile.c | 2 ++ Python/opcode_targets.h | 2 +- 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Include/opcode.h b/Include/opcode.h index 2a29e978857035a..0311aae76a0b1e3 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -124,6 +124,7 @@ extern "C" { #define BUILD_CONST_KEY_MAP 156 #define BUILD_STRING 157 #define BUILD_TUPLE_UNPACK_WITH_CALL 158 +#define BUILD_LIST_PREALLOC 159 #define LOAD_METHOD 160 #define CALL_METHOD 161 #define CALL_FINALLY 162 diff --git a/Lib/opcode.py b/Lib/opcode.py index 3fb716b5d96a290..2b0a74fc857bc37 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -207,6 +207,7 @@ def jabs_op(name, op): def_op('BUILD_CONST_KEY_MAP', 156) def_op('BUILD_STRING', 157) def_op('BUILD_TUPLE_UNPACK_WITH_CALL', 158) +def_op('BUILD_LIST_PREALLOC', 159) name_op('LOAD_METHOD', 160) def_op('CALL_METHOD', 161) diff --git a/Python/compile.c b/Python/compile.c index a992e4b4653cc18..23ea63b36562ece 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1006,6 +1006,8 @@ stack_effect(int opcode, int oparg, int jump) case BUILD_SET: case BUILD_STRING: return 1-oparg; + case BUILD_LIST_PREALLOC: + return 0; case BUILD_LIST_UNPACK: case BUILD_TUPLE_UNPACK: case BUILD_TUPLE_UNPACK_WITH_CALL: diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index e82959be086455f..3bbc54794bcc91f 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -158,7 +158,7 @@ static void *opcode_targets[256] = { &&TARGET_BUILD_CONST_KEY_MAP, &&TARGET_BUILD_STRING, &&TARGET_BUILD_TUPLE_UNPACK_WITH_CALL, - &&_unknown_opcode, + &&TARGET_BUILD_LIST_PREALLOC, &&TARGET_LOAD_METHOD, &&TARGET_CALL_METHOD, &&TARGET_CALL_FINALLY, From 64a6fddf93c7539640156de51a4db5c47f530c78 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 10:50:22 +1000 Subject: [PATCH 02/12] Add a _PyList_NewPrealloc function API (private) for creating lists with a preallocated memory size. --- Include/listobject.h | 2 +- Objects/listobject.c | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Include/listobject.h b/Include/listobject.h index 6057279d51c3a48..499bfd479384c10 100644 --- a/Include/listobject.h +++ b/Include/listobject.h @@ -62,7 +62,7 @@ PyAPI_FUNC(int) PyList_Reverse(PyObject *); PyAPI_FUNC(PyObject *) PyList_AsTuple(PyObject *); #ifndef Py_LIMITED_API PyAPI_FUNC(PyObject *) _PyList_Extend(PyListObject *, PyObject *); - +PyAPI_FUNC(PyObject *) _PyList_NewPrealloc(Py_ssize_t size); PyAPI_FUNC(int) PyList_ClearFreeList(void); PyAPI_FUNC(void) _PyList_DebugMallocStats(FILE *out); #endif diff --git a/Objects/listobject.c b/Objects/listobject.c index c29954283c488ea..a7644d0b98a45cc 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -198,6 +198,12 @@ PyList_New(Py_ssize_t size) return (PyObject *) op; } +PyObject * +_PyList_NewPrealloc(Py_ssize_t size) +{ + return list_new_prealloc(size); +} + static PyObject * list_new_prealloc(Py_ssize_t size) { From a7337a793d55c0c157bd367376513410c45f05e6 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 10:54:25 +1000 Subject: [PATCH 03/12] Use _PyList_NewPrealloc from BUILD_LIST_PREALLOC --- Objects/listobject.c | 12 ++++++------ Python/ceval.c | 13 +++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index a7644d0b98a45cc..3d780db1684cad7 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -198,12 +198,6 @@ PyList_New(Py_ssize_t size) return (PyObject *) op; } -PyObject * -_PyList_NewPrealloc(Py_ssize_t size) -{ - return list_new_prealloc(size); -} - static PyObject * list_new_prealloc(Py_ssize_t size) { @@ -221,6 +215,12 @@ list_new_prealloc(Py_ssize_t size) return (PyObject *) op; } +PyObject * +_PyList_NewPrealloc(Py_ssize_t size) +{ + return list_new_prealloc(size); +} + Py_ssize_t PyList_Size(PyObject *op) { diff --git a/Python/ceval.c b/Python/ceval.c index 28e923219d389c7..4c062cf0f6d3294 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2484,6 +2484,19 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) DISPATCH(); } + case TARGET(BUILD_LIST_PREALLOC): { + PyObject *list, *target = GETLOCAL(oparg); + Py_INCREF(target); + Py_ssize_t size = PyObject_LengthHint(target, 2); + Py_DECREF(target); + if (size < 0) + size = 0; + + list = _PyList_NewPrealloc(size); + PUSH(list); + DISPATCH(); + } + case TARGET(BUILD_LIST): { PyObject *list = PyList_New(oparg); if (list == NULL) From 2063a2c79431609d099fa5176f3413e064c40bc3 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 11:04:57 +1000 Subject: [PATCH 04/12] Set stack effect of BUILD_LIST_PREALLOC --- Python/compile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/compile.c b/Python/compile.c index 23ea63b36562ece..70f7b1d975c4884 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1007,7 +1007,7 @@ stack_effect(int opcode, int oparg, int jump) case BUILD_STRING: return 1-oparg; case BUILD_LIST_PREALLOC: - return 0; + return 1; case BUILD_LIST_UNPACK: case BUILD_TUPLE_UNPACK: case BUILD_TUPLE_UNPACK_WITH_CALL: From 96e754cae931afe5e768bdb2d3e831d06bad034d Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 11:05:43 +1000 Subject: [PATCH 05/12] Change list comprehensions to use BUILD_LIST_PREALLOC when there is no 'ifs' within the comprehension. Effectively preallocating the list to the length of the iterator --- Python/compile.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Python/compile.c b/Python/compile.c index 70f7b1d975c4884..3231c90b83e94ca 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -4370,7 +4370,10 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, int op; switch (type) { case COMP_LISTCOMP: - op = BUILD_LIST; + if (asdl_seq_LEN(outermost->ifs) > 0) + op = BUILD_LIST; + else + op = BUILD_LIST_PREALLOC; break; case COMP_SETCOMP: op = BUILD_SET; From 5b3ce9254d8ddd4e584893b1d32c3183eb4f0e3b Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 11:10:33 +1000 Subject: [PATCH 06/12] Document the opcode --- Doc/library/dis.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 060d4bb6997a4d5..5b172f9c72e08f4 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -874,6 +874,13 @@ All of the following opcodes use their arguments. Works as :opcode:`BUILD_TUPLE`, but creates a list. +.. opcode:: BUILD_LIST_PREALLOC (var_num) + + Creates a list with a pre-allocated size and pushes the resulting list onto the stack. + The size is the length-hint of the local ``co_varnames[var_num]``, if the length cannot + be established, it will default a length of 0. + + .. opcode:: BUILD_SET (count) Works as :opcode:`BUILD_TUPLE`, but creates a set. From 836616beeb23bc8c64dc1ac513fec8e10d080890 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 11:13:37 +1000 Subject: [PATCH 07/12] Protect against overflow errors --- Python/ceval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/ceval.c b/Python/ceval.c index 4c062cf0f6d3294..22311a24affe989 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2490,7 +2490,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) Py_ssize_t size = PyObject_LengthHint(target, 2); Py_DECREF(target); if (size < 0) - size = 0; + goto error; list = _PyList_NewPrealloc(size); PUSH(list); From 1710c8439e2b171a6dd8e4edb472a4a7b61ce153 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 13:31:46 +1000 Subject: [PATCH 08/12] Update the test assertion for the dis module to use the new opcode --- Lib/test/test_dis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 8d1912b6ee58818..37dea9727a97dc6 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -384,7 +384,7 @@ def foo(x): dis_nested_2 = """%s Disassembly of at 0x..., file "%s", line %d>: -%3d 0 BUILD_LIST 0 +%3d 0 BUILD_LIST_PREALLOC 0 2 LOAD_FAST 0 (.0) >> 4 FOR_ITER 12 (to 18) 6 STORE_FAST 1 (z) From f80b6a70848419a10f1fe09d95fa35f92b66c2bc Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 8 Apr 2019 13:35:13 +1000 Subject: [PATCH 09/12] Add a new test to validate the OverflowError assertion for very-large iterators --- Lib/test/test_listcomps.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index ddb169fe58957c2..063afcfe31cd377 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -123,6 +123,13 @@ >>> test_func() [2, 2, 2, 2, 2] +Verify that an overflow error is raised for listcomps with very-large iterators + + >>> [y for y in range(2**256)] # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + OverflowError: ... + """ From 5f06333a4e4931e7da305d80d6439977cc2d44b4 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" Date: Mon, 8 Apr 2019 03:42:25 +0000 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core and Builtins/2019-04-08-03-42-24.bpo-36551.5Ocm2j.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-04-08-03-42-24.bpo-36551.5Ocm2j.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-04-08-03-42-24.bpo-36551.5Ocm2j.rst b/Misc/NEWS.d/next/Core and Builtins/2019-04-08-03-42-24.bpo-36551.5Ocm2j.rst new file mode 100644 index 000000000000000..118c1d8f6797633 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-04-08-03-42-24.bpo-36551.5Ocm2j.rst @@ -0,0 +1,2 @@ +Change the compiled bytecode for list comprehensions where there is no if statement. Allocates the size of the list to the length of the iterator in order to avoid having to continuously resize the list. +Also fixes a bug for a list comprehension containing an iterator with a potential size > PY_SSIZE_MAX, e.g. range(2**256). \ No newline at end of file From 0bcfa4d38a1b0e94c9163da7724fee6fc5581d99 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Tue, 9 Apr 2019 09:58:33 +1000 Subject: [PATCH 11/12] Add branch prediction Replace LengthHint for HasLen and Object_Length --- Python/ceval.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Python/ceval.c b/Python/ceval.c index 22311a24affe989..a077d522e93f838 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2487,13 +2487,16 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) case TARGET(BUILD_LIST_PREALLOC): { PyObject *list, *target = GETLOCAL(oparg); Py_INCREF(target); - Py_ssize_t size = PyObject_LengthHint(target, 2); + Py_ssize_t size = 0; + if (_PyObject_HasLen(target)) + size = PyObject_Length(target); Py_DECREF(target); if (size < 0) goto error; list = _PyList_NewPrealloc(size); PUSH(list); + PREDICTED(LOAD_FAST); DISPATCH(); } From 22debe635c72196457bcb8b5507dbdfc935659a5 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Tue, 9 Apr 2019 14:57:06 +1000 Subject: [PATCH 12/12] correct the branch evaluation --- Python/ceval.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index a077d522e93f838..aa6537b699cab7b 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1168,6 +1168,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) } case TARGET(LOAD_FAST): { + PREDICTED(LOAD_FAST); PyObject *value = GETLOCAL(oparg); if (value == NULL) { format_exc_check_arg(PyExc_UnboundLocalError, @@ -2487,16 +2488,14 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) case TARGET(BUILD_LIST_PREALLOC): { PyObject *list, *target = GETLOCAL(oparg); Py_INCREF(target); - Py_ssize_t size = 0; - if (_PyObject_HasLen(target)) - size = PyObject_Length(target); + Py_ssize_t size = PyObject_LengthHint(target, 0); Py_DECREF(target); if (size < 0) goto error; list = _PyList_NewPrealloc(size); PUSH(list); - PREDICTED(LOAD_FAST); + PREDICT(LOAD_FAST); DISPATCH(); }