From 01440d3a3993b26f4a5f53c44be42cdbb0925289 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 4 Mar 2024 12:32:39 -0700 Subject: [PATCH 01/26] gh-76785: Simplify Channels XID Types (gh-116318) I had added an extra cleanup abstraction a while back that has turned out to be unnecessary. --- Modules/_xxinterpchannelsmodule.c | 127 ++++++++++++------------------ 1 file changed, 49 insertions(+), 78 deletions(-) diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 82d2ae7fc4c963..c0d4fd08088434 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -93,38 +93,6 @@ API.. The module does not create any objects that are shared globally. PyMem_RawFree(VAR) -struct xid_class_registry { - size_t count; -#define MAX_XID_CLASSES 5 - struct { - PyTypeObject *cls; - } added[MAX_XID_CLASSES]; -}; - -static int -register_xid_class(PyTypeObject *cls, crossinterpdatafunc shared, - struct xid_class_registry *classes) -{ - int res = ensure_xid_class(cls, shared); - if (res == 0) { - assert(classes->count < MAX_XID_CLASSES); - // The class has refs elsewhere, so we need to incref here. - classes->added[classes->count].cls = cls; - classes->count += 1; - } - return res; -} - -static void -clear_xid_class_registry(struct xid_class_registry *classes) -{ - while (classes->count > 0) { - classes->count -= 1; - PyTypeObject *cls = classes->added[classes->count].cls; - _PyCrossInterpreterData_UnregisterClass(cls); - } -} - #define XID_IGNORE_EXC 1 #define XID_FREE 2 @@ -223,28 +191,6 @@ add_new_exception(PyObject *mod, const char *name, PyObject *base) #define ADD_NEW_EXCEPTION(MOD, NAME, BASE) \ add_new_exception(MOD, MODULE_NAME_STR "." Py_STRINGIFY(NAME), BASE) -static PyTypeObject * -add_new_type(PyObject *mod, PyType_Spec *spec, crossinterpdatafunc shared, - struct xid_class_registry *classes) -{ - PyTypeObject *cls = (PyTypeObject *)PyType_FromModuleAndSpec( - mod, spec, NULL); - if (cls == NULL) { - return NULL; - } - if (PyModule_AddType(mod, cls) < 0) { - Py_DECREF(cls); - return NULL; - } - if (shared != NULL) { - if (register_xid_class(cls, shared, classes)) { - Py_DECREF(cls); - return NULL; - } - } - return cls; -} - static int wait_for_lock(PyThread_type_lock mutex, PY_TIMEOUT_T timeout) { @@ -269,8 +215,6 @@ wait_for_lock(PyThread_type_lock mutex, PY_TIMEOUT_T timeout) /* module state *************************************************************/ typedef struct { - struct xid_class_registry xid_classes; - /* Added at runtime by interpreters module. */ PyTypeObject *send_channel_type; PyTypeObject *recv_channel_type; @@ -332,19 +276,33 @@ traverse_module_state(module_state *state, visitproc visit, void *arg) return 0; } -static int -clear_module_state(module_state *state) +static void +clear_xid_types(module_state *state) { /* external types */ - Py_CLEAR(state->send_channel_type); - Py_CLEAR(state->recv_channel_type); + if (state->send_channel_type != NULL) { + (void)_PyCrossInterpreterData_UnregisterClass(state->send_channel_type); + Py_CLEAR(state->send_channel_type); + } + if (state->recv_channel_type != NULL) { + (void)_PyCrossInterpreterData_UnregisterClass(state->recv_channel_type); + Py_CLEAR(state->recv_channel_type); + } /* heap types */ - Py_CLEAR(state->ChannelInfoType); if (state->ChannelIDType != NULL) { (void)_PyCrossInterpreterData_UnregisterClass(state->ChannelIDType); + Py_CLEAR(state->ChannelIDType); } - Py_CLEAR(state->ChannelIDType); +} + +static int +clear_module_state(module_state *state) +{ + clear_xid_types(state); + + /* heap types */ + Py_CLEAR(state->ChannelInfoType); /* exceptions */ Py_CLEAR(state->ChannelError); @@ -2614,6 +2572,25 @@ static PyType_Spec channelid_typespec = { .slots = channelid_typeslots, }; +static PyTypeObject * +add_channelid_type(PyObject *mod) +{ + PyTypeObject *cls = (PyTypeObject *)PyType_FromModuleAndSpec( + mod, &channelid_typespec, NULL); + if (cls == NULL) { + return NULL; + } + if (PyModule_AddType(mod, cls) < 0) { + Py_DECREF(cls); + return NULL; + } + if (ensure_xid_class(cls, _channelid_shared) < 0) { + Py_DECREF(cls); + return NULL; + } + return cls; +} + /* SendChannel and RecvChannel classes */ @@ -2697,7 +2674,6 @@ set_channelend_types(PyObject *mod, PyTypeObject *send, PyTypeObject *recv) if (state == NULL) { return -1; } - struct xid_class_registry *xid_classes = &state->xid_classes; if (state->send_channel_type != NULL || state->recv_channel_type != NULL) @@ -2707,11 +2683,15 @@ set_channelend_types(PyObject *mod, PyTypeObject *send, PyTypeObject *recv) } state->send_channel_type = (PyTypeObject *)Py_NewRef(send); state->recv_channel_type = (PyTypeObject *)Py_NewRef(recv); - - if (register_xid_class(send, _channelend_shared, xid_classes)) { + if (ensure_xid_class(send, _channelend_shared) < 0) { + Py_CLEAR(state->send_channel_type); + Py_CLEAR(state->recv_channel_type); return -1; } - if (register_xid_class(recv, _channelend_shared, xid_classes)) { + if (ensure_xid_class(recv, _channelend_shared) < 0) { + (void)_PyCrossInterpreterData_UnregisterClass(state->send_channel_type); + Py_CLEAR(state->send_channel_type); + Py_CLEAR(state->recv_channel_type); return -1; } @@ -3294,13 +3274,11 @@ module_exec(PyObject *mod) if (_globals_init() != 0) { return -1; } - struct xid_class_registry *xid_classes = NULL; module_state *state = get_module_state(mod); if (state == NULL) { goto error; } - xid_classes = &state->xid_classes; /* Add exception types */ if (exceptions_init(mod) != 0) { @@ -3319,8 +3297,7 @@ module_exec(PyObject *mod) } // ChannelID - state->ChannelIDType = add_new_type( - mod, &channelid_typespec, _channelid_shared, xid_classes); + state->ChannelIDType = add_channelid_type(mod); if (state->ChannelIDType == NULL) { goto error; } @@ -3332,8 +3309,8 @@ module_exec(PyObject *mod) return 0; error: - if (xid_classes != NULL) { - clear_xid_class_registry(xid_classes); + if (state != NULL) { + clear_xid_types(state); } _globals_fini(); return -1; @@ -3360,9 +3337,6 @@ module_clear(PyObject *mod) module_state *state = get_module_state(mod); assert(state != NULL); - // Before clearing anything, we unregister the various XID types. */ - clear_xid_class_registry(&state->xid_classes); - // Now we clear the module state. clear_module_state(state); return 0; @@ -3374,9 +3348,6 @@ module_free(void *mod) module_state *state = get_module_state(mod); assert(state != NULL); - // Before clearing anything, we unregister the various XID types. */ - clear_xid_class_registry(&state->xid_classes); - // Now we clear the module state. clear_module_state(state); From 207030f5527d405940b79c10c1413c1e8ff696c1 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 4 Mar 2024 23:03:59 +0300 Subject: [PATCH 02/26] gh-115320: Refactor `get_hash_info` in `sysmodule.c` not to swallow errors (#115321) --- Python/sysmodule.c | 48 ++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 1bfd031fdd26b2..a6e171601447c0 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1498,31 +1498,33 @@ get_hash_info(PyThreadState *tstate) int field = 0; PyHash_FuncDef *hashfunc; hash_info = PyStructSequence_New(&Hash_InfoType); - if (hash_info == NULL) - return NULL; - hashfunc = PyHash_GetFuncDef(); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(8*sizeof(Py_hash_t))); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromSsize_t(_PyHASH_MODULUS)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(_PyHASH_INF)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(0)); // This is no longer used - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(_PyHASH_IMAG)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyUnicode_FromString(hashfunc->name)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(hashfunc->hash_bits)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(hashfunc->seed_bits)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(Py_HASH_CUTOFF)); - if (_PyErr_Occurred(tstate)) { - Py_CLEAR(hash_info); + if (hash_info == NULL) { return NULL; } + hashfunc = PyHash_GetFuncDef(); + +#define SET_HASH_INFO_ITEM(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + Py_CLEAR(hash_info); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM(hash_info, field++, item); \ + } while(0) + + SET_HASH_INFO_ITEM(PyLong_FromLong(8 * sizeof(Py_hash_t))); + SET_HASH_INFO_ITEM(PyLong_FromSsize_t(_PyHASH_MODULUS)); + SET_HASH_INFO_ITEM(PyLong_FromLong(_PyHASH_INF)); + SET_HASH_INFO_ITEM(PyLong_FromLong(0)); // This is no longer used + SET_HASH_INFO_ITEM(PyLong_FromLong(_PyHASH_IMAG)); + SET_HASH_INFO_ITEM(PyUnicode_FromString(hashfunc->name)); + SET_HASH_INFO_ITEM(PyLong_FromLong(hashfunc->hash_bits)); + SET_HASH_INFO_ITEM(PyLong_FromLong(hashfunc->seed_bits)); + SET_HASH_INFO_ITEM(PyLong_FromLong(Py_HASH_CUTOFF)); + +#undef SET_HASH_INFO_ITEM + return hash_info; } /*[clinic input] From eb22e2b251002b65f3b93e67c990c21e1151b25d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 4 Mar 2024 13:59:30 -0700 Subject: [PATCH 03/26] gh-115490: Make the interpreter.channels and interpreter.queues Modules Handle Reloading Properly (gh-115493) The problem manifested when the .py module got reloaded and the corresponding extension module didn't. The .py module registers types with the extension and the extension was not allowing that to happen more than once. The solution: let it happen more than once. --- Lib/test/libregrtest/main.py | 9 ++------- Lib/test/test__xxinterpchannels.py | 5 +++++ Lib/test/test_interpreters/test_channels.py | 19 +++++++++++++++++++ Lib/test/test_interpreters/test_queues.py | 15 +++++++++++++++ Modules/_xxinterpchannelsmodule.c | 15 ++++++++++----- Modules/_xxinterpqueuesmodule.c | 16 +++++++++------- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index 6fbe3d00981ddd..126daca388fd7f 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -384,15 +384,10 @@ def run_tests_sequentially(self, runtests) -> None: result = self.run_test(test_name, runtests, tracer) - # Unload the newly imported test modules (best effort - # finalization). To work around gh-115490, don't unload - # test.support.interpreters and its submodules even if they - # weren't loaded before. - keep = "test.support.interpreters" + # Unload the newly imported test modules (best effort finalization) new_modules = [module for module in sys.modules if module not in save_modules and - module.startswith(("test.", "test_")) - and not module.startswith(keep)] + module.startswith(("test.", "test_"))] for module in new_modules: sys.modules.pop(module, None) # Remove the attribute of the parent module. diff --git a/Lib/test/test__xxinterpchannels.py b/Lib/test/test__xxinterpchannels.py index cc2ed7849b0c0f..c5d29bd2dd911f 100644 --- a/Lib/test/test__xxinterpchannels.py +++ b/Lib/test/test__xxinterpchannels.py @@ -18,6 +18,11 @@ channels = import_helper.import_module('_xxinterpchannels') +# Additional tests are found in Lib/test/test_interpreters/test_channels.py. +# New tests should be added there. +# XXX The tests here should be moved there. See the note under LowLevelTests. + + ################################## # helpers diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py index 07e503837bcf75..57204e2776468d 100644 --- a/Lib/test/test_interpreters/test_channels.py +++ b/Lib/test/test_interpreters/test_channels.py @@ -1,3 +1,4 @@ +import importlib import threading from textwrap import dedent import unittest @@ -11,6 +12,24 @@ from .utils import _run_output, TestBase +class LowLevelTests(TestBase): + + # The behaviors in the low-level module is important in as much + # as it is exercised by the high-level module. Therefore the + # most # important testing happens in the high-level tests. + # These low-level tests cover corner cases that are not + # encountered by the high-level module, thus they + # mostly shouldn't matter as much. + + # Additional tests are found in Lib/test/test__xxinterpchannels.py. + # XXX Those should be either moved to LowLevelTests or eliminated + # in favor of high-level tests in this file. + + def test_highlevel_reloaded(self): + # See gh-115490 (https://github.com/python/cpython/issues/115490). + importlib.reload(channels) + + class TestChannels(TestBase): def test_create(self): diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index 905ef035d43feb..0a1fdb41f73166 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -1,3 +1,4 @@ +import importlib import threading from textwrap import dedent import unittest @@ -20,6 +21,20 @@ def tearDown(self): pass +class LowLevelTests(TestBase): + + # The behaviors in the low-level module is important in as much + # as it is exercised by the high-level module. Therefore the + # most # important testing happens in the high-level tests. + # These low-level tests cover corner cases that are not + # encountered by the high-level module, thus they + # mostly shouldn't matter as much. + + def test_highlevel_reloaded(self): + # See gh-115490 (https://github.com/python/cpython/issues/115490). + importlib.reload(queues) + + class QueueTests(TestBase): def test_create(self): diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index c0d4fd08088434..0ad184a78e8c1a 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -2675,12 +2675,17 @@ set_channelend_types(PyObject *mod, PyTypeObject *send, PyTypeObject *recv) return -1; } - if (state->send_channel_type != NULL - || state->recv_channel_type != NULL) - { - PyErr_SetString(PyExc_TypeError, "already registered"); - return -1; + // Clear the old values if the .py module was reloaded. + if (state->send_channel_type != NULL) { + (void)_PyCrossInterpreterData_UnregisterClass(state->send_channel_type); + Py_CLEAR(state->send_channel_type); } + if (state->recv_channel_type != NULL) { + (void)_PyCrossInterpreterData_UnregisterClass(state->recv_channel_type); + Py_CLEAR(state->recv_channel_type); + } + + // Add and register the types. state->send_channel_type = (PyTypeObject *)Py_NewRef(send); state->recv_channel_type = (PyTypeObject *)Py_NewRef(recv); if (ensure_xid_class(send, _channelend_shared) < 0) { diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index e35d1699cfea89..1b76b6963ae0f1 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -169,6 +169,9 @@ static int clear_module_state(module_state *state) { /* external types */ + if (state->queue_type != NULL) { + (void)_PyCrossInterpreterData_UnregisterClass(state->queue_type); + } Py_CLEAR(state->queue_type); /* QueueError */ @@ -1078,15 +1081,18 @@ set_external_queue_type(PyObject *module, PyTypeObject *queue_type) { module_state *state = get_module_state(module); + // Clear the old value if the .py module was reloaded. if (state->queue_type != NULL) { - PyErr_SetString(PyExc_TypeError, "already registered"); - return -1; + (void)_PyCrossInterpreterData_UnregisterClass( + state->queue_type); + Py_CLEAR(state->queue_type); } - state->queue_type = (PyTypeObject *)Py_NewRef(queue_type); + // Add and register the new type. if (ensure_xid_class(queue_type, _queueobj_shared) < 0) { return -1; } + state->queue_type = (PyTypeObject *)Py_NewRef(queue_type); return 0; } @@ -1703,10 +1709,6 @@ module_clear(PyObject *mod) { module_state *state = get_module_state(mod); - if (state->queue_type != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->queue_type); - } - // Now we clear the module state. clear_module_state(state); return 0; From 88b5c665ee1624af1bc5097d3eb2af090b9cabed Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Mon, 4 Mar 2024 16:49:42 -0500 Subject: [PATCH 04/26] gh-116265: Remove obsolete sentence. (#116284) Remove sentence in Tools/c-analyzer/README referring to deleted ignore-globals.txt. --- Tools/c-analyzer/README | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tools/c-analyzer/README b/Tools/c-analyzer/README index 86bf1e77e0bfea..41ea13280347a7 100644 --- a/Tools/c-analyzer/README +++ b/Tools/c-analyzer/README @@ -11,9 +11,8 @@ falls into one of several categories: * module state * Python runtime state -The ignored-globals.txt file is organized similarly. Of the different -categories, the last two are problematic and generally should not exist -in the codebase. +Of the different categories, the last two are problematic and +generally should not exist in the codebase. Globals that hold module state (i.e. in Modules/*.c) cause problems when multiple interpreters are in use. For more info, see PEP 3121, @@ -42,4 +41,3 @@ You can also use the more generic tool: If it reports any globals then they should be resolved. If the globals are runtime state then they should be folded into _PyRuntimeState. -Otherwise they should be added to ignored-globals.txt. From 58c7919d05a360f3437a204960cddbeb78a71dce Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 4 Mar 2024 22:06:21 -0500 Subject: [PATCH 05/26] gh-116029: Fix unused function warning on macOS (#116340) --- Objects/dictobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 39778df603f43f..027cff1bfb451a 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1238,10 +1238,10 @@ _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **valu return ix; } +#ifdef Py_GIL_DISABLED static inline void ensure_shared_on_read(PyDictObject *mp) { -#ifdef Py_GIL_DISABLED if (!_Py_IsOwnedByCurrentThread((PyObject *)mp) && !IS_DICT_SHARED(mp)) { // The first time we access a dict from a non-owning thread we mark it // as shared. This ensures that a concurrent resize operation will @@ -1253,8 +1253,8 @@ ensure_shared_on_read(PyDictObject *mp) } Py_END_CRITICAL_SECTION(); } -#endif } +#endif static inline void ensure_shared_on_resize(PyDictObject *mp) From 6cddc731fb59edb66b64b7a8dbd9e281309a8384 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 5 Mar 2024 13:58:14 +0900 Subject: [PATCH 06/26] gh-112087: Make list_{slice, ass_slice, subscript} to be threadsafe (gh-116233) --- Lib/test/test_list.py | 5 ++ Objects/listobject.c | 132 ++++++++++++++++++++++++++---------------- 2 files changed, 87 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 2969c6e2f98a23..0601b33e79ebb6 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -96,6 +96,11 @@ def imul(a, b): a *= b self.assertRaises((MemoryError, OverflowError), mul, lst, n) self.assertRaises((MemoryError, OverflowError), imul, lst, n) + def test_empty_slice(self): + x = [] + x[:] = x + self.assertEqual(x, []) + def test_list_resize_overflow(self): # gh-97616: test new_allocated * sizeof(PyObject*) overflow # check in list_resize() diff --git a/Objects/listobject.c b/Objects/listobject.c index 87effb1b3a65fa..79d61878c381bc 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -515,7 +515,7 @@ list_item(PyObject *aa, Py_ssize_t i) } static PyObject * -list_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) +list_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) { PyListObject *np; PyObject **src, **dest; @@ -559,7 +559,7 @@ PyList_GetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) else if (ihigh > Py_SIZE(a)) { ihigh = Py_SIZE(a); } - ret = list_slice((PyListObject *)a, ilow, ihigh); + ret = list_slice_lock_held((PyListObject *)a, ilow, ihigh); Py_END_CRITICAL_SECTION(); return ret; } @@ -584,13 +584,13 @@ list_concat_lock_held(PyListObject *a, PyListObject *b) dest = np->ob_item; for (i = 0; i < Py_SIZE(a); i++) { PyObject *v = src[i]; - FT_ATOMIC_STORE_PTR_RELAXED(dest[i], Py_NewRef(v)); + dest[i] = Py_NewRef(v); } src = b->ob_item; dest = np->ob_item + Py_SIZE(a); for (i = 0; i < Py_SIZE(b); i++) { PyObject *v = src[i]; - FT_ATOMIC_STORE_PTR_RELAXED(dest[i], Py_NewRef(v)); + dest[i] = Py_NewRef(v); } Py_SET_SIZE(np, size); return (PyObject *)np; @@ -636,7 +636,7 @@ list_repeat_lock_held(PyListObject *a, Py_ssize_t n) _Py_RefcntAdd(elem, n); PyObject **dest_end = dest + output_size; while (dest < dest_end) { - FT_ATOMIC_STORE_PTR_RELAXED(*dest++, elem); + *dest++ = elem; } } else { @@ -644,7 +644,7 @@ list_repeat_lock_held(PyListObject *a, Py_ssize_t n) PyObject **src_end = src + input_size; while (src < src_end) { _Py_RefcntAdd(*src, n); - FT_ATOMIC_STORE_PTR_RELAXED(*dest++, *src++); + *dest++ = *src++; } _Py_memory_repeat((char *)np->ob_item, sizeof(PyObject *)*output_size, @@ -718,7 +718,7 @@ list_clear_slot(PyObject *self) * guaranteed the call cannot fail. */ static int -list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) +list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) { /* Because [X]DECREF can recursively invoke list operations on this list, we must postpone all [X]DECREF activity until @@ -741,15 +741,6 @@ list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) if (v == NULL) n = 0; else { - if (a == b) { - /* Special case "a[i:j] = a" -- copy b first */ - v = list_slice(b, 0, Py_SIZE(b)); - if (v == NULL) - return result; - result = list_ass_slice(a, ilow, ihigh, v); - Py_DECREF(v); - return result; - } v_as_SF = PySequence_Fast(v, "can only assign an iterable"); if(v_as_SF == NULL) goto Error; @@ -823,6 +814,34 @@ list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) #undef b } +static int +list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) +{ + int ret; + if (a == (PyListObject *)v) { + Py_BEGIN_CRITICAL_SECTION(a); + Py_ssize_t n = PyList_GET_SIZE(a); + PyObject *copy = list_slice_lock_held(a, 0, n); + if (copy == NULL) { + return -1; + } + ret = list_ass_slice_lock_held(a, ilow, ihigh, copy); + Py_DECREF(copy); + Py_END_CRITICAL_SECTION(); + } + else if (v != NULL && PyList_CheckExact(v)) { + Py_BEGIN_CRITICAL_SECTION2(a, v); + ret = list_ass_slice_lock_held(a, ilow, ihigh, v); + Py_END_CRITICAL_SECTION2(); + } + else { + Py_BEGIN_CRITICAL_SECTION(a); + ret = list_ass_slice_lock_held(a, ilow, ihigh, v); + Py_END_CRITICAL_SECTION(); + } + return ret; +} + int PyList_SetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) { @@ -956,7 +975,7 @@ static PyObject * list_copy_impl(PyListObject *self) /*[clinic end generated code: output=ec6b72d6209d418e input=81c54b0c7bb4f73d]*/ { - return list_slice(self, 0, Py_SIZE(self)); + return list_slice_lock_held(self, 0, Py_SIZE(self)); } /*[clinic input] @@ -2884,8 +2903,7 @@ list_remove_impl(PyListObject *self, PyObject *value) int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); Py_DECREF(obj); if (cmp > 0) { - if (list_ass_slice(self, i, i+1, - (PyObject *)NULL) == 0) + if (list_ass_slice_lock_held(self, i, i+1, NULL) == 0) Py_RETURN_NONE; return NULL; } @@ -3085,6 +3103,45 @@ static PySequenceMethods list_as_sequence = { list_inplace_repeat, /* sq_inplace_repeat */ }; +static inline PyObject * +list_slice_step_lock_held(PyListObject *a, Py_ssize_t start, Py_ssize_t step, Py_ssize_t len) +{ + PyListObject *np = (PyListObject *)list_new_prealloc(len); + if (np == NULL) { + return NULL; + } + size_t cur; + Py_ssize_t i; + PyObject **src = a->ob_item; + PyObject **dest = np->ob_item; + for (cur = start, i = 0; i < len; + cur += (size_t)step, i++) { + PyObject *v = src[cur]; + dest[i] = Py_NewRef(v); + } + Py_SET_SIZE(np, len); + return (PyObject *)np; +} + +static PyObject * +list_slice_wrap(PyListObject *aa, Py_ssize_t start, Py_ssize_t stop, Py_ssize_t step) +{ + PyObject *res = NULL; + Py_BEGIN_CRITICAL_SECTION(aa); + Py_ssize_t len = PySlice_AdjustIndices(Py_SIZE(aa), &start, &stop, step); + if (len <= 0) { + res = PyList_New(0); + } + else if (step == 1) { + res = list_slice_lock_held(aa, start, stop); + } + else { + res = list_slice_step_lock_held(aa, start, step, len); + } + Py_END_CRITICAL_SECTION(); + return res; +} + static PyObject * list_subscript(PyObject* _self, PyObject* item) { @@ -3099,38 +3156,11 @@ list_subscript(PyObject* _self, PyObject* item) return list_item((PyObject *)self, i); } else if (PySlice_Check(item)) { - Py_ssize_t start, stop, step, slicelength, i; - size_t cur; - PyObject* result; - PyObject* it; - PyObject **src, **dest; - + Py_ssize_t start, stop, step; if (PySlice_Unpack(item, &start, &stop, &step) < 0) { return NULL; } - slicelength = PySlice_AdjustIndices(Py_SIZE(self), &start, &stop, - step); - - if (slicelength <= 0) { - return PyList_New(0); - } - else if (step == 1) { - return list_slice(self, start, stop); - } - else { - result = list_new_prealloc(slicelength); - if (!result) return NULL; - - src = self->ob_item; - dest = ((PyListObject *)result)->ob_item; - for (cur = start, i = 0; i < slicelength; - cur += (size_t)step, i++) { - it = Py_NewRef(src[cur]); - dest[i] = it; - } - Py_SET_SIZE(result, slicelength); - return result; - } + return list_slice_wrap(self, start, stop, step); } else { PyErr_Format(PyExc_TypeError, @@ -3241,8 +3271,10 @@ list_ass_subscript(PyObject* _self, PyObject* item, PyObject* value) /* protect against a[::-1] = a */ if (self == (PyListObject*)value) { - seq = list_slice((PyListObject*)value, 0, - PyList_GET_SIZE(value)); + Py_BEGIN_CRITICAL_SECTION(value); + seq = list_slice_lock_held((PyListObject*)value, 0, + Py_SIZE(value)); + Py_END_CRITICAL_SECTION(); } else { seq = PySequence_Fast(value, From 7af063d1d85f965da06a65eca800f4c537d55fa5 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Mar 2024 00:18:53 -0800 Subject: [PATCH 07/26] GH-116313: get WASI builds to run under wasmtime 18 w/ WASI 0.2/preview2 primitives (#116327) * GH-116313: get WASI builds to run under wasmtime 18 w/ WASI 0.2/preview2 primitives * Add the configure changes * Update `wasm_build.py` --- .devcontainer/Dockerfile | 2 +- .../Build/2024-03-04-12-43-42.gh-issue-116313.cLLb8S.rst | 1 + Tools/wasm/wasi.py | 6 ++++-- Tools/wasm/wasm_build.py | 6 ++++-- configure | 2 +- configure.ac | 2 +- 6 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-03-04-12-43-42.gh-issue-116313.cLLb8S.rst diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9f808af38e69df..365756458bba30 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -6,7 +6,7 @@ ENV WASI_SDK_VERSION=20 ENV WASI_SDK_PATH=/opt/wasi-sdk ENV WASMTIME_HOME=/opt/wasmtime -ENV WASMTIME_VERSION=14.0.4 +ENV WASMTIME_VERSION=18.0.2 ENV WASMTIME_CPU_ARCH=x86_64 RUN dnf -y --nodocs --setopt=install_weak_deps=False install /usr/bin/{blurb,clang,curl,git,ln,tar,xz} 'dnf-command(builddep)' && \ diff --git a/Misc/NEWS.d/next/Build/2024-03-04-12-43-42.gh-issue-116313.cLLb8S.rst b/Misc/NEWS.d/next/Build/2024-03-04-12-43-42.gh-issue-116313.cLLb8S.rst new file mode 100644 index 00000000000000..61501549060024 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-03-04-12-43-42.gh-issue-116313.cLLb8S.rst @@ -0,0 +1 @@ +Get WASI builds to work under wasmtime 18 w/ WASI 0.2/preview2 primitives. diff --git a/Tools/wasm/wasi.py b/Tools/wasm/wasi.py index 1e75db5c7b8329..efb005e53ab989 100644 --- a/Tools/wasm/wasi.py +++ b/Tools/wasm/wasi.py @@ -283,8 +283,10 @@ def main(): # The 8388608 value comes from `ulimit -s` under Linux # which equates to 8291 KiB. "--wasm max-wasm-stack=8388608 " - # Enable thread support. - "--wasm threads=y --wasi threads=y " + # Use WASI 0.2 primitives. + "--wasi preview2 " + # Enable thread support; causes use of preview1. + #"--wasm threads=y --wasi threads=y " # Map the checkout to / to load the stdlib from /Lib. "--dir {HOST_DIR}::{GUEST_DIR} " # Set PYTHONPATH to the sysconfig data. diff --git a/Tools/wasm/wasm_build.py b/Tools/wasm/wasm_build.py index c0b9999a5dad03..47a0abb8b5feef 100755 --- a/Tools/wasm/wasm_build.py +++ b/Tools/wasm/wasm_build.py @@ -329,8 +329,10 @@ def _check_wasi() -> None: # workaround for https://github.com/python/cpython/issues/95952 "HOSTRUNNER": ( "wasmtime run " - "--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib " - "--mapdir /::{srcdir} --" + "--wasm max-wasm-stack=8388608 " + "--wasi preview2 " + "--dir {srcdir}::/ " + "--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib" ), "PATH": [WASI_SDK_PATH / "bin", os.environ["PATH"]], }, diff --git a/configure b/configure index 4a980fea453697..c758749cd371ed 100755 --- a/configure +++ b/configure @@ -7655,7 +7655,7 @@ then : fi ;; #( WASI/*) : - HOSTRUNNER='wasmtime run --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt):/Lib --mapdir /::$(srcdir) --' ;; #( + HOSTRUNNER='wasmtime run --wasm max-wasm-stack=8388608 --wasi preview2 --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt):/Lib --dir $(srcdir)::/' ;; #( *) : HOSTRUNNER='' ;; diff --git a/configure.ac b/configure.ac index 103c24962b7b42..80d0a7edc7cad4 100644 --- a/configure.ac +++ b/configure.ac @@ -1537,7 +1537,7 @@ then dnl TODO: support other WASI runtimes dnl wasmtime starts the proces with "/" as CWD. For OOT builds add the dnl directory containing _sysconfigdata to PYTHONPATH. - [WASI/*], [HOSTRUNNER='wasmtime run --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt):/Lib --mapdir /::$(srcdir) --'], + [WASI/*], [HOSTRUNNER='wasmtime run --wasm max-wasm-stack=8388608 --wasi preview2 --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt):/Lib --dir $(srcdir)::/'], [HOSTRUNNER=''] ) fi From ffcc450a9b8b6927549b501eff7ac14abc238448 Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Tue, 5 Mar 2024 01:08:18 -0800 Subject: [PATCH 08/26] gh-112075: Enable freeing with qsbr and fallback to lock on key changed (GH-116336) --- Objects/dictobject.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 027cff1bfb451a..536746ca41eed5 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -183,9 +183,6 @@ set_values(PyDictObject *mp, PyDictValues *values) _Py_atomic_store_ptr_release(&mp->ma_values, values); } -// Defined until we get QSBR -#define _PyMem_FreeQsbr PyMem_Free - #define LOCK_KEYS(keys) PyMutex_LockFlags(&keys->dk_mutex, _Py_LOCK_DONT_DETACH) #define UNLOCK_KEYS(keys) PyMutex_Unlock(&keys->dk_mutex) @@ -806,7 +803,7 @@ free_keys_object(PyDictKeysObject *keys, bool use_qsbr) { #ifdef Py_GIL_DISABLED if (use_qsbr) { - _PyMem_FreeQsbr(keys); + _PyMem_FreeDelayed(keys); return; } #endif @@ -846,7 +843,7 @@ free_values(PyDictValues *values, bool use_qsbr) int prefix_size = DICT_VALUES_SIZE(values); #ifdef Py_GIL_DISABLED if (use_qsbr) { - _PyMem_FreeQsbr(((char *)values)-prefix_size); + _PyMem_FreeDelayed(((char *)values)-prefix_size); return; } #endif @@ -6694,7 +6691,7 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, #ifdef Py_GIL_DISABLED // Try a thread-safe lookup to see if the index is already allocated ix = unicodekeys_lookup_unicode_threadsafe(keys, name, hash); - if (ix == DKIX_EMPTY) { + if (ix == DKIX_EMPTY || ix == DKIX_KEY_CHANGED) { // Lock keys and do insert LOCK_KEYS(keys); ix = insert_into_splitdictkeys(keys, name, hash); From a29998a06bf75264c3faaeeec4584a5f75b45a1f Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 5 Mar 2024 12:14:18 +0300 Subject: [PATCH 09/26] gh-116325: Raise `SyntaxError` rather than `IndexError` on ForwardRef with empty string arg (#116341) --- Lib/test/test_typing.py | 6 ++++++ Lib/typing.py | 2 +- .../Library/2024-03-05-02-09-18.gh-issue-116325.FmlBYv.rst | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-03-05-02-09-18.gh-issue-116325.FmlBYv.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index dbbbe63e95ad46..912384ab6bfe84 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -5867,6 +5867,12 @@ def foo(a: 'Node[T'): with self.assertRaises(SyntaxError): get_type_hints(foo) + def test_syntax_error_empty_string(self): + for form in [typing.List, typing.Set, typing.Type, typing.Deque]: + with self.subTest(form=form): + with self.assertRaises(SyntaxError): + form[''] + def test_name_error(self): def foo(a: 'Noode[T]'): diff --git a/Lib/typing.py b/Lib/typing.py index 2cb4a714ea3a85..cca9525d632ea5 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -880,7 +880,7 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False): # If we do `def f(*args: *Ts)`, then we'll have `arg = '*Ts'`. # Unfortunately, this isn't a valid expression on its own, so we # do the unpacking manually. - if arg[0] == '*': + if arg.startswith('*'): arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0] or (*tuple[int, int],)[0] else: arg_to_compile = arg diff --git a/Misc/NEWS.d/next/Library/2024-03-05-02-09-18.gh-issue-116325.FmlBYv.rst b/Misc/NEWS.d/next/Library/2024-03-05-02-09-18.gh-issue-116325.FmlBYv.rst new file mode 100644 index 00000000000000..aec4ee79b59cf2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-05-02-09-18.gh-issue-116325.FmlBYv.rst @@ -0,0 +1,2 @@ +:mod:`typing`: raise :exc:`SyntaxError` instead of :exc:`AttributeError` +on forward references as empty strings. From cbf3d38cbeb6e640d5959549169ec45cdedc1a71 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 5 Mar 2024 11:23:46 +0000 Subject: [PATCH 10/26] GH-115685: Optimize `TO_BOOL` and variants based on truthiness of input. (GH-116311) --- Include/internal/pycore_optimizer.h | 1 + Python/optimizer_analysis.c | 22 +++++++++ Python/optimizer_bytecodes.c | 73 ++++++++++++++++++----------- Python/optimizer_cases.c.h | 65 ++++++++++++++----------- Python/optimizer_symbols.c | 44 +++++++++++++++++ 5 files changed, 150 insertions(+), 55 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index d32e6c0174f680..7c977728a95024 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -96,6 +96,7 @@ extern bool _Py_uop_sym_set_non_null(_Py_UopsSymbol *sym); extern bool _Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ); extern bool _Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val); extern bool _Py_uop_sym_is_bottom(_Py_UopsSymbol *sym); +extern int _Py_uop_sym_truthiness(_Py_UopsSymbol *sym); extern int _Py_uop_abstractcontext_init(_Py_UOpsContext *ctx); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index a326e2249bb4de..1e1d5529ee17d7 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -298,9 +298,31 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_set_type _Py_uop_sym_set_type #define sym_set_const _Py_uop_sym_set_const #define sym_is_bottom _Py_uop_sym_is_bottom +#define sym_truthiness _Py_uop_sym_truthiness #define frame_new _Py_uop_frame_new #define frame_pop _Py_uop_frame_pop +static int +optimize_to_bool( + _PyUOpInstruction *this_instr, + _Py_UOpsContext *ctx, + _Py_UopsSymbol *value, + _Py_UopsSymbol **result_ptr) +{ + if (sym_matches_type(value, &PyBool_Type)) { + REPLACE_OP(this_instr, _NOP, 0, 0); + *result_ptr = value; + return 1; + } + int truthiness = sym_truthiness(value); + if (truthiness >= 0) { + PyObject *load = truthiness ? Py_True : Py_False; + REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load); + *result_ptr = sym_new_const(ctx, load); + return 1; + } + return 0; +} /* 1 for success, 0 for not ready, cannot error at the moment. */ static int diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 786d884fc5a1a8..2cf54270e4ad35 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -29,6 +29,14 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; #define frame_new _Py_uop_frame_new #define frame_pop _Py_uop_frame_pop +extern int +optimize_to_bool( + _PyUOpInstruction *this_instr, + _Py_UOpsContext *ctx, + _Py_UopsSymbol *value, + _Py_UopsSymbol **result_ptr); + + static int dummy_func(void) { @@ -271,63 +279,72 @@ dummy_func(void) { } op(_TO_BOOL, (value -- res)) { - (void)value; - res = sym_new_type(ctx, &PyBool_Type); - OUT_OF_SPACE_IF_NULL(res); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + res = sym_new_type(ctx, &PyBool_Type); + OUT_OF_SPACE_IF_NULL(res); + } } - op(_TO_BOOL_BOOL, (value -- value)) { - if (sym_matches_type(value, &PyBool_Type)) { - REPLACE_OP(this_instr, _NOP, 0, 0); + op(_TO_BOOL_BOOL, (value -- res)) { + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); } else { if(!sym_set_type(value, &PyBool_Type)) { goto hit_bottom; } + res = value; } } op(_TO_BOOL_INT, (value -- res)) { - if (sym_is_const(value) && sym_matches_type(value, &PyLong_Type)) { - PyObject *load = _PyLong_IsZero((PyLongObject *)sym_get_const(value)) - ? Py_False : Py_True; - REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load); - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, load)); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); } else { + if(!sym_set_type(value, &PyLong_Type)) { + goto hit_bottom; + } OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); } - if(!sym_set_type(value, &PyLong_Type)) { - goto hit_bottom; - } } op(_TO_BOOL_LIST, (value -- res)) { - if(!sym_set_type(value, &PyList_Type)) { - goto hit_bottom; + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if(!sym_set_type(value, &PyList_Type)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); } - OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); } op(_TO_BOOL_NONE, (value -- res)) { - if (sym_get_const(value) == Py_None) { - REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)Py_False); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if (!sym_set_const(value, Py_None)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, Py_False)); } - sym_set_const(value, Py_None); - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, Py_False)); } op(_TO_BOOL_STR, (value -- res)) { - if (sym_is_const(value) && sym_matches_type(value, &PyUnicode_Type)) { - PyObject *load = sym_get_const(value) == &_Py_STR(empty) ? Py_False : Py_True; - REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load); - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, load)); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); } else { OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); - } - if(!sym_set_type(value, &PyUnicode_Type)) { - goto hit_bottom; + if(!sym_set_type(value, &PyUnicode_Type)) { + goto hit_bottom; + } } } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 6d3488f2118589..f2c186a0ae1380 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -107,24 +107,31 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - (void)value; - res = sym_new_type(ctx, &PyBool_Type); - OUT_OF_SPACE_IF_NULL(res); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + res = sym_new_type(ctx, &PyBool_Type); + OUT_OF_SPACE_IF_NULL(res); + } stack_pointer[-1] = res; break; } case _TO_BOOL_BOOL: { _Py_UopsSymbol *value; + _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (sym_matches_type(value, &PyBool_Type)) { - REPLACE_OP(this_instr, _NOP, 0, 0); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); } else { if(!sym_set_type(value, &PyBool_Type)) { goto hit_bottom; } + res = value; } + stack_pointer[-1] = res; break; } @@ -132,18 +139,15 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (sym_is_const(value) && sym_matches_type(value, &PyLong_Type)) { - PyObject *load = _PyLong_IsZero((PyLongObject *)sym_get_const(value)) - ? Py_False : Py_True; - REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load); - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, load)); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); } else { + if(!sym_set_type(value, &PyLong_Type)) { + goto hit_bottom; + } OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); } - if(!sym_set_type(value, &PyLong_Type)) { - goto hit_bottom; - } stack_pointer[-1] = res; break; } @@ -152,10 +156,15 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if(!sym_set_type(value, &PyList_Type)) { - goto hit_bottom; + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if(!sym_set_type(value, &PyList_Type)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); } - OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); stack_pointer[-1] = res; break; } @@ -164,11 +173,15 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (sym_get_const(value) == Py_None) { - REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)Py_False); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if (!sym_set_const(value, Py_None)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, Py_False)); } - sym_set_const(value, Py_None); - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, Py_False)); stack_pointer[-1] = res; break; } @@ -177,16 +190,14 @@ _Py_UopsSymbol *value; _Py_UopsSymbol *res; value = stack_pointer[-1]; - if (sym_is_const(value) && sym_matches_type(value, &PyUnicode_Type)) { - PyObject *load = sym_get_const(value) == &_Py_STR(empty) ? Py_False : Py_True; - REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load); - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, load)); + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); } else { OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); - } - if(!sym_set_type(value, &PyUnicode_Type)) { - goto hit_bottom; + if(!sym_set_type(value, &PyUnicode_Type)) { + goto hit_bottom; + } } stack_pointer[-1] = res; break; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 5c3ec2b5ed1a4c..29fe31a0e9b94c 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -4,6 +4,7 @@ #include "cpython/optimizer.h" #include "pycore_code.h" #include "pycore_frame.h" +#include "pycore_long.h" #include "pycore_optimizer.h" #include @@ -240,6 +241,40 @@ _Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ) return sym->typ == typ; } +int +_Py_uop_sym_truthiness(_Py_UopsSymbol *sym) +{ + /* There are some non-constant values for + * which `bool(val)` always evaluates to + * True or False, such as tuples with known + * length, but unknown contents, or bound-methods. + * This function will need updating + * should we support those values. + */ + if (_Py_uop_sym_is_bottom(sym)) { + return -1; + } + if (!_Py_uop_sym_is_const(sym)) { + return -1; + } + PyObject *value = _Py_uop_sym_get_const(sym); + if (value == Py_None) { + return 0; + } + /* Only handle a few known safe types */ + PyTypeObject *tp = Py_TYPE(value); + if (tp == &PyLong_Type) { + return !_PyLong_IsZero((PyLongObject *)value); + } + if (tp == &PyUnicode_Type) { + return value != &_Py_STR(empty); + } + if (tp == &PyBool_Type) { + return value == Py_True; + } + return -1; +} + // 0 on success, -1 on error. _Py_UOpsAbstractFrame * _Py_uop_frame_new( @@ -413,6 +448,7 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) goto fail; } _Py_uop_sym_set_const(sym, val_42); + TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 1, "bool(42) is not True"); TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "42 is NULL"); TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "42 isn't not NULL"); TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "42 isn't an int"); @@ -436,6 +472,14 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) _Py_uop_sym_set_const(sym, val_43); // Should make it bottom TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(42 and 43) isn't bottom"); + + sym = _Py_uop_sym_new_const(ctx, Py_None); + TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(None) is not False"); + sym = _Py_uop_sym_new_const(ctx, Py_False); + TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(False) is not False"); + sym = _Py_uop_sym_new_const(ctx, PyLong_FromLong(0)); + TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(0) is not False"); + _Py_uop_abstractcontext_fini(ctx); Py_DECREF(val_42); Py_DECREF(val_43); From c91bdf86ef1cf9365b61a46aa2e51e5d1932b00a Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 5 Mar 2024 15:31:04 +0300 Subject: [PATCH 11/26] gh-116326: Handler errors correctly in `getwindowsversion` in `sysmodule` (#116339) --- Python/sysmodule.c | 52 ++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index a6e171601447c0..a4161da02980a7 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1643,12 +1643,13 @@ sys_getwindowsversion_impl(PyObject *module) int pos = 0; OSVERSIONINFOEXW ver; - version = PyObject_GetAttrString(module, "_cached_windows_version"); + if (PyObject_GetOptionalAttrString(module, "_cached_windows_version", &version) < 0) { + return NULL; + }; if (version && PyObject_TypeCheck(version, &WindowsVersionType)) { return version; } Py_XDECREF(version); - PyErr_Clear(); ver.dwOSVersionInfoSize = sizeof(ver); if (!GetVersionExW((OSVERSIONINFOW*) &ver)) @@ -1658,15 +1659,24 @@ sys_getwindowsversion_impl(PyObject *module) if (version == NULL) return NULL; - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.dwMajorVersion)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.dwMinorVersion)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.dwBuildNumber)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.dwPlatformId)); - PyStructSequence_SET_ITEM(version, pos++, PyUnicode_FromWideChar(ver.szCSDVersion, -1)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wServicePackMajor)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wServicePackMinor)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wSuiteMask)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wProductType)); +#define SET_VERSION_INFO(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + goto error; \ + } \ + PyStructSequence_SET_ITEM(version, pos++, item); \ + } while(0) + + SET_VERSION_INFO(PyLong_FromLong(ver.dwMajorVersion)); + SET_VERSION_INFO(PyLong_FromLong(ver.dwMinorVersion)); + SET_VERSION_INFO(PyLong_FromLong(ver.dwBuildNumber)); + SET_VERSION_INFO(PyLong_FromLong(ver.dwPlatformId)); + SET_VERSION_INFO(PyUnicode_FromWideChar(ver.szCSDVersion, -1)); + SET_VERSION_INFO(PyLong_FromLong(ver.wServicePackMajor)); + SET_VERSION_INFO(PyLong_FromLong(ver.wServicePackMinor)); + SET_VERSION_INFO(PyLong_FromLong(ver.wSuiteMask)); + SET_VERSION_INFO(PyLong_FromLong(ver.wProductType)); // GetVersion will lie if we are running in a compatibility mode. // We need to read the version info from a system file resource @@ -1674,6 +1684,10 @@ sys_getwindowsversion_impl(PyObject *module) // just return whatever GetVersion said. PyObject *realVersion = _sys_getwindowsversion_from_kernel32(); if (!realVersion) { + if (!PyErr_ExceptionMatches(PyExc_WindowsError)) { + return NULL; + } + PyErr_Clear(); realVersion = Py_BuildValue("(kkk)", ver.dwMajorVersion, @@ -1682,21 +1696,19 @@ sys_getwindowsversion_impl(PyObject *module) ); } - if (realVersion) { - PyStructSequence_SET_ITEM(version, pos++, realVersion); - } + SET_VERSION_INFO(realVersion); - if (PyErr_Occurred()) { - Py_DECREF(version); - return NULL; - } +#undef SET_VERSION_INFO if (PyObject_SetAttrString(module, "_cached_windows_version", version) < 0) { - Py_DECREF(version); - return NULL; + goto error; } return version; + +error: + Py_DECREF(version); + return NULL; } #pragma warning(pop) From 0c81ce13602b88fd59f23f701ed8dc377d74e76e Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 5 Mar 2024 15:06:00 +0000 Subject: [PATCH 12/26] GH-115819: Eliminate Boolean guards when value is known (GH-116355) --- Include/internal/pycore_optimizer.h | 1 + Lib/test/test_capi/test_opt.py | 6 +++-- Python/optimizer_analysis.c | 11 ++++++++ Python/optimizer_bytecodes.c | 41 +++++++++++++++++++++++++++++ Python/optimizer_cases.c.h | 36 +++++++++++++++++++++++++ Python/optimizer_symbols.c | 9 +++++++ 6 files changed, 102 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 7c977728a95024..fcead4d8714870 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -90,6 +90,7 @@ extern _Py_UopsSymbol *_Py_uop_sym_new_type( _Py_UOpsContext *ctx, PyTypeObject *typ); extern _Py_UopsSymbol *_Py_uop_sym_new_const(_Py_UOpsContext *ctx, PyObject *const_val); extern _Py_UopsSymbol *_Py_uop_sym_new_null(_Py_UOpsContext *ctx); +extern bool _Py_uop_sym_has_type(_Py_UopsSymbol *sym); extern bool _Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ); extern bool _Py_uop_sym_set_null(_Py_UopsSymbol *sym); extern bool _Py_uop_sym_set_non_null(_Py_UopsSymbol *sym); diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index a0a19225b79433..b0859a382de523 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -331,7 +331,8 @@ def testfunc(a): ex = get_first_executor(testfunc) self.assertIsNotNone(ex) uops = get_opnames(ex) - self.assertIn("_GUARD_IS_NOT_NONE_POP", uops) + self.assertNotIn("_GUARD_IS_NONE_POP", uops) + self.assertNotIn("_GUARD_IS_NOT_NONE_POP", uops) def test_pop_jump_if_not_none(self): def testfunc(a): @@ -347,7 +348,8 @@ def testfunc(a): ex = get_first_executor(testfunc) self.assertIsNotNone(ex) uops = get_opnames(ex) - self.assertIn("_GUARD_IS_NONE_POP", uops) + self.assertNotIn("_GUARD_IS_NONE_POP", uops) + self.assertNotIn("_GUARD_IS_NOT_NONE_POP", uops) def test_pop_jump_if_true(self): def testfunc(n): diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 1e1d5529ee17d7..21cccdb95de76b 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -292,6 +292,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_is_null _Py_uop_sym_is_null #define sym_new_const _Py_uop_sym_new_const #define sym_new_null _Py_uop_sym_new_null +#define sym_has_type _Py_uop_sym_has_type #define sym_matches_type _Py_uop_sym_matches_type #define sym_set_null _Py_uop_sym_set_null #define sym_set_non_null _Py_uop_sym_set_non_null @@ -324,6 +325,16 @@ optimize_to_bool( return 0; } +static void +eliminate_pop_guard(_PyUOpInstruction *this_instr, bool exit) +{ + REPLACE_OP(this_instr, _POP_TOP, 0, 0); + if (exit) { + REPLACE_OP((this_instr+1), _EXIT_TRACE, 0, 0); + this_instr[1].target = this_instr->target; + } +} + /* 1 for success, 0 for not ready, cannot error at the moment. */ static int optimize_uops( diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 2cf54270e4ad35..ee67a2b075b015 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -21,6 +21,7 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; #define sym_new_const _Py_uop_sym_new_const #define sym_new_null _Py_uop_sym_new_null #define sym_matches_type _Py_uop_sym_matches_type +#define sym_has_type _Py_uop_sym_has_type #define sym_set_null _Py_uop_sym_set_null #define sym_set_non_null _Py_uop_sym_set_non_null #define sym_set_type _Py_uop_sym_set_type @@ -36,6 +37,8 @@ optimize_to_bool( _Py_UopsSymbol *value, _Py_UopsSymbol **result_ptr); +extern void +eliminate_pop_guard(_PyUOpInstruction *this_instr, bool exit) static int dummy_func(void) { @@ -557,7 +560,45 @@ dummy_func(void) { (void)iter; } + op(_GUARD_IS_TRUE_POP, (flag -- )) { + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, value != Py_True); + } + } + + op(_GUARD_IS_FALSE_POP, (flag -- )) { + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, value != Py_False); + } + } + + op(_GUARD_IS_NONE_POP, (flag -- )) { + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, !Py_IsNone(value)); + } + else if (sym_has_type(flag)) { + assert(!sym_matches_type(flag, &_PyNone_Type)); + eliminate_pop_guard(this_instr, true); + } + } + op(_GUARD_IS_NOT_NONE_POP, (flag -- )) { + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, Py_IsNone(value)); + } + else if (sym_has_type(flag)) { + assert(!sym_matches_type(flag, &_PyNone_Type)); + eliminate_pop_guard(this_instr, false); + } + } // END BYTECODES // diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index f2c186a0ae1380..6e65e2e0b4c4ca 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1803,21 +1803,57 @@ /* _INSTRUMENTED_POP_JUMP_IF_NOT_NONE is not a viable micro-op for tier 2 */ case _GUARD_IS_TRUE_POP: { + _Py_UopsSymbol *flag; + flag = stack_pointer[-1]; + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, value != Py_True); + } stack_pointer += -1; break; } case _GUARD_IS_FALSE_POP: { + _Py_UopsSymbol *flag; + flag = stack_pointer[-1]; + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, value != Py_False); + } stack_pointer += -1; break; } case _GUARD_IS_NONE_POP: { + _Py_UopsSymbol *flag; + flag = stack_pointer[-1]; + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, !Py_IsNone(value)); + } + else if (sym_has_type(flag)) { + assert(!sym_matches_type(flag, &_PyNone_Type)); + eliminate_pop_guard(this_instr, true); + } stack_pointer += -1; break; } case _GUARD_IS_NOT_NONE_POP: { + _Py_UopsSymbol *flag; + flag = stack_pointer[-1]; + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, Py_IsNone(value)); + } + else if (sym_has_type(flag)) { + assert(!sym_matches_type(flag, &_PyNone_Type)); + eliminate_pop_guard(this_instr, false); + } stack_pointer += -1; break; } diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 29fe31a0e9b94c..86b0d4d395afa2 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -231,6 +231,15 @@ _Py_uop_sym_new_null(_Py_UOpsContext *ctx) return null_sym; } +bool +_Py_uop_sym_has_type(_Py_UopsSymbol *sym) +{ + if (_Py_uop_sym_is_bottom(sym)) { + return false; + } + return sym->typ != NULL; +} + bool _Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ) { From 23db9c62272f7470aadf8f52fe3ebb42b5e5d380 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 5 Mar 2024 15:23:08 +0000 Subject: [PATCH 13/26] GH-115685: Split `_TO_BOOL_ALWAYS_TRUE` into micro-ops (GH-116352) --- Include/internal/pycore_opcode_metadata.h | 2 +- Include/internal/pycore_uop_ids.h | 40 +++++++++++------------ Include/internal/pycore_uop_metadata.h | 4 +-- Python/bytecodes.c | 13 ++++---- Python/executor_cases.c.h | 7 +--- Python/generated_cases.c.h | 23 ++++++++----- Python/optimizer_analysis.c | 3 ++ Python/optimizer_cases.c.h | 2 +- 8 files changed, 50 insertions(+), 44 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index ab34366ab1066c..f355a496cf2e70 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1320,7 +1320,7 @@ _PyOpcode_macro_expansion[256] = { [STORE_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { _STORE_SUBSCR_LIST_INT, 0, 0 } } }, [SWAP] = { .nuops = 1, .uops = { { _SWAP, 0, 0 } } }, [TO_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL, 0, 0 } } }, - [TO_BOOL_ALWAYS_TRUE] = { .nuops = 1, .uops = { { _TO_BOOL_ALWAYS_TRUE, 2, 1 } } }, + [TO_BOOL_ALWAYS_TRUE] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _REPLACE_WITH_TRUE, 0, 0 } } }, [TO_BOOL_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL_BOOL, 0, 0 } } }, [TO_BOOL_INT] = { .nuops = 1, .uops = { { _TO_BOOL_INT, 0, 0 } } }, [TO_BOOL_LIST] = { .nuops = 1, .uops = { { _TO_BOOL_LIST, 0, 0 } } }, diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 8f71eab44d914d..f1601de6ae75b6 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -220,40 +220,40 @@ extern "C" { #define _PUSH_EXC_INFO PUSH_EXC_INFO #define _PUSH_FRAME 405 #define _PUSH_NULL PUSH_NULL +#define _REPLACE_WITH_TRUE 406 #define _RESUME_CHECK RESUME_CHECK -#define _SAVE_RETURN_OFFSET 406 -#define _SEND 407 +#define _SAVE_RETURN_OFFSET 407 +#define _SEND 408 #define _SEND_GEN SEND_GEN #define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS #define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE #define _SET_UPDATE SET_UPDATE -#define _START_EXECUTOR 408 -#define _STORE_ATTR 409 -#define _STORE_ATTR_INSTANCE_VALUE 410 -#define _STORE_ATTR_SLOT 411 +#define _START_EXECUTOR 409 +#define _STORE_ATTR 410 +#define _STORE_ATTR_INSTANCE_VALUE 411 +#define _STORE_ATTR_SLOT 412 #define _STORE_ATTR_WITH_HINT STORE_ATTR_WITH_HINT #define _STORE_DEREF STORE_DEREF -#define _STORE_FAST 412 -#define _STORE_FAST_0 413 -#define _STORE_FAST_1 414 -#define _STORE_FAST_2 415 -#define _STORE_FAST_3 416 -#define _STORE_FAST_4 417 -#define _STORE_FAST_5 418 -#define _STORE_FAST_6 419 -#define _STORE_FAST_7 420 +#define _STORE_FAST 413 +#define _STORE_FAST_0 414 +#define _STORE_FAST_1 415 +#define _STORE_FAST_2 416 +#define _STORE_FAST_3 417 +#define _STORE_FAST_4 418 +#define _STORE_FAST_5 419 +#define _STORE_FAST_6 420 +#define _STORE_FAST_7 421 #define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST #define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST #define _STORE_GLOBAL STORE_GLOBAL #define _STORE_NAME STORE_NAME #define _STORE_SLICE STORE_SLICE -#define _STORE_SUBSCR 421 +#define _STORE_SUBSCR 422 #define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT #define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT #define _SWAP SWAP -#define _TO_BOOL 422 -#define _TO_BOOL_ALWAYS_TRUE TO_BOOL_ALWAYS_TRUE +#define _TO_BOOL 423 #define _TO_BOOL_BOOL TO_BOOL_BOOL #define _TO_BOOL_INT TO_BOOL_INT #define _TO_BOOL_LIST TO_BOOL_LIST @@ -263,12 +263,12 @@ extern "C" { #define _UNARY_NEGATIVE UNARY_NEGATIVE #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 423 +#define _UNPACK_SEQUENCE 424 #define _UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_LIST #define _UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TUPLE #define _UNPACK_SEQUENCE_TWO_TUPLE UNPACK_SEQUENCE_TWO_TUPLE #define _WITH_EXCEPT_START WITH_EXCEPT_START -#define MAX_UOP_ID 423 +#define MAX_UOP_ID 424 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 7f921a6cd3f4c8..9452dff9d8797d 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -54,7 +54,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_TO_BOOL_LIST] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, [_TO_BOOL_NONE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, [_TO_BOOL_STR] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, - [_TO_BOOL_ALWAYS_TRUE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_REPLACE_WITH_TRUE] = 0, [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_BOTH_INT] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, @@ -430,6 +430,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_PUSH_EXC_INFO] = "_PUSH_EXC_INFO", [_PUSH_FRAME] = "_PUSH_FRAME", [_PUSH_NULL] = "_PUSH_NULL", + [_REPLACE_WITH_TRUE] = "_REPLACE_WITH_TRUE", [_RESUME_CHECK] = "_RESUME_CHECK", [_SAVE_RETURN_OFFSET] = "_SAVE_RETURN_OFFSET", [_SETUP_ANNOTATIONS] = "_SETUP_ANNOTATIONS", @@ -461,7 +462,6 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_STORE_SUBSCR_LIST_INT] = "_STORE_SUBSCR_LIST_INT", [_SWAP] = "_SWAP", [_TO_BOOL] = "_TO_BOOL", - [_TO_BOOL_ALWAYS_TRUE] = "_TO_BOOL_ALWAYS_TRUE", [_TO_BOOL_BOOL] = "_TO_BOOL_BOOL", [_TO_BOOL_INT] = "_TO_BOOL_INT", [_TO_BOOL_LIST] = "_TO_BOOL_LIST", diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 396a8f09f3feca..15794cb017f9ca 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -382,15 +382,16 @@ dummy_func( } } - inst(TO_BOOL_ALWAYS_TRUE, (unused/1, version/2, value -- res)) { - // This one is a bit weird, because we expect *some* failures: - assert(version); - EXIT_IF(Py_TYPE(value)->tp_version_tag != version); - STAT_INC(TO_BOOL, hit); - DECREF_INPUTS(); + op(_REPLACE_WITH_TRUE, (value -- res)) { + Py_DECREF(value); res = Py_True; } + macro(TO_BOOL_ALWAYS_TRUE) = + unused/1 + + _GUARD_TYPE_VERSION + + _REPLACE_WITH_TRUE; + inst(UNARY_INVERT, (value -- res)) { res = PyNumber_Invert(value); DECREF_INPUTS(); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 56ee93862743d5..806f748b05ebbf 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -383,15 +383,10 @@ break; } - case _TO_BOOL_ALWAYS_TRUE: { + case _REPLACE_WITH_TRUE: { PyObject *value; PyObject *res; value = stack_pointer[-1]; - uint32_t version = (uint32_t)CURRENT_OPERAND(); - // This one is a bit weird, because we expect *some* failures: - assert(version); - if (Py_TYPE(value)->tp_version_tag != version) goto side_exit; - STAT_INC(TO_BOOL, hit); Py_DECREF(value); res = Py_True; stack_pointer[-1] = res; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index e612c9e4c37632..4947c919b4ca20 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -5580,17 +5580,24 @@ next_instr += 4; INSTRUCTION_STATS(TO_BOOL_ALWAYS_TRUE); static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); + PyObject *owner; PyObject *value; PyObject *res; /* Skip 1 cache entry */ - value = stack_pointer[-1]; - uint32_t version = read_u32(&this_instr[2].cache); - // This one is a bit weird, because we expect *some* failures: - assert(version); - DEOPT_IF(Py_TYPE(value)->tp_version_tag != version, TO_BOOL); - STAT_INC(TO_BOOL, hit); - Py_DECREF(value); - res = Py_True; + // _GUARD_TYPE_VERSION + owner = stack_pointer[-1]; + { + uint32_t type_version = read_u32(&this_instr[2].cache); + PyTypeObject *tp = Py_TYPE(owner); + assert(type_version != 0); + DEOPT_IF(tp->tp_version_tag != type_version, TO_BOOL); + } + // _REPLACE_WITH_TRUE + value = owner; + { + Py_DECREF(value); + res = Py_True; + } stack_pointer[-1] = res; DISPATCH(); } diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 21cccdb95de76b..18e3382839a92f 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -456,6 +456,9 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) last->opcode = _NOP; buffer[pc].opcode = NOP; } + if (last->opcode == _REPLACE_WITH_TRUE) { + last->opcode = _NOP; + } break; } case _JUMP_TO_TOP: diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 6e65e2e0b4c4ca..4ed1ac90c1e4bd 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -203,7 +203,7 @@ break; } - case _TO_BOOL_ALWAYS_TRUE: { + case _REPLACE_WITH_TRUE: { _Py_UopsSymbol *res; res = sym_new_unknown(ctx); if (res == NULL) goto out_of_space; From 0064dfa0919cc93257c351a609f99461f6e4e3ac Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 5 Mar 2024 17:41:53 +0200 Subject: [PATCH 14/26] gh-115777: Fix double versionadded directives (GH-116269) --- Doc/library/asyncio-eventloop.rst | 2 +- Doc/library/audit_events.rst | 2 +- Doc/library/decimal.rst | 2 +- Doc/library/ipaddress.rst | 12 +----------- Doc/library/ssl.rst | 6 +++--- Doc/library/typing.rst | 8 +------- 6 files changed, 8 insertions(+), 24 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 828e506a72c937..be4d04e2d8cb89 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -626,7 +626,7 @@ Opening network connections Added support for Windows. .. versionchanged:: 3.11 - The *reuse_address* parameter, disabled since Python 3.9.0, 3.8.1, + The *reuse_address* parameter, disabled since Python 3.8.1, 3.7.6 and 3.6.10, has been entirely removed. .. coroutinemethod:: loop.create_unix_connection(protocol_factory, \ diff --git a/Doc/library/audit_events.rst b/Doc/library/audit_events.rst index 8227a7955bef81..a2a90a00d0cfca 100644 --- a/Doc/library/audit_events.rst +++ b/Doc/library/audit_events.rst @@ -7,7 +7,7 @@ Audit events table This table contains all events raised by :func:`sys.audit` or :c:func:`PySys_Audit` calls throughout the CPython runtime and the -standard library. These calls were added in 3.8.0 or later (see :pep:`578`). +standard library. These calls were added in 3.8 or later (see :pep:`578`). See :func:`sys.addaudithook` and :c:func:`PySys_AddAuditHook` for information on handling these events. diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index fb8bbf72adf268..8c671bf900712b 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1517,7 +1517,7 @@ are also included in the pure Python version for compatibility. the C version uses a thread-local rather than a coroutine-local context and the value is ``False``. This is slightly faster in some nested context scenarios. -.. versionadded:: 3.9 backported to 3.7 and 3.8. +.. versionadded:: 3.8.3 Rounding modes diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 1de36b643c4dca..7b07a34bd57575 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -121,22 +121,12 @@ write code that handles both IP versions correctly. Address objects are Leading zeros are tolerated, even in ambiguous cases that look like octal notation. - .. versionchanged:: 3.10 + .. versionchanged:: 3.9.5 Leading zeros are no longer tolerated and are treated as an error. IPv4 address strings are now parsed as strict as glibc :func:`~socket.inet_pton`. - .. versionchanged:: 3.9.5 - - The above change was also included in Python 3.9 starting with - version 3.9.5. - - .. versionchanged:: 3.8.12 - - The above change was also included in Python 3.8 starting with - version 3.8.12. - .. attribute:: version The appropriate version number: ``4`` for IPv4, ``6`` for IPv6. diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index ce8f3dd2d317c1..84b3c258e91949 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -737,11 +737,11 @@ Constants When Python has been compiled against an older version of OpenSSL, the flag defaults to *0*. - .. versionadded:: 3.7 + .. versionadded:: 3.6.3 .. deprecated:: 3.7 - The option is deprecated since OpenSSL 1.1.0. It was added to 2.7.15, - 3.6.3 and 3.7.0 for backwards compatibility with OpenSSL 1.0.2. + The option is deprecated since OpenSSL 1.1.0. It was added to 2.7.15 and + 3.6.3 for backwards compatibility with OpenSSL 1.0.2. .. data:: OP_NO_RENEGOTIATION diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 63bd62d1f6679b..da70757ea74452 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -954,7 +954,6 @@ using ``[]``. be used for this concept instead. Type checkers should treat the two equivalently. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.2 .. data:: Self @@ -3292,7 +3291,6 @@ Aliases to types in :mod:`collections` Deprecated alias to :class:`collections.ChainMap`. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.1 .. deprecated:: 3.9 @@ -3303,7 +3301,6 @@ Aliases to types in :mod:`collections` Deprecated alias to :class:`collections.Counter`. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.1 .. deprecated:: 3.9 @@ -3314,7 +3311,6 @@ Aliases to types in :mod:`collections` Deprecated alias to :class:`collections.deque`. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.1 .. deprecated:: 3.9 @@ -3389,7 +3385,7 @@ Aliases to container ABCs in :mod:`collections.abc` Deprecated alias to :class:`collections.abc.Collection`. - .. versionadded:: 3.6.0 + .. versionadded:: 3.6 .. deprecated:: 3.9 :class:`collections.abc.Collection` now supports subscripting (``[]``). @@ -3681,7 +3677,6 @@ Aliases to :mod:`contextlib` ABCs Deprecated alias to :class:`contextlib.AbstractContextManager`. .. versionadded:: 3.5.4 - .. versionadded:: 3.6.0 .. deprecated:: 3.9 :class:`contextlib.AbstractContextManager` @@ -3692,7 +3687,6 @@ Aliases to :mod:`contextlib` ABCs Deprecated alias to :class:`contextlib.AbstractAsyncContextManager`. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.2 .. deprecated:: 3.9 From df594011089a83d151ac7000954665536f3461b5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 5 Mar 2024 17:48:20 +0200 Subject: [PATCH 15/26] Fix the PyGetSetDef documentation (GH-116056) closure is not a function pointer, it is a user data pointer. --- Doc/c-api/structures.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index e2943f18ddc601..f9461ab01f6049 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -702,12 +702,12 @@ Defining Getters and Setters .. c:member:: void* closure - Optional function pointer, providing additional data for getter and setter. + Optional user data pointer, providing additional data for getter and setter. .. c:type:: PyObject *(*getter)(PyObject *, void *) The ``get`` function takes one :c:expr:`PyObject*` parameter (the - instance) and a function pointer (the associated ``closure``): + instance) and a user data pointer (the associated ``closure``): It should return a new reference on success or ``NULL`` with a set exception on failure. @@ -715,7 +715,7 @@ Defining Getters and Setters .. c:type:: int (*setter)(PyObject *, PyObject *, void *) ``set`` functions take two :c:expr:`PyObject*` parameters (the instance and - the value to be set) and a function pointer (the associated ``closure``): + the value to be set) and a user data pointer (the associated ``closure``): In case the attribute should be deleted the second parameter is ``NULL``. Should return ``0`` on success or ``-1`` with a set exception on failure. From f97f25ef5dfcdfec0d9a359fd970abd139cf3428 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 5 Mar 2024 17:49:01 +0200 Subject: [PATCH 16/26] gh-76511: Fix email.Message.as_string() for non-ASCII message with ASCII charset (GH-116125) --- Lib/email/generator.py | 2 +- Lib/email/message.py | 2 +- Lib/test/test_email/test_email.py | 15 +++++++++++++++ .../2024-02-29-17-06-54.gh-issue-76511.WqjRLP.rst | 4 ++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-29-17-06-54.gh-issue-76511.WqjRLP.rst diff --git a/Lib/email/generator.py b/Lib/email/generator.py index 7ccbe10eb76856..c8056ad47baa0f 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -243,7 +243,7 @@ def _handle_text(self, msg): # existing message. msg = deepcopy(msg) del msg['content-transfer-encoding'] - msg.set_payload(payload, charset) + msg.set_payload(msg._payload, charset) payload = msg.get_payload() self._munge_cte = (msg['content-transfer-encoding'], msg['content-type']) diff --git a/Lib/email/message.py b/Lib/email/message.py index fe769580fed5d0..a14cca56b3745a 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -340,7 +340,7 @@ def set_payload(self, payload, charset=None): return if not isinstance(charset, Charset): charset = Charset(charset) - payload = payload.encode(charset.output_charset) + payload = payload.encode(charset.output_charset, 'surrogateescape') if hasattr(payload, 'decode'): self._payload = payload.decode('ascii', 'surrogateescape') else: diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index 39d4ace8d4a1d8..d9af05c306eb30 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -337,6 +337,21 @@ def test_nonascii_as_string_without_cte(self): msg = email.message_from_bytes(source) self.assertEqual(msg.as_string(), expected) + def test_nonascii_as_string_with_ascii_charset(self): + m = textwrap.dedent("""\ + MIME-Version: 1.0 + Content-type: text/plain; charset="us-ascii" + Content-Transfer-Encoding: 8bit + + Test if non-ascii messages with no Content-Transfer-Encoding set + can be as_string'd: + Föö bär + """) + source = m.encode('iso-8859-1') + expected = source.decode('ascii', 'replace') + msg = email.message_from_bytes(source) + self.assertEqual(msg.as_string(), expected) + def test_nonascii_as_string_without_content_type_and_cte(self): m = textwrap.dedent("""\ MIME-Version: 1.0 diff --git a/Misc/NEWS.d/next/Library/2024-02-29-17-06-54.gh-issue-76511.WqjRLP.rst b/Misc/NEWS.d/next/Library/2024-02-29-17-06-54.gh-issue-76511.WqjRLP.rst new file mode 100644 index 00000000000000..da62f8a2450711 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-29-17-06-54.gh-issue-76511.WqjRLP.rst @@ -0,0 +1,4 @@ +Fix UnicodeEncodeError in :meth:`email.Message.as_string` that results when +a message that claims to be in the ascii character set actually has non-ascii +characters. Non-ascii characters are now replaced with the U+FFFD replacement +character, like in the ``replace`` error handler. From bdba8ef42b15e651dc23374a08143cc2b4c4657d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 5 Mar 2024 17:49:50 +0200 Subject: [PATCH 17/26] gh-74668: Fix support of bytes in urllib.parse.parse_qsl() (GH-115771) urllib.parse functions parse_qs() and parse_qsl() now support bytes arguments containing raw and percent-encoded non-ASCII data. --- Lib/test/test_urlparse.py | 37 +++++++++++++- Lib/urllib/parse.py | 50 ++++++++++--------- ...4-02-21-17-54-59.gh-issue-74668.JT-Q8W.rst | 3 ++ 3 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-21-17-54-59.gh-issue-74668.JT-Q8W.rst diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py index 625c6dc88796b6..72f028680f4cb5 100644 --- a/Lib/test/test_urlparse.py +++ b/Lib/test/test_urlparse.py @@ -19,6 +19,10 @@ ("=a", [('', 'a')]), ("a", [('a', '')]), ("a=", [('a', '')]), + ("a=b=c", [('a', 'b=c')]), + ("a%3Db=c", [('a=b', 'c')]), + ("a=b&c=d", [('a', 'b'), ('c', 'd')]), + ("a=b%26c=d", [('a', 'b&c=d')]), ("&a=b", [('a', 'b')]), ("a=a+b&b=b+c", [('a', 'a b'), ('b', 'b c')]), ("a=1&a=2", [('a', '1'), ('a', '2')]), @@ -29,6 +33,10 @@ (b"=a", [(b'', b'a')]), (b"a", [(b'a', b'')]), (b"a=", [(b'a', b'')]), + (b"a=b=c", [(b'a', b'b=c')]), + (b"a%3Db=c", [(b'a=b', b'c')]), + (b"a=b&c=d", [(b'a', b'b'), (b'c', b'd')]), + (b"a=b%26c=d", [(b'a', b'b&c=d')]), (b"&a=b", [(b'a', b'b')]), (b"a=a+b&b=b+c", [(b'a', b'a b'), (b'b', b'b c')]), (b"a=1&a=2", [(b'a', b'1'), (b'a', b'2')]), @@ -36,6 +44,14 @@ ("a=a+b;b=b+c", [('a', 'a b;b=b c')]), (b";a=b", [(b';a', b'b')]), (b"a=a+b;b=b+c", [(b'a', b'a b;b=b c')]), + + ("\u0141=\xE9", [('\u0141', '\xE9')]), + ("%C5%81=%C3%A9", [('\u0141', '\xE9')]), + ("%81=%A9", [('\ufffd', '\ufffd')]), + (b"\xc5\x81=\xc3\xa9", [(b'\xc5\x81', b'\xc3\xa9')]), + (b"%C5%81=%C3%A9", [(b'\xc5\x81', b'\xc3\xa9')]), + (b"\x81=\xA9", [(b'\x81', b'\xa9')]), + (b"%81=%A9", [(b'\x81', b'\xa9')]), ] # Each parse_qs testcase is a two-tuple that contains @@ -49,6 +65,10 @@ ("=a", {'': ['a']}), ("a", {'a': ['']}), ("a=", {'a': ['']}), + ("a=b=c", {'a': ['b=c']}), + ("a%3Db=c", {'a=b': ['c']}), + ("a=b&c=d", {'a': ['b'], 'c': ['d']}), + ("a=b%26c=d", {'a': ['b&c=d']}), ("&a=b", {'a': ['b']}), ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}), ("a=1&a=2", {'a': ['1', '2']}), @@ -59,6 +79,10 @@ (b"=a", {b'': [b'a']}), (b"a", {b'a': [b'']}), (b"a=", {b'a': [b'']}), + (b"a=b=c", {b'a': [b'b=c']}), + (b"a%3Db=c", {b'a=b': [b'c']}), + (b"a=b&c=d", {b'a': [b'b'], b'c': [b'd']}), + (b"a=b%26c=d", {b'a': [b'b&c=d']}), (b"&a=b", {b'a': [b'b']}), (b"a=a+b&b=b+c", {b'a': [b'a b'], b'b': [b'b c']}), (b"a=1&a=2", {b'a': [b'1', b'2']}), @@ -66,6 +90,15 @@ ("a=a+b;b=b+c", {'a': ['a b;b=b c']}), (b";a=b", {b';a': [b'b']}), (b"a=a+b;b=b+c", {b'a':[ b'a b;b=b c']}), + (b"a=a%E2%80%99b", {b'a': [b'a\xe2\x80\x99b']}), + + ("\u0141=\xE9", {'\u0141': ['\xE9']}), + ("%C5%81=%C3%A9", {'\u0141': ['\xE9']}), + ("%81=%A9", {'\ufffd': ['\ufffd']}), + (b"\xc5\x81=\xc3\xa9", {b'\xc5\x81': [b'\xc3\xa9']}), + (b"%C5%81=%C3%A9", {b'\xc5\x81': [b'\xc3\xa9']}), + (b"\x81=\xA9", {b'\x81': [b'\xa9']}), + (b"%81=%A9", {b'\x81': [b'\xa9']}), ] class UrlParseTestCase(unittest.TestCase): @@ -995,8 +1028,8 @@ def test_parse_qsl_encoding(self): def test_parse_qsl_max_num_fields(self): with self.assertRaises(ValueError): - urllib.parse.parse_qs('&'.join(['a=a']*11), max_num_fields=10) - urllib.parse.parse_qs('&'.join(['a=a']*10), max_num_fields=10) + urllib.parse.parse_qsl('&'.join(['a=a']*11), max_num_fields=10) + urllib.parse.parse_qsl('&'.join(['a=a']*10), max_num_fields=10) def test_parse_qs_separator(self): parse_qs_semicolon_cases = [ diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py index c129b0d7971d71..ec52821125005c 100644 --- a/Lib/urllib/parse.py +++ b/Lib/urllib/parse.py @@ -763,42 +763,44 @@ def parse_qsl(qs, keep_blank_values=False, strict_parsing=False, Returns a list, as G-d intended. """ - qs, _coerce_result = _coerce_args(qs) - separator, _ = _coerce_args(separator) - if not separator or (not isinstance(separator, (str, bytes))): + if not separator or not isinstance(separator, (str, bytes)): raise ValueError("Separator must be of type string or bytes.") + if isinstance(qs, str): + if not isinstance(separator, str): + separator = str(separator, 'ascii') + eq = '=' + def _unquote(s): + return unquote_plus(s, encoding=encoding, errors=errors) + else: + qs = bytes(qs) + if isinstance(separator, str): + separator = bytes(separator, 'ascii') + eq = b'=' + def _unquote(s): + return unquote_to_bytes(s.replace(b'+', b' ')) + + if not qs: + return [] # If max_num_fields is defined then check that the number of fields # is less than max_num_fields. This prevents a memory exhaustion DOS # attack via post bodies with many fields. if max_num_fields is not None: - num_fields = 1 + qs.count(separator) if qs else 0 + num_fields = 1 + qs.count(separator) if max_num_fields < num_fields: raise ValueError('Max number of fields exceeded') r = [] - query_args = qs.split(separator) if qs else [] - for name_value in query_args: - if not name_value and not strict_parsing: - continue - nv = name_value.split('=', 1) - if len(nv) != 2: - if strict_parsing: + for name_value in qs.split(separator): + if name_value or strict_parsing: + name, has_eq, value = name_value.partition(eq) + if not has_eq and strict_parsing: raise ValueError("bad query field: %r" % (name_value,)) - # Handle case of a control-name with no equal sign - if keep_blank_values: - nv.append('') - else: - continue - if len(nv[1]) or keep_blank_values: - name = nv[0].replace('+', ' ') - name = unquote(name, encoding=encoding, errors=errors) - name = _coerce_result(name) - value = nv[1].replace('+', ' ') - value = unquote(value, encoding=encoding, errors=errors) - value = _coerce_result(value) - r.append((name, value)) + if value or keep_blank_values: + name = _unquote(name) + value = _unquote(value) + r.append((name, value)) return r def unquote_plus(string, encoding='utf-8', errors='replace'): diff --git a/Misc/NEWS.d/next/Library/2024-02-21-17-54-59.gh-issue-74668.JT-Q8W.rst b/Misc/NEWS.d/next/Library/2024-02-21-17-54-59.gh-issue-74668.JT-Q8W.rst new file mode 100644 index 00000000000000..f4a6e6d7b51b41 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-21-17-54-59.gh-issue-74668.JT-Q8W.rst @@ -0,0 +1,3 @@ +:mod:`urllib.parse` functions :func:`~urllib.parse.parse_qs` and +:func:`~urllib.parse.parse_qsl` now support bytes arguments containing raw +and percent-encoded non-ASCII data. From 4402b3cbcf8323bfa908ef86a687a5a7d46d27f3 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 5 Mar 2024 08:54:46 -0700 Subject: [PATCH 18/26] gh-76785: Minor Improvements to "interpreters" Module (gh-116328) This includes adding pickle support to various classes, and small changes to improve the maintainability of the low-level _xxinterpqueues module. --- Lib/test/support/interpreters/__init__.py | 8 + Lib/test/support/interpreters/channels.py | 12 +- Lib/test/support/interpreters/queues.py | 31 +-- Lib/test/test_interpreters/test_api.py | 7 + Lib/test/test_interpreters/test_channels.py | 13 + Lib/test/test_interpreters/test_queues.py | 71 +++++- Modules/_interpreters_common.h | 8 + Modules/_xxinterpchannelsmodule.c | 14 +- Modules/_xxinterpqueuesmodule.c | 261 +++++++++++++++----- 9 files changed, 337 insertions(+), 88 deletions(-) diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py index d02ffbae1113c0..d8e6654fc96efd 100644 --- a/Lib/test/support/interpreters/__init__.py +++ b/Lib/test/support/interpreters/__init__.py @@ -129,6 +129,14 @@ def __hash__(self): def __del__(self): self._decref() + # for pickling: + def __getnewargs__(self): + return (self._id,) + + # for pickling: + def __getstate__(self): + return None + def _decref(self): if not self._ownsref: return diff --git a/Lib/test/support/interpreters/channels.py b/Lib/test/support/interpreters/channels.py index 75a5a60f54f926..f7f523b1fc5a77 100644 --- a/Lib/test/support/interpreters/channels.py +++ b/Lib/test/support/interpreters/channels.py @@ -38,7 +38,8 @@ class _ChannelEnd: _end = None - def __init__(self, cid): + def __new__(cls, cid): + self = super().__new__(cls) if self._end == 'send': cid = _channels._channel_id(cid, send=True, force=True) elif self._end == 'recv': @@ -46,6 +47,7 @@ def __init__(self, cid): else: raise NotImplementedError(self._end) self._id = cid + return self def __repr__(self): return f'{type(self).__name__}(id={int(self._id)})' @@ -61,6 +63,14 @@ def __eq__(self, other): return NotImplemented return other._id == self._id + # for pickling: + def __getnewargs__(self): + return (int(self._id),) + + # for pickling: + def __getstate__(self): + return None + @property def id(self): return self._id diff --git a/Lib/test/support/interpreters/queues.py b/Lib/test/support/interpreters/queues.py index f9978f0bec5a62..5849a1cc15e447 100644 --- a/Lib/test/support/interpreters/queues.py +++ b/Lib/test/support/interpreters/queues.py @@ -18,14 +18,14 @@ ] -class QueueEmpty(_queues.QueueEmpty, queue.Empty): +class QueueEmpty(QueueError, queue.Empty): """Raised from get_nowait() when the queue is empty. It is also raised from get() if it times out. """ -class QueueFull(_queues.QueueFull, queue.Full): +class QueueFull(QueueError, queue.Full): """Raised from put_nowait() when the queue is full. It is also raised from put() if it times out. @@ -66,7 +66,7 @@ def __new__(cls, id, /, *, _fmt=None): else: raise TypeError(f'id must be an int, got {id!r}') if _fmt is None: - _fmt = _queues.get_default_fmt(id) + _fmt, = _queues.get_queue_defaults(id) try: self = _known_queues[id] except KeyError: @@ -93,6 +93,14 @@ def __repr__(self): def __hash__(self): return hash(self._id) + # for pickling: + def __getnewargs__(self): + return (self._id,) + + # for pickling: + def __getstate__(self): + return None + @property def id(self): return self._id @@ -159,9 +167,8 @@ def put(self, obj, timeout=None, *, while True: try: _queues.put(self._id, obj, fmt) - except _queues.QueueFull as exc: + except QueueFull as exc: if timeout is not None and time.time() >= end: - exc.__class__ = QueueFull raise # re-raise time.sleep(_delay) else: @@ -174,11 +181,7 @@ def put_nowait(self, obj, *, syncobj=None): fmt = _SHARED_ONLY if syncobj else _PICKLED if fmt is _PICKLED: obj = pickle.dumps(obj) - try: - _queues.put(self._id, obj, fmt) - except _queues.QueueFull as exc: - exc.__class__ = QueueFull - raise # re-raise + _queues.put(self._id, obj, fmt) def get(self, timeout=None, *, _delay=10 / 1000, # 10 milliseconds @@ -195,9 +198,8 @@ def get(self, timeout=None, *, while True: try: obj, fmt = _queues.get(self._id) - except _queues.QueueEmpty as exc: + except QueueEmpty as exc: if timeout is not None and time.time() >= end: - exc.__class__ = QueueEmpty raise # re-raise time.sleep(_delay) else: @@ -216,8 +218,7 @@ def get_nowait(self): """ try: obj, fmt = _queues.get(self._id) - except _queues.QueueEmpty as exc: - exc.__class__ = QueueEmpty + except QueueEmpty as exc: raise # re-raise if fmt == _PICKLED: obj = pickle.loads(obj) @@ -226,4 +227,4 @@ def get_nowait(self): return obj -_queues._register_queue_type(Queue) +_queues._register_heap_types(Queue, QueueEmpty, QueueFull) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 363143fa810f35..3cde9bd0014d9a 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1,4 +1,5 @@ import os +import pickle import threading from textwrap import dedent import unittest @@ -261,6 +262,12 @@ def test_equality(self): self.assertEqual(interp1, interp1) self.assertNotEqual(interp1, interp2) + def test_pickle(self): + interp = interpreters.create() + data = pickle.dumps(interp) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, interp) + class TestInterpreterIsRunning(TestBase): diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py index 57204e2776468d..7e0b82884c33d3 100644 --- a/Lib/test/test_interpreters/test_channels.py +++ b/Lib/test/test_interpreters/test_channels.py @@ -1,4 +1,5 @@ import importlib +import pickle import threading from textwrap import dedent import unittest @@ -100,6 +101,12 @@ def test_equality(self): self.assertEqual(ch1, ch1) self.assertNotEqual(ch1, ch2) + def test_pickle(self): + ch, _ = channels.create() + data = pickle.dumps(ch) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, ch) + class TestSendChannelAttrs(TestBase): @@ -125,6 +132,12 @@ def test_equality(self): self.assertEqual(ch1, ch1) self.assertNotEqual(ch1, ch2) + def test_pickle(self): + _, ch = channels.create() + data = pickle.dumps(ch) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, ch) + class TestSendRecv(TestBase): diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index 0a1fdb41f73166..d16d294b82d044 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -1,20 +1,25 @@ import importlib +import pickle import threading from textwrap import dedent import unittest import time -from test.support import import_helper +from test.support import import_helper, Py_DEBUG # Raise SkipTest if subinterpreters not supported. _queues = import_helper.import_module('_xxinterpqueues') from test.support import interpreters from test.support.interpreters import queues -from .utils import _run_output, TestBase +from .utils import _run_output, TestBase as _TestBase -class TestBase(TestBase): +def get_num_queues(): + return len(_queues.list_all()) + + +class TestBase(_TestBase): def tearDown(self): - for qid in _queues.list_all(): + for qid, _ in _queues.list_all(): try: _queues.destroy(qid) except Exception: @@ -34,6 +39,58 @@ def test_highlevel_reloaded(self): # See gh-115490 (https://github.com/python/cpython/issues/115490). importlib.reload(queues) + def test_create_destroy(self): + qid = _queues.create(2, 0) + _queues.destroy(qid) + self.assertEqual(get_num_queues(), 0) + with self.assertRaises(queues.QueueNotFoundError): + _queues.get(qid) + with self.assertRaises(queues.QueueNotFoundError): + _queues.destroy(qid) + + def test_not_destroyed(self): + # It should have cleaned up any remaining queues. + stdout, stderr = self.assert_python_ok( + '-c', + dedent(f""" + import {_queues.__name__} as _queues + _queues.create(2, 0) + """), + ) + self.assertEqual(stdout, '') + if Py_DEBUG: + self.assertNotEqual(stderr, '') + else: + self.assertEqual(stderr, '') + + def test_bind_release(self): + with self.subTest('typical'): + qid = _queues.create(2, 0) + _queues.bind(qid) + _queues.release(qid) + self.assertEqual(get_num_queues(), 0) + + with self.subTest('bind too much'): + qid = _queues.create(2, 0) + _queues.bind(qid) + _queues.bind(qid) + _queues.release(qid) + _queues.destroy(qid) + self.assertEqual(get_num_queues(), 0) + + with self.subTest('nested'): + qid = _queues.create(2, 0) + _queues.bind(qid) + _queues.bind(qid) + _queues.release(qid) + _queues.release(qid) + self.assertEqual(get_num_queues(), 0) + + with self.subTest('release without binding'): + qid = _queues.create(2, 0) + with self.assertRaises(queues.QueueError): + _queues.release(qid) + class QueueTests(TestBase): @@ -127,6 +184,12 @@ def test_equality(self): self.assertEqual(queue1, queue1) self.assertNotEqual(queue1, queue2) + def test_pickle(self): + queue = queues.create() + data = pickle.dumps(queue) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, queue) + class TestQueueOps(TestBase): diff --git a/Modules/_interpreters_common.h b/Modules/_interpreters_common.h index 5661a26d8790d1..07120f6ccc7207 100644 --- a/Modules/_interpreters_common.h +++ b/Modules/_interpreters_common.h @@ -11,3 +11,11 @@ ensure_xid_class(PyTypeObject *cls, crossinterpdatafunc getdata) //assert(cls->tp_flags & Py_TPFLAGS_HEAPTYPE); return _PyCrossInterpreterData_RegisterClass(cls, getdata); } + +#ifdef REGISTERS_HEAP_TYPES +static int +clear_xid_class(PyTypeObject *cls) +{ + return _PyCrossInterpreterData_UnregisterClass(cls); +} +#endif diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 0ad184a78e8c1a..28ec00a159d6cd 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -17,7 +17,9 @@ #include // sched_yield() #endif +#define REGISTERS_HEAP_TYPES #include "_interpreters_common.h" +#undef REGISTERS_HEAP_TYPES /* @@ -281,17 +283,17 @@ clear_xid_types(module_state *state) { /* external types */ if (state->send_channel_type != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->send_channel_type); + (void)clear_xid_class(state->send_channel_type); Py_CLEAR(state->send_channel_type); } if (state->recv_channel_type != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->recv_channel_type); + (void)clear_xid_class(state->recv_channel_type); Py_CLEAR(state->recv_channel_type); } /* heap types */ if (state->ChannelIDType != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->ChannelIDType); + (void)clear_xid_class(state->ChannelIDType); Py_CLEAR(state->ChannelIDType); } } @@ -2677,11 +2679,11 @@ set_channelend_types(PyObject *mod, PyTypeObject *send, PyTypeObject *recv) // Clear the old values if the .py module was reloaded. if (state->send_channel_type != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->send_channel_type); + (void)clear_xid_class(state->send_channel_type); Py_CLEAR(state->send_channel_type); } if (state->recv_channel_type != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->recv_channel_type); + (void)clear_xid_class(state->recv_channel_type); Py_CLEAR(state->recv_channel_type); } @@ -2694,7 +2696,7 @@ set_channelend_types(PyObject *mod, PyTypeObject *send, PyTypeObject *recv) return -1; } if (ensure_xid_class(recv, _channelend_shared) < 0) { - (void)_PyCrossInterpreterData_UnregisterClass(state->send_channel_type); + (void)clear_xid_class(state->send_channel_type); Py_CLEAR(state->send_channel_type); Py_CLEAR(state->recv_channel_type); return -1; diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index 1b76b6963ae0f1..cb8b9e4a661d5a 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -8,7 +8,9 @@ #include "Python.h" #include "pycore_crossinterp.h" // struct _xid +#define REGISTERS_HEAP_TYPES #include "_interpreters_common.h" +#undef REGISTERS_HEAP_TYPES #define MODULE_NAME _xxinterpqueues @@ -128,6 +130,22 @@ idarg_int64_converter(PyObject *arg, void *ptr) } +static int +ensure_highlevel_module_loaded(void) +{ + PyObject *highlevel = PyImport_ImportModule("interpreters.queues"); + if (highlevel == NULL) { + PyErr_Clear(); + highlevel = PyImport_ImportModule("test.support.interpreters.queues"); + if (highlevel == NULL) { + return -1; + } + } + Py_DECREF(highlevel); + return 0; +} + + /* module state *************************************************************/ typedef struct { @@ -170,7 +188,7 @@ clear_module_state(module_state *state) { /* external types */ if (state->queue_type != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->queue_type); + (void)clear_xid_class(state->queue_type); } Py_CLEAR(state->queue_type); @@ -195,6 +213,9 @@ clear_module_state(module_state *state) // single-queue errors #define ERR_QUEUE_EMPTY (-21) #define ERR_QUEUE_FULL (-22) +#define ERR_QUEUE_NEVER_BOUND (-23) + +static int ensure_external_exc_types(module_state *); static int resolve_module_errcode(module_state *state, int errcode, int64_t qid, @@ -212,13 +233,23 @@ resolve_module_errcode(module_state *state, int errcode, int64_t qid, msg = PyUnicode_FromFormat("queue %" PRId64 " not found", qid); break; case ERR_QUEUE_EMPTY: + if (ensure_external_exc_types(state) < 0) { + return -1; + } exctype = state->QueueEmpty; msg = PyUnicode_FromFormat("queue %" PRId64 " is empty", qid); break; case ERR_QUEUE_FULL: + if (ensure_external_exc_types(state) < 0) { + return -1; + } exctype = state->QueueFull; msg = PyUnicode_FromFormat("queue %" PRId64 " is full", qid); break; + case ERR_QUEUE_NEVER_BOUND: + exctype = state->QueueError; + msg = PyUnicode_FromFormat("queue %" PRId64 " never bound", qid); + break; default: PyErr_Format(PyExc_ValueError, "unsupported error code %d", errcode); @@ -267,20 +298,59 @@ add_QueueError(PyObject *mod) #define PREFIX "test.support.interpreters." #define ADD_EXCTYPE(NAME, BASE, DOC) \ + assert(state->NAME == NULL); \ if (add_exctype(mod, &state->NAME, PREFIX #NAME, DOC, BASE) < 0) { \ return -1; \ } ADD_EXCTYPE(QueueError, PyExc_RuntimeError, "Indicates that a queue-related error happened.") ADD_EXCTYPE(QueueNotFoundError, state->QueueError, NULL) - ADD_EXCTYPE(QueueEmpty, state->QueueError, NULL) - ADD_EXCTYPE(QueueFull, state->QueueError, NULL) + // QueueEmpty and QueueFull are set by set_external_exc_types(). + state->QueueEmpty = NULL; + state->QueueFull = NULL; #undef ADD_EXCTYPE #undef PREFIX return 0; } +static int +set_external_exc_types(module_state *state, + PyObject *emptyerror, PyObject *fullerror) +{ + if (state->QueueEmpty != NULL) { + assert(state->QueueFull != NULL); + Py_CLEAR(state->QueueEmpty); + Py_CLEAR(state->QueueFull); + } + else { + assert(state->QueueFull == NULL); + } + assert(PyObject_IsSubclass(emptyerror, state->QueueError)); + assert(PyObject_IsSubclass(fullerror, state->QueueError)); + state->QueueEmpty = Py_NewRef(emptyerror); + state->QueueFull = Py_NewRef(fullerror); + return 0; +} + +static int +ensure_external_exc_types(module_state *state) +{ + if (state->QueueEmpty != NULL) { + assert(state->QueueFull != NULL); + return 0; + } + assert(state->QueueFull == NULL); + + // Force the module to be loaded, to register the type. + if (ensure_highlevel_module_loaded() < 0) { + return -1; + } + assert(state->QueueEmpty != NULL); + assert(state->QueueFull != NULL); + return 0; +} + static int handle_queue_error(int err, PyObject *mod, int64_t qid) { @@ -393,6 +463,7 @@ _queueitem_popped(_queueitem *item, /* the queue */ + typedef struct _queue { Py_ssize_t num_waiters; // protected by global lock PyThread_type_lock mutex; @@ -435,6 +506,8 @@ _queue_clear(_queue *queue) *queue = (_queue){0}; } +static void _queue_free(_queue *); + static void _queue_kill_and_wait(_queue *queue) { @@ -667,6 +740,32 @@ _queuerefs_find(_queueref *first, int64_t qid, _queueref **pprev) return ref; } +static void +_queuerefs_clear(_queueref *head) +{ + _queueref *next = head; + while (next != NULL) { + _queueref *ref = next; + next = ref->next; + +#ifdef Py_DEBUG + int64_t qid = ref->qid; + fprintf(stderr, "queue %ld still exists\n", qid); +#endif + _queue *queue = ref->queue; + GLOBAL_FREE(ref); + + _queue_kill_and_wait(queue); +#ifdef Py_DEBUG + if (queue->items.count > 0) { + fprintf(stderr, "queue %ld still holds %ld items\n", + qid, queue->items.count); + } +#endif + _queue_free(queue); + } +} + /* a collection of queues ***************************************************/ @@ -689,8 +788,15 @@ _queues_init(_queues *queues, PyThread_type_lock mutex) static void _queues_fini(_queues *queues) { - assert(queues->count == 0); - assert(queues->head == NULL); + if (queues->count > 0) { + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + assert((queues->count == 0) != (queues->head != NULL)); + _queueref *head = queues->head; + queues->head = NULL; + queues->count = 0; + PyThread_release_lock(queues->mutex); + _queuerefs_clear(head); + } if (queues->mutex != NULL) { PyThread_free_lock(queues->mutex); queues->mutex = NULL; @@ -822,19 +928,21 @@ _queues_incref(_queues *queues, int64_t qid) return res; } -static void _queue_free(_queue *); - -static void +static int _queues_decref(_queues *queues, int64_t qid) { + int res = -1; PyThread_acquire_lock(queues->mutex, WAIT_LOCK); _queueref *prev = NULL; _queueref *ref = _queuerefs_find(queues->head, qid, &prev); if (ref == NULL) { assert(!PyErr_Occurred()); - // Already destroyed. - // XXX Warn? + res = ERR_QUEUE_NOT_FOUND; + goto finally; + } + if (ref->refcount == 0) { + res = ERR_QUEUE_NEVER_BOUND; goto finally; } assert(ref->refcount > 0); @@ -849,11 +957,13 @@ _queues_decref(_queues *queues, int64_t qid) _queue_kill_and_wait(queue); _queue_free(queue); - return; + return 0; } + res = 0; finally: PyThread_release_lock(queues->mutex); + return res; } struct queue_id_and_fmt { @@ -1077,14 +1187,11 @@ static int _queueobj_shared(PyThreadState *, PyObject *, _PyCrossInterpreterData *); static int -set_external_queue_type(PyObject *module, PyTypeObject *queue_type) +set_external_queue_type(module_state *state, PyTypeObject *queue_type) { - module_state *state = get_module_state(module); - // Clear the old value if the .py module was reloaded. if (state->queue_type != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass( - state->queue_type); + (void)clear_xid_class(state->queue_type); Py_CLEAR(state->queue_type); } @@ -1105,15 +1212,9 @@ get_external_queue_type(PyObject *module) PyTypeObject *cls = state->queue_type; if (cls == NULL) { // Force the module to be loaded, to register the type. - PyObject *highlevel = PyImport_ImportModule("interpreters.queue"); - if (highlevel == NULL) { - PyErr_Clear(); - highlevel = PyImport_ImportModule("test.support.interpreters.queue"); - if (highlevel == NULL) { - return NULL; - } + if (ensure_highlevel_module_loaded() < 0) { + return NULL; } - Py_DECREF(highlevel); cls = state->queue_type; assert(cls != NULL); } @@ -1152,7 +1253,14 @@ _queueid_xid_free(void *data) int64_t qid = ((struct _queueid_xid *)data)->qid; PyMem_RawFree(data); _queues *queues = _get_global_queues(); - _queues_decref(queues, qid); + int res = _queues_decref(queues, qid); + if (res == ERR_QUEUE_NOT_FOUND) { + // Already destroyed. + // XXX Warn? + } + else { + assert(res == 0); + } } static PyObject * @@ -1319,10 +1427,13 @@ queuesmod_create(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(queuesmod_create_doc, -"create() -> qid\n\ +"create(maxsize, fmt) -> qid\n\ \n\ Create a new cross-interpreter queue and return its unique generated ID.\n\ -It is a new reference as though bind() had been called on the queue."); +It is a new reference as though bind() had been called on the queue.\n\ +\n\ +The caller is responsible for calling destroy() for the new queue\n\ +before the runtime is finalized."); static PyObject * queuesmod_destroy(PyObject *self, PyObject *args, PyObject *kwds) @@ -1379,9 +1490,10 @@ queuesmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(queuesmod_list_all_doc, -"list_all() -> [qid]\n\ +"list_all() -> [(qid, fmt)]\n\ \n\ -Return the list of IDs for all queues."); +Return the list of IDs for all queues.\n\ +Each corresponding default format is also included."); static PyObject * queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds) @@ -1398,6 +1510,7 @@ queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds) /* Queue up the object. */ int err = queue_put(&_globals.queues, qid, obj, fmt); + // This is the only place that raises QueueFull. if (handle_queue_error(err, self, qid)) { return NULL; } @@ -1406,18 +1519,17 @@ queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(queuesmod_put_doc, -"put(qid, obj, sharedonly=False)\n\ +"put(qid, obj, fmt)\n\ \n\ Add the object's data to the queue."); static PyObject * queuesmod_get(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"qid", "default", NULL}; + static char *kwlist[] = {"qid", NULL}; qidarg_converter_data qidarg; - PyObject *dflt = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O:get", kwlist, - qidarg_converter, &qidarg, &dflt)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:get", kwlist, + qidarg_converter, &qidarg)) { return NULL; } int64_t qid = qidarg.id; @@ -1425,11 +1537,8 @@ queuesmod_get(PyObject *self, PyObject *args, PyObject *kwds) PyObject *obj = NULL; int fmt = 0; int err = queue_get(&_globals.queues, qid, &obj, &fmt); - if (err == ERR_QUEUE_EMPTY && dflt != NULL) { - assert(obj == NULL); - obj = Py_NewRef(dflt); - } - else if (handle_queue_error(err, self, qid)) { + // This is the only place that raises QueueEmpty. + if (handle_queue_error(err, self, qid)) { return NULL; } @@ -1439,12 +1548,12 @@ queuesmod_get(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(queuesmod_get_doc, -"get(qid, [default]) -> obj\n\ +"get(qid) -> (obj, fmt)\n\ \n\ Return a new object from the data at the front of the queue.\n\ +The object's format is also returned.\n\ \n\ -If there is nothing to receive then raise QueueEmpty, unless\n\ -a default value is provided. In that case return it."); +If there is nothing to receive then raise QueueEmpty."); static PyObject * queuesmod_bind(PyObject *self, PyObject *args, PyObject *kwds) @@ -1491,7 +1600,10 @@ queuesmod_release(PyObject *self, PyObject *args, PyObject *kwds) // XXX Check module state if bound already. // XXX Update module state. - _queues_decref(&_globals.queues, qid); + int err = _queues_decref(&_globals.queues, qid); + if (handle_queue_error(err, self, qid)) { + return NULL; + } Py_RETURN_NONE; } @@ -1528,12 +1640,12 @@ PyDoc_STRVAR(queuesmod_get_maxsize_doc, Return the maximum number of items in the queue."); static PyObject * -queuesmod_get_default_fmt(PyObject *self, PyObject *args, PyObject *kwds) +queuesmod_get_queue_defaults(PyObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"qid", NULL}; qidarg_converter_data qidarg; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "O&:get_default_fmt", kwlist, + "O&:get_queue_defaults", kwlist, qidarg_converter, &qidarg)) { return NULL; } @@ -1546,13 +1658,21 @@ queuesmod_get_default_fmt(PyObject *self, PyObject *args, PyObject *kwds) } int fmt = queue->fmt; _queue_unmark_waiter(queue, _globals.queues.mutex); - return PyLong_FromLong(fmt); + + PyObject *fmt_obj = PyLong_FromLong(fmt); + if (fmt_obj == NULL) { + return NULL; + } + // For now queues only have one default. + PyObject *res = PyTuple_Pack(1, fmt_obj); + Py_DECREF(fmt_obj); + return res; } -PyDoc_STRVAR(queuesmod_get_default_fmt_doc, -"get_default_fmt(qid)\n\ +PyDoc_STRVAR(queuesmod_get_queue_defaults_doc, +"get_queue_defaults(qid)\n\ \n\ -Return the default format to use for the queue."); +Return the queue's default values, set when it was created."); static PyObject * queuesmod_is_full(PyObject *self, PyObject *args, PyObject *kwds) @@ -1609,22 +1729,39 @@ PyDoc_STRVAR(queuesmod_get_count_doc, Return the number of items in the queue."); static PyObject * -queuesmod__register_queue_type(PyObject *self, PyObject *args, PyObject *kwds) +queuesmod__register_heap_types(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"queuetype", NULL}; + static char *kwlist[] = {"queuetype", "emptyerror", "fullerror", NULL}; PyObject *queuetype; + PyObject *emptyerror; + PyObject *fullerror; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "O:_register_queue_type", kwlist, - &queuetype)) { + "OOO:_register_heap_types", kwlist, + &queuetype, &emptyerror, &fullerror)) { return NULL; } if (!PyType_Check(queuetype)) { - PyErr_SetString(PyExc_TypeError, "expected a type for 'queuetype'"); + PyErr_SetString(PyExc_TypeError, + "expected a type for 'queuetype'"); + return NULL; + } + if (!PyExceptionClass_Check(emptyerror)) { + PyErr_SetString(PyExc_TypeError, + "expected an exception type for 'emptyerror'"); + return NULL; + } + if (!PyExceptionClass_Check(fullerror)) { + PyErr_SetString(PyExc_TypeError, + "expected an exception type for 'fullerror'"); return NULL; } - PyTypeObject *cls_queue = (PyTypeObject *)queuetype; - if (set_external_queue_type(self, cls_queue) < 0) { + module_state *state = get_module_state(self); + + if (set_external_queue_type(state, (PyTypeObject *)queuetype) < 0) { + return NULL; + } + if (set_external_exc_types(state, emptyerror, fullerror) < 0) { return NULL; } @@ -1638,23 +1775,23 @@ static PyMethodDef module_functions[] = { METH_VARARGS | METH_KEYWORDS, queuesmod_destroy_doc}, {"list_all", queuesmod_list_all, METH_NOARGS, queuesmod_list_all_doc}, - {"put", _PyCFunction_CAST(queuesmod_put), + {"put", _PyCFunction_CAST(queuesmod_put), METH_VARARGS | METH_KEYWORDS, queuesmod_put_doc}, - {"get", _PyCFunction_CAST(queuesmod_get), + {"get", _PyCFunction_CAST(queuesmod_get), METH_VARARGS | METH_KEYWORDS, queuesmod_get_doc}, - {"bind", _PyCFunction_CAST(queuesmod_bind), + {"bind", _PyCFunction_CAST(queuesmod_bind), METH_VARARGS | METH_KEYWORDS, queuesmod_bind_doc}, {"release", _PyCFunction_CAST(queuesmod_release), METH_VARARGS | METH_KEYWORDS, queuesmod_release_doc}, {"get_maxsize", _PyCFunction_CAST(queuesmod_get_maxsize), METH_VARARGS | METH_KEYWORDS, queuesmod_get_maxsize_doc}, - {"get_default_fmt", _PyCFunction_CAST(queuesmod_get_default_fmt), - METH_VARARGS | METH_KEYWORDS, queuesmod_get_default_fmt_doc}, + {"get_queue_defaults", _PyCFunction_CAST(queuesmod_get_queue_defaults), + METH_VARARGS | METH_KEYWORDS, queuesmod_get_queue_defaults_doc}, {"is_full", _PyCFunction_CAST(queuesmod_is_full), METH_VARARGS | METH_KEYWORDS, queuesmod_is_full_doc}, {"get_count", _PyCFunction_CAST(queuesmod_get_count), METH_VARARGS | METH_KEYWORDS, queuesmod_get_count_doc}, - {"_register_queue_type", _PyCFunction_CAST(queuesmod__register_queue_type), + {"_register_heap_types", _PyCFunction_CAST(queuesmod__register_heap_types), METH_VARARGS | METH_KEYWORDS, NULL}, {NULL, NULL} /* sentinel */ From e7ba6e9dbe5433b4a0bcb0658da6a68197c28630 Mon Sep 17 00:00:00 2001 From: cui fliter Date: Wed, 6 Mar 2024 00:05:52 +0800 Subject: [PATCH 19/26] chore: fix typos (#116345) Signed-off-by: cui fliter --- Doc/library/idle.rst | 2 +- Include/cpython/code.h | 2 +- Include/internal/pycore_compile.h | 2 +- Include/internal/pycore_gc.h | 2 +- Include/internal/pycore_instruments.h | 2 +- Include/internal/pycore_interp.h | 2 +- Include/internal/pycore_lock.h | 4 ++-- Lib/asyncio/tasks.py | 2 +- Lib/importlib/util.py | 2 +- Lib/test/libregrtest/run_workers.py | 2 +- Lib/test/libregrtest/utils.py | 2 +- Lib/test/test_asyncio/test_waitfor.py | 4 ++-- Lib/test/test_baseexception.py | 2 +- Objects/mimalloc/prim/windows/etw.h | 2 +- Tools/build/stable_abi.py | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Doc/library/idle.rst b/Doc/library/idle.rst index 249dc0ea6ba735..17a5144b4c0635 100644 --- a/Doc/library/idle.rst +++ b/Doc/library/idle.rst @@ -604,7 +604,7 @@ in an editor window. The editing features described in previous subsections work when entering code interactively. IDLE's Shell window also responds to the following: -* :kbd:`C-c` attemps to interrupt statement execution (but may fail). +* :kbd:`C-c` attempts to interrupt statement execution (but may fail). * :kbd:`C-d` closes Shell if typed at a ``>>>`` prompt. diff --git a/Include/cpython/code.h b/Include/cpython/code.h index 1f47d99fb60443..4e7e212fd099db 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -73,7 +73,7 @@ typedef struct { PyObject *_co_freevars; } _PyCoCached; -/* Ancilliary data structure used for instrumentation. +/* Ancillary data structure used for instrumentation. Line instrumentation creates an array of these. One entry per code unit.*/ typedef struct { diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h index e5870759ba74f1..0f446a00b4df22 100644 --- a/Include/internal/pycore_compile.h +++ b/Include/internal/pycore_compile.h @@ -103,7 +103,7 @@ int _PyCompile_EnsureArrayLargeEnough( int _PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj); -// Export for '_opcode' extention module +// Export for '_opcode' extension module PyAPI_FUNC(int) _PyCompile_OpcodeIsValid(int opcode); PyAPI_FUNC(int) _PyCompile_OpcodeHasArg(int opcode); PyAPI_FUNC(int) _PyCompile_OpcodeHasConst(int opcode); diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index 40414a868518bb..cf0b148c2b6463 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -73,7 +73,7 @@ static inline int _PyObject_GC_MAY_BE_TRACKED(PyObject *obj) { /* True if an object is shared between multiple threads and * needs special purpose when freeing to do the possibility - * of in-flight lock-free reads occuring */ + * of in-flight lock-free reads occurring */ static inline int _PyObject_GC_IS_SHARED(PyObject *op) { return (op->ob_gc_bits & _PyGC_BITS_SHARED) != 0; } diff --git a/Include/internal/pycore_instruments.h b/Include/internal/pycore_instruments.h index eae8371ef7f9b8..7f84d4a763bbcf 100644 --- a/Include/internal/pycore_instruments.h +++ b/Include/internal/pycore_instruments.h @@ -39,7 +39,7 @@ extern "C" { #define PY_MONITORING_EVENT_RERAISE 14 -/* Ancilliary events */ +/* Ancillary events */ #define PY_MONITORING_EVENT_C_RETURN 15 #define PY_MONITORING_EVENT_C_RAISE 16 diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 6a00aafea73779..d79fd3b6039ef5 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -85,7 +85,7 @@ typedef struct _rare_events { */ struct _is { - /* This struct countains the eval_breaker, + /* This struct contains the eval_breaker, * which is by far the hottest field in this struct * and should be placed at the beginning. */ struct _ceval_state ceval; diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index f648be496ea4af..971b4611a87f3b 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -271,7 +271,7 @@ PyAPI_FUNC(void) _PyRWMutex_Unlock(_PyRWMutex *rwmutex); // underlying data and then read the sequence number again after reading the data. If the // sequence has not changed the data is valid. // -// Differs a little bit in that we use CAS on sequence as the lock, instead of a seperate spin lock. +// Differs a little bit in that we use CAS on sequence as the lock, instead of a separate spin lock. // The writer can also detect that the undelering data has not changed and abandon the write // and restore the previous sequence. typedef struct { @@ -284,7 +284,7 @@ PyAPI_FUNC(void) _PySeqLock_LockWrite(_PySeqLock *seqlock); // Unlock the sequence lock and move to the next sequence number. PyAPI_FUNC(void) _PySeqLock_UnlockWrite(_PySeqLock *seqlock); -// Abandon the current update indicating that no mutations have occured +// Abandon the current update indicating that no mutations have occurred // and restore the previous sequence value. PyAPI_FUNC(void) _PySeqLock_AbandonWrite(_PySeqLock *seqlock); diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index fafee3e738f6aa..48e31af9a43167 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -464,7 +464,7 @@ async def wait_for(fut, timeout): If the wait is cancelled, the task is also cancelled. - If the task supresses the cancellation and returns a value instead, + If the task suppresses the cancellation and returns a value instead, that value is returned. This function is a coroutine. diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index ff4f12fb1af70c..da9bd080a8dd5a 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -146,7 +146,7 @@ class _incompatible_extension_module_restrictions: You can get the same effect as this function by implementing the basic interface of multi-phase init (PEP 489) and lying about - support for mulitple interpreters (or per-interpreter GIL). + support for multiple interpreters (or per-interpreter GIL). """ def __init__(self, *, disable_check): diff --git a/Lib/test/libregrtest/run_workers.py b/Lib/test/libregrtest/run_workers.py index 18a0342f0611cf..9cfe1b9d6fd07d 100644 --- a/Lib/test/libregrtest/run_workers.py +++ b/Lib/test/libregrtest/run_workers.py @@ -209,7 +209,7 @@ def _run_process(self, runtests: WorkerRunTests, output_fd: int, self._popen = None def create_stdout(self, stack: contextlib.ExitStack) -> TextIO: - """Create stdout temporay file (file descriptor).""" + """Create stdout temporary file (file descriptor).""" if MS_WINDOWS: # gh-95027: When stdout is not a TTY, Python uses the ANSI code diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index b30025d962413c..7765ae8a933be4 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -422,7 +422,7 @@ def get_work_dir(parent_dir: StrPath, worker: bool = False) -> StrPath: # the tests. The name of the dir includes the pid to allow parallel # testing (see the -j option). # Emscripten and WASI have stubbed getpid(), Emscripten has only - # milisecond clock resolution. Use randint() instead. + # millisecond clock resolution. Use randint() instead. if support.is_emscripten or support.is_wasi: nounce = random.randint(0, 1_000_000) else: diff --git a/Lib/test/test_asyncio/test_waitfor.py b/Lib/test/test_asyncio/test_waitfor.py index d52f32534a0cfe..11a8eeeab37634 100644 --- a/Lib/test/test_asyncio/test_waitfor.py +++ b/Lib/test/test_asyncio/test_waitfor.py @@ -249,8 +249,8 @@ async def test_cancel_wait_for(self): await self._test_cancel_wait_for(60.0) async def test_wait_for_cancel_suppressed(self): - # GH-86296: Supressing CancelledError is discouraged - # but if a task subpresses CancelledError and returns a value, + # GH-86296: Suppressing CancelledError is discouraged + # but if a task suppresses CancelledError and returns a value, # `wait_for` should return the value instead of raising CancelledError. # This is the same behavior as `asyncio.timeout`. diff --git a/Lib/test/test_baseexception.py b/Lib/test/test_baseexception.py index 4c3cf0b964ae56..6dc06c5e4bc9d5 100644 --- a/Lib/test/test_baseexception.py +++ b/Lib/test/test_baseexception.py @@ -129,7 +129,7 @@ class Value(str): d[HashThisKeyWillClearTheDict()] = Value() # refcount of Value() is 1 now - # Exception.__setstate__ should aquire a strong reference of key and + # Exception.__setstate__ should acquire a strong reference of key and # value in the dict. Otherwise, Value()'s refcount would go below # zero in the tp_hash call in PyObject_SetAttr(), and it would cause # crash in GC. diff --git a/Objects/mimalloc/prim/windows/etw.h b/Objects/mimalloc/prim/windows/etw.h index 4e0a092a10f4ba..e9ec35fc9ed7eb 100644 --- a/Objects/mimalloc/prim/windows/etw.h +++ b/Objects/mimalloc/prim/windows/etw.h @@ -136,7 +136,7 @@ extern "C" { // - MCGEN_EVENTSETINFORMATION // - MCGEN_EVENTWRITETRANSFER // -// If the the macro is undefined, the MC implementation will default to the +// If the macro is undefined, the MC implementation will default to the // corresponding ETW APIs. For example, if the MCGEN_EVENTREGISTER macro is // undefined, the EventRegister[MyProviderName] macro will use EventRegister // in user mode and will use EtwRegister in kernel mode. diff --git a/Tools/build/stable_abi.py b/Tools/build/stable_abi.py index 83146622c74f94..95fc4dfbf11e49 100644 --- a/Tools/build/stable_abi.py +++ b/Tools/build/stable_abi.py @@ -601,7 +601,7 @@ def check_private_names(manifest): if name.startswith('_') and not item.abi_only: raise ValueError( f'`{name}` is private (underscore-prefixed) and should be ' - + 'removed from the stable ABI list or or marked `abi_only`') + + 'removed from the stable ABI list or marked `abi_only`') def check_dump(manifest, filename): """Check that manifest.dump() corresponds to the data. From d444dec09ae05150f0d339a060fca23990d4834e Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 5 Mar 2024 10:05:29 -0800 Subject: [PATCH 20/26] Fix debug output for optimized executor (#116337) This adjusts `length` rather than using `length+1` all over the place. --- Python/optimizer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index acd6d52c4a885f..4dbfd918a51f62 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -899,7 +899,8 @@ make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *depende uint32_t used[(UOP_MAX_TRACE_LENGTH + 31)/32] = { 0 }; int exit_count; int length = compute_used(buffer, used, &exit_count); - _PyExecutorObject *executor = allocate_executor(exit_count, length+1); + length += 1; // For _START_EXECUTOR + _PyExecutorObject *executor = allocate_executor(exit_count, length); if (executor == NULL) { return NULL; } @@ -909,7 +910,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *depende executor->exits[i].temperature = 0; } int next_exit = exit_count-1; - _PyUOpInstruction *dest = (_PyUOpInstruction *)&executor->trace[length]; + _PyUOpInstruction *dest = (_PyUOpInstruction *)&executor->trace[length-1]; /* Scan backwards, so that we see the destinations of jumps before the jumps themselves. */ for (int i = UOP_MAX_TRACE_LENGTH-1; i >= 0; i--) { if (!BIT_IS_SET(used, i)) { @@ -957,7 +958,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *depende #ifdef _Py_JIT executor->jit_code = NULL; executor->jit_size = 0; - if (_PyJIT_Compile(executor, executor->trace, length+1)) { + if (_PyJIT_Compile(executor, executor->trace, length)) { Py_DECREF(executor); return NULL; } From edc9d85c68af600431556f3e8edae9b0fbfdfd34 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Tue, 5 Mar 2024 21:12:00 +0300 Subject: [PATCH 21/26] gh-109653: Just import `recursive_repr` in `dataclasses` (gh-109822) --- Lib/dataclasses.py | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 3335821f3f32e9..45ce5a98b51ae0 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -4,10 +4,9 @@ import types import inspect import keyword -import functools import itertools import abc -import _thread +from reprlib import recursive_repr from types import FunctionType, GenericAlias @@ -245,25 +244,6 @@ def __repr__(self): property, }) -# This function's logic is copied from "recursive_repr" function in -# reprlib module to avoid dependency. -def _recursive_repr(user_function): - # Decorator to make a repr function return "..." for a recursive - # call. - repr_running = set() - - @functools.wraps(user_function) - def wrapper(self): - key = id(self), _thread.get_ident() - if key in repr_running: - return '...' - repr_running.add(key) - try: - result = user_function(self) - finally: - repr_running.discard(key) - return result - return wrapper class InitVar: __slots__ = ('type', ) @@ -322,7 +302,7 @@ def __init__(self, default, default_factory, init, repr, hash, compare, self.kw_only = kw_only self._field_type = None - @_recursive_repr + @recursive_repr() def __repr__(self): return ('Field(' f'name={self.name!r},' @@ -632,7 +612,7 @@ def _repr_fn(fields, globals): for f in fields]) + ')"'], globals=globals) - return _recursive_repr(fn) + return recursive_repr()(fn) def _frozen_get_del_attr(cls, fields, globals): From dab85e01898ae737ba6197199090a51a6d4dd1fb Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 5 Mar 2024 11:51:04 -0700 Subject: [PATCH 22/26] gh-76785: Use PRId64 to Fix a Compiler Warning on Windows (gh-116369) I accidentally introduced the warning in gh-116328. --- Modules/_xxinterpqueuesmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index cb8b9e4a661d5a..00e49f385ee658 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -750,7 +750,7 @@ _queuerefs_clear(_queueref *head) #ifdef Py_DEBUG int64_t qid = ref->qid; - fprintf(stderr, "queue %ld still exists\n", qid); + fprintf(stderr, "queue %" PRId64 " still exists\n", qid); #endif _queue *queue = ref->queue; GLOBAL_FREE(ref); @@ -758,7 +758,7 @@ _queuerefs_clear(_queueref *head) _queue_kill_and_wait(queue); #ifdef Py_DEBUG if (queue->items.count > 0) { - fprintf(stderr, "queue %ld still holds %ld items\n", + fprintf(stderr, "queue %" PRId64 " still holds %" PRId64 " items\n", qid, queue->items.count); } #endif From 72714c0266ce6d39c7c7fb63f617573b8f5a3cb2 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 5 Mar 2024 13:54:20 -0500 Subject: [PATCH 23/26] gh-115103: Enable internal mimalloc assertions in debug builds (#116343) This sets `MI_DEBUG` to `2` in debug builds to enable `mi_assert_internal()` calls. Expensive internal assertions are not enabled. This also disables an assertion in free-threaded builds that would be triggered by the free-threaded GC because we traverse heaps that are not owned by the current thread. --- Include/internal/pycore_mimalloc.h | 2 +- Objects/mimalloc/alloc.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_mimalloc.h b/Include/internal/pycore_mimalloc.h index 14c98108ec131e..44c160b7ac19bf 100644 --- a/Include/internal/pycore_mimalloc.h +++ b/Include/internal/pycore_mimalloc.h @@ -27,7 +27,7 @@ typedef enum { # define MI_DEBUG_FREED PYMEM_DEADBYTE # define MI_DEBUG_PADDING PYMEM_FORBIDDENBYTE #ifdef Py_DEBUG -# define MI_DEBUG 1 +# define MI_DEBUG 2 #else # define MI_DEBUG 0 #endif diff --git a/Objects/mimalloc/alloc.c b/Objects/mimalloc/alloc.c index e6286b54bedc14..c133f23fc9830d 100644 --- a/Objects/mimalloc/alloc.c +++ b/Objects/mimalloc/alloc.c @@ -609,7 +609,10 @@ bool _mi_free_delayed_block(mi_block_t* block) { // get segment and page const mi_segment_t* const segment = _mi_ptr_segment(block); mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie); +#ifndef Py_GIL_DISABLED + // The GC traverses heaps of other threads, which can trigger this assert. mi_assert_internal(_mi_thread_id() == segment->thread_id); +#endif mi_page_t* const page = _mi_segment_page_of(segment, block); // Clear the no-delayed flag so delayed freeing is used again for this page. From e205c5cd8f1a49d0ef126123312ee8a40d1416b6 Mon Sep 17 00:00:00 2001 From: Kerim Kabirov <39376984+Privat33r-dev@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:14:52 +0100 Subject: [PATCH 24/26] gh-115986: Fix inaccuracies in pprint docs (#116104) Amend wording after gh-116019 was merged. --- Doc/library/pprint.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index 2a2eb098646364..32e1351b7ffeeb 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -59,7 +59,7 @@ Functions The configuration parameters *stream*, *indent*, *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* are passed to the :class:`PrettyPrinter` constructor and their meanings are as - described in its documentation above. + described in its documentation below. >>> import pprint >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] @@ -78,7 +78,7 @@ Functions Return the formatted representation of *object* as a string. *indent*, *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* are passed to the :class:`PrettyPrinter` constructor as formatting parameters - and their meanings are as described in its documentation above. + and their meanings are as described in its documentation below. .. function:: isreadable(object) From 990a5f17d05214abe8aafedf8e6418a0fb5ffd50 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Wed, 6 Mar 2024 01:08:18 +0300 Subject: [PATCH 25/26] gh-116112: Fix `ResourceWarning` in `test_asyncio.test_stream` (#116371) Co-authored-by: @CendioOssman --- Lib/test/test_asyncio/test_streams.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index bf123ebf9bd158..2cf48538d5d30d 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1188,6 +1188,7 @@ async def handle_echo(reader, writer): def test_unhandled_cancel(self): async def handle_echo(reader, writer): + writer.close() asyncio.current_task().cancel() messages = self._basetest_unhandled_exceptions(handle_echo) self.assertEqual(messages, []) From e84eeed8dbe6eca001c674c826d381ec24fcc200 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 5 Mar 2024 14:43:10 -0800 Subject: [PATCH 26/26] Implement _GROW_TIER2_FRAME without adding tier2_extra_size --- Include/internal/pycore_frame.h | 31 -------------------------- Include/internal/pycore_uop_metadata.h | 2 +- Lib/test/test_sys.py | 4 ++-- Python/bytecodes.c | 5 ++++- Python/ceval.c | 10 ++++----- Python/executor_cases.c.h | 5 ++++- 6 files changed, 15 insertions(+), 42 deletions(-) diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h index f2d1c5f26ddcff..0f9e7333cf1e1c 100644 --- a/Include/internal/pycore_frame.h +++ b/Include/internal/pycore_frame.h @@ -65,7 +65,6 @@ typedef struct _PyInterpreterFrame { _Py_CODEUNIT *instr_ptr; /* Instruction currently executing (or about to begin) */ int stacktop; /* Offset of TOS from localsplus */ uint16_t return_offset; /* Only relevant during a function call */ - uint16_t tier2_extra_size; /* How many extra entries is at the end of localsplus for tier 2 inlining */ char owner; /* Locals and stack */ PyObject *localsplus[1]; @@ -132,7 +131,6 @@ _PyFrame_Initialize( frame->instr_ptr = _PyCode_CODE(code); frame->return_offset = 0; frame->owner = FRAME_OWNED_BY_THREAD; - frame->tier2_extra_size = 0; for (int i = null_locals_from; i < code->co_nlocalsplus; i++) { frame->localsplus[i] = NULL; @@ -260,35 +258,6 @@ _PyThreadState_PushFrame(PyThreadState *tstate, size_t size); void _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame *frame); - -/* Converts frame for tier 2. - * Adds stack space at the end of the current frame for Tier 2 execution. - * The frame that is being expanded MUST be the current executing frame, and - * it must be at the top of the datastack. - * */ -static inline int -_PyFrame_ConvertToTier2(PyThreadState *tstate, _PyInterpreterFrame *frame, - int localsplus_grow) -{ - assert(localsplus_grow > 0); - // Already grown previously - if (frame->tier2_extra_size >= localsplus_grow) { - return 0; - } - if (frame->owner != FRAME_OWNED_BY_THREAD) { - return 1; - } - if (!_PyThreadState_HasStackSpace(tstate, localsplus_grow)) { - return 1; - } - assert(_PyThreadState_HasStackSpace(tstate, localsplus_grow)); - assert(tstate->current_frame == frame); - tstate->datastack_top += localsplus_grow; - assert(tstate->datastack_top < tstate->datastack_limit); - frame->tier2_extra_size += localsplus_grow; - return 0; -} - /* Pushes a frame without checking for space. * Must be guarded by _PyThreadState_HasStackSpace() * Consumes reference to func. */ diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index bec9a052036b21..cb8b427c7239f8 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -245,7 +245,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_VALIDITY_AND_SET_IP] = HAS_DEOPT_FLAG, [_PRE_INLINE] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG, [_POST_INLINE] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ESCAPES_FLAG, - [_GROW_TIER2_FRAME] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_GROW_TIER2_FRAME] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, }; const uint8_t _PyUop_Replication[MAX_UOP_ID+1] = { diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 39fdb1a36dce8c..38dcabd84d8170 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1550,7 +1550,7 @@ class C(object): pass def func(): return sys._getframe() x = func() - check(x, size('3Pi3c7P4ic??2P')) + check(x, size('3Pi3c7P2ic??2P')) # function def func(): pass check(func, size('15Pi')) @@ -1567,7 +1567,7 @@ def bar(cls): check(bar, size('PP')) # generator def get_gen(): yield 1 - check(get_gen(), size('PP4P4c7P4ic??2P')) + check(get_gen(), size('PP4P4c7P2ic??2P')) # iterator check(iter('abc'), size('lP')) # callable-iterator diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 883a66205ba067..d2f104ac5a5666 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4175,7 +4175,10 @@ dummy_func( } op(_GROW_TIER2_FRAME, (--)) { - DEOPT_IF(_PyFrame_ConvertToTier2(tstate, frame, oparg)); + DEOPT_IF(frame->owner != FRAME_OWNED_BY_THREAD); + DEOPT_IF(stack_pointer + oparg > tstate->datastack_limit); + assert(stack_pointer <= tstate->datastack_top); + tstate->datastack_top = stack_pointer + oparg; } diff --git a/Python/ceval.c b/Python/ceval.c index 3be9668dc65050..f9eb7c2889d9d2 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1675,11 +1675,10 @@ static void clear_thread_frame(PyThreadState *tstate, _PyInterpreterFrame * frame) { assert(frame->owner == FRAME_OWNED_BY_THREAD); - // Make sure that this is, indeed, the top frame. We can't check this in - // _PyThreadState_PopFrame, since f_code is already cleared at that point: - // This doesn't apply to tier 2 frames. - assert(frame->tier2_extra_size == 0 ? (PyObject **)frame + _PyFrame_GetCode(frame)->co_framesize == - tstate->datastack_top : 1); + // // Make sure that this is, indeed, the top frame. We can't check this in + // // _PyThreadState_PopFrame, since f_code is already cleared at that point: + // assert((PyObject **)frame + _PyFrame_GetCode(frame)->co_framesize == + // tstate->datastack_top); tstate->c_recursion_remaining--; assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame); _PyFrame_ClearExceptCode(frame); @@ -1793,7 +1792,6 @@ _PyEvalFramePushAndInit_Ex(PyThreadState *tstate, PyFunctionObject *func, return NULL; } - PyObject * _PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func, PyObject *locals, diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index e8568fc764d1b1..0121479ac6e4b7 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3928,7 +3928,10 @@ case _GROW_TIER2_FRAME: { oparg = CURRENT_OPARG(); - if (_PyFrame_ConvertToTier2(tstate, frame, oparg)) goto deoptimize; + if (frame->owner != FRAME_OWNED_BY_THREAD) goto deoptimize; + if (stack_pointer + oparg > tstate->datastack_limit) goto deoptimize; + assert(stack_pointer <= tstate->datastack_top); + tstate->datastack_top = stack_pointer + oparg; break; }