From 02cb5fdee391670d63b2fc0a92ca9b36a32ac95a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 1 Jul 2024 17:49:03 +0200 Subject: [PATCH 01/41] gh-121200: Fix test_expanduser_pwd2() of test_posixpath (#121228) Call getpwnam() to get pw_dir, since it can be different than getpwall() pw_dir. --- Lib/test/test_posixpath.py | 11 ++++++++--- .../2024-07-01-16-15-06.gh-issue-121200.4Pc-gc.rst | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-07-01-16-15-06.gh-issue-121200.4Pc-gc.rst diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 9f36a4cd9ce43f..fb714fd90ae2b3 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -359,11 +359,16 @@ def test_expanduser_pwd(self): "no home directory on VxWorks") def test_expanduser_pwd2(self): pwd = import_helper.import_module('pwd') - for entry in pwd.getpwall(): - name = entry.pw_name + for all_entry in pwd.getpwall(): + name = all_entry.pw_name + + # gh-121200: pw_dir can be different between getpwall() and + # getpwnam(), so use getpwnam() pw_dir as expanduser() does. + entry = pwd.getpwnam(name) home = entry.pw_dir home = home.rstrip('/') or '/' - with self.subTest(pwd=entry): + + with self.subTest(all_entry=all_entry, entry=entry): self.assertEqual(posixpath.expanduser('~' + name), home) self.assertEqual(posixpath.expanduser(os.fsencode('~' + name)), os.fsencode(home)) diff --git a/Misc/NEWS.d/next/Tests/2024-07-01-16-15-06.gh-issue-121200.4Pc-gc.rst b/Misc/NEWS.d/next/Tests/2024-07-01-16-15-06.gh-issue-121200.4Pc-gc.rst new file mode 100644 index 00000000000000..01e0d9b9f217d4 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-07-01-16-15-06.gh-issue-121200.4Pc-gc.rst @@ -0,0 +1,3 @@ +Fix ``test_expanduser_pwd2()`` of ``test_posixpath``. Call ``getpwnam()`` +to get ``pw_dir``, since it can be different than ``getpwall()`` ``pw_dir``. +Patch by Victor Stinner. From d44c550f7ebee7d33785142e6031a4621cf21573 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 1 Jul 2024 18:27:50 +0200 Subject: [PATCH 02/41] gh-120743: Soft deprecate os.popen() function (#120744) Soft deprecate os.popen() and os.spawn*() functions. --- Doc/library/os.rst | 10 +++++++++- Doc/whatsnew/3.14.rst | 5 +++++ .../2024-06-19-15-43-04.gh-issue-120743.CMMl2P.rst | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-19-15-43-04.gh-issue-120743.CMMl2P.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 360d71e70960c7..8d95d01fe55ed9 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4642,6 +4642,10 @@ written in Python, such as a mail server's external command delivery program. Use :class:`subprocess.Popen` or :func:`subprocess.run` to control options like encodings. + .. deprecated:: 3.14 + The function is :term:`soft deprecated` and should no longer be used to + write new code. The :mod:`subprocess` module is recommended instead. + .. function:: posix_spawn(path, argv, env, *, file_actions=None, \ setpgroup=None, resetids=False, setsid=False, setsigmask=(), \ @@ -4868,6 +4872,10 @@ written in Python, such as a mail server's external command delivery program. .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. deprecated:: 3.14 + These functions are :term:`soft deprecated` and should no longer be used + to write new code. The :mod:`subprocess` module is recommended instead. + .. data:: P_NOWAIT P_NOWAITO @@ -4972,7 +4980,7 @@ written in Python, such as a mail server's external command delivery program. shell documentation. The :mod:`subprocess` module provides more powerful facilities for spawning - new processes and retrieving their results; using that module is preferable + new processes and retrieving their results; using that module is recommended to using this function. See the :ref:`subprocess-replacements` section in the :mod:`subprocess` documentation for some helpful recipes. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ee3001661b3143..6ebadd75092fac 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -144,6 +144,11 @@ Deprecated as a single positional argument. (Contributed by Serhiy Storchaka in :gh:`109218`.) +* :term:`Soft deprecate ` :func:`os.popen` and + :func:`os.spawn* ` functions. They should no longer be used to + write new code. The :mod:`subprocess` module is recommended instead. + (Contributed by Victor Stinner in :gh:`120743`.) + Removed ======= diff --git a/Misc/NEWS.d/next/Library/2024-06-19-15-43-04.gh-issue-120743.CMMl2P.rst b/Misc/NEWS.d/next/Library/2024-06-19-15-43-04.gh-issue-120743.CMMl2P.rst new file mode 100644 index 00000000000000..e06dcf8af26a60 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-19-15-43-04.gh-issue-120743.CMMl2P.rst @@ -0,0 +1,3 @@ +:term:`Soft deprecate ` :func:`os.popen` and :func:`os.spawn* +` functions. They should no longer be used to write new code. The +:mod:`subprocess` module is recommended instead. Patch by Victor Stinner. From 91313afdb392d0d6105e9aaa57b5a50112b613e7 Mon Sep 17 00:00:00 2001 From: Danny Yang Date: Mon, 1 Jul 2024 12:34:39 -0400 Subject: [PATCH 03/41] gh-114104: clarify asynchronous comprehension docs to match runtime behavior (#121175) --- Doc/reference/expressions.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 872773f4d28235..95ece0e1608dcc 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -218,10 +218,12 @@ A comprehension in an :keyword:`!async def` function may consist of either a :keyword:`!for` or :keyword:`!async for` clause following the leading expression, may contain additional :keyword:`!for` or :keyword:`!async for` clauses, and may also use :keyword:`await` expressions. -If a comprehension contains either :keyword:`!async for` clauses or -:keyword:`!await` expressions or other asynchronous comprehensions it is called -an :dfn:`asynchronous comprehension`. An asynchronous comprehension may -suspend the execution of the coroutine function in which it appears. + +If a comprehension contains :keyword:`!async for` clauses, or if it contains +:keyword:`!await` expressions or other asynchronous comprehensions anywhere except +the iterable expression in the leftmost :keyword:`!for` clause, it is called an +:dfn:`asynchronous comprehension`. An asynchronous comprehension may suspend the +execution of the coroutine function in which it appears. See also :pep:`530`. .. versionadded:: 3.6 From 9bcb7d8c6f8277c4e76145ec4c834213167e3387 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 1 Jul 2024 11:58:25 -0600 Subject: [PATCH 04/41] gh-121110: Temporarily Skip test_basic_multiple_interpreters_reset_each (gh-121236) This will allow Py_TRACE_REFS builds to pass the test suite, until the underlying issue can be resolved. --- Lib/test/test_import/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index e29097baaf53ae..c10f689c4ea34b 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -3034,6 +3034,13 @@ def test_basic_multiple_interpreters_deleted_no_reset(self): def test_basic_multiple_interpreters_reset_each(self): # resetting between each interpreter + if Py_TRACE_REFS: + # It's a Py_TRACE_REFS build. + # This test breaks interpreter isolation a little, + # which causes problems on Py_TRACE_REF builds. + # See gh-121110. + raise unittest.SkipTest('crashes on Py_TRACE_REFS builds') + # At this point: # * alive in 0 interpreters # * module def may or may not be loaded already From 294e72496439da984cb8dba9100d3613c8cc8a6d Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 2 Jul 2024 03:11:39 +0800 Subject: [PATCH 05/41] gh-117657: Fix data races reported by TSAN in some set methods (#120914) Refactor the fast Unicode hash check into `_PyObject_HashFast` and use relaxed atomic loads in the free-threaded build. After this change, the TSAN doesn't report data races for this method. --- Include/internal/pycore_object.h | 14 +++ Modules/_collectionsmodule.c | 9 +- Objects/dictobject.c | 135 ++++++++------------- Objects/setobject.c | 30 ++--- Objects/typeobject.c | 13 +- Tools/tsan/suppressions_free_threading.txt | 1 - 6 files changed, 81 insertions(+), 121 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 9c963d8970d665..fa789611133a6f 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -613,6 +613,20 @@ _PyObject_IS_GC(PyObject *obj) && (type->tp_is_gc == NULL || type->tp_is_gc(obj))); } +// Fast inlined version of PyObject_Hash() +static inline Py_hash_t +_PyObject_HashFast(PyObject *op) +{ + if (PyUnicode_CheckExact(op)) { + Py_hash_t hash = FT_ATOMIC_LOAD_SSIZE_RELAXED( + _PyASCIIObject_CAST(op)->hash); + if (hash != -1) { + return hash; + } + } + return PyObject_Hash(op); +} + // Fast inlined version of PyType_IS_GC() #define _PyType_IS_GC(t) _PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC) diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index 641d57a64c8357..0bc61db4117c5d 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -2537,12 +2537,9 @@ _collections__count_elements_impl(PyObject *module, PyObject *mapping, if (key == NULL) break; - if (!PyUnicode_CheckExact(key) || - (hash = _PyASCIIObject_CAST(key)->hash) == -1) - { - hash = PyObject_Hash(key); - if (hash == -1) - goto done; + hash = _PyObject_HashFast(key); + if (hash == -1) { + goto done; } oldval = _PyDict_GetItem_KnownHash(mapping, key, hash); diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 5d325465608f99..2b11a01595b0bc 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -433,7 +433,7 @@ static inline Py_hash_t unicode_get_hash(PyObject *o) { assert(PyUnicode_CheckExact(o)); - return _PyASCIIObject_CAST(o)->hash; + return FT_ATOMIC_LOAD_SSIZE_RELAXED(_PyASCIIObject_CAST(o)->hash); } /* Print summary info about the state of the optimized allocator */ @@ -2177,13 +2177,10 @@ dict_getitem(PyObject *op, PyObject *key, const char *warnmsg) } PyDictObject *mp = (PyDictObject *)op; - Py_hash_t hash; - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) { - PyErr_FormatUnraisable(warnmsg); - return NULL; - } + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + PyErr_FormatUnraisable(warnmsg); + return NULL; } PyThreadState *tstate = _PyThreadState_GET(); @@ -2232,12 +2229,9 @@ _PyDict_LookupIndex(PyDictObject *mp, PyObject *key) assert(PyDict_CheckExact((PyObject*)mp)); assert(PyUnicode_CheckExact(key)); - Py_hash_t hash = unicode_get_hash(key); + Py_hash_t hash = _PyObject_HashFast(key); if (hash == -1) { - hash = PyObject_Hash(key); - if (hash == -1) { - return -1; - } + return -1; } return _Py_dict_lookup(mp, key, hash, &value); @@ -2308,14 +2302,10 @@ PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject **result) return -1; } - Py_hash_t hash; - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) - { - hash = PyObject_Hash(key); - if (hash == -1) { - *result = NULL; - return -1; - } + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + *result = NULL; + return -1; } return _PyDict_GetItemRef_KnownHash((PyDictObject *)op, key, hash, result); @@ -2327,13 +2317,10 @@ _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, PyObject *key, PyObject ** ASSERT_DICT_LOCKED(op); assert(PyUnicode_CheckExact(key)); - Py_hash_t hash; - if ((hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) { - *result = NULL; - return -1; - } + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + *result = NULL; + return -1; } PyObject *value; @@ -2367,12 +2354,9 @@ PyDict_GetItemWithError(PyObject *op, PyObject *key) PyErr_BadInternalCall(); return NULL; } - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) - { - hash = PyObject_Hash(key); - if (hash == -1) { - return NULL; - } + hash = _PyObject_HashFast(key); + if (hash == -1) { + return NULL; } #ifdef Py_GIL_DISABLED @@ -2440,10 +2424,9 @@ _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key) Py_hash_t hash; PyObject *value; - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return NULL; + hash = _PyObject_HashFast(key); + if (hash == -1) { + return NULL; } /* namespace 1: globals */ @@ -2468,14 +2451,11 @@ setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) assert(key); assert(value); assert(PyDict_Check(mp)); - Py_hash_t hash; - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) { - Py_DECREF(key); - Py_DECREF(value); - return -1; - } + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + Py_DECREF(key); + Py_DECREF(value); + return -1; } PyInterpreterState *interp = _PyInterpreterState_GET(); @@ -2624,12 +2604,10 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, int PyDict_DelItem(PyObject *op, PyObject *key) { - Py_hash_t hash; assert(key); - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return -1; + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + return -1; } return _PyDict_DelItem_KnownHash(op, key, hash); @@ -2953,15 +2931,12 @@ pop_lock_held(PyObject *op, PyObject *key, PyObject **result) return 0; } - Py_hash_t hash; - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) { - if (result) { - *result = NULL; - } - return -1; + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + if (result) { + *result = NULL; } + return -1; } return _PyDict_Pop_KnownHash(dict, key, hash, result); } @@ -3293,10 +3268,9 @@ dict_subscript(PyObject *self, PyObject *key) Py_hash_t hash; PyObject *value; - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return NULL; + hash = _PyObject_HashFast(key); + if (hash == -1) { + return NULL; } ix = _Py_dict_lookup_threadsafe(mp, key, hash, &value); if (ix == DKIX_ERROR) @@ -4183,10 +4157,9 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) Py_hash_t hash; Py_ssize_t ix; - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return NULL; + hash = _PyObject_HashFast(key); + if (hash == -1) { + return NULL; } ix = _Py_dict_lookup_threadsafe(self, key, hash, &val); if (ix == DKIX_ERROR) @@ -4216,14 +4189,12 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu return -1; } - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) { - if (result) { - *result = NULL; - } - return -1; + hash = _PyObject_HashFast(key); + if (hash == -1) { + if (result) { + *result = NULL; } + return -1; } if (mp->ma_keys == Py_EMPTY_KEYS) { @@ -4655,12 +4626,10 @@ static PyMethodDef mapp_methods[] = { int PyDict_Contains(PyObject *op, PyObject *key) { - Py_hash_t hash; + Py_hash_t hash = _PyObject_HashFast(key); - if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return -1; + if (hash == -1) { + return -1; } return _PyDict_Contains_KnownHash(op, key, hash); @@ -6743,11 +6712,9 @@ int _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject *name, PyObject *value) { if (value == NULL) { - Py_hash_t hash; - if (!PyUnicode_CheckExact(name) || (hash = unicode_get_hash(name)) == -1) { - hash = PyObject_Hash(name); - if (hash == -1) - return -1; + Py_hash_t hash = _PyObject_HashFast(name); + if (hash == -1) { + return -1; } return delitem_knownhash_lock_held((PyObject *)dict, name, hash); } else { diff --git a/Objects/setobject.c b/Objects/setobject.c index 68986bb6a6b557..eb0c404bf6b8e0 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -365,13 +365,9 @@ set_discard_entry(PySetObject *so, PyObject *key, Py_hash_t hash) static int set_add_key(PySetObject *so, PyObject *key) { - Py_hash_t hash; - - if (!PyUnicode_CheckExact(key) || - (hash = _PyASCIIObject_CAST(key)->hash) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return -1; + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + return -1; } return set_add_entry(so, key, hash); } @@ -379,13 +375,9 @@ set_add_key(PySetObject *so, PyObject *key) static int set_contains_key(PySetObject *so, PyObject *key) { - Py_hash_t hash; - - if (!PyUnicode_CheckExact(key) || - (hash = _PyASCIIObject_CAST(key)->hash) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return -1; + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + return -1; } return set_contains_entry(so, key, hash); } @@ -393,13 +385,9 @@ set_contains_key(PySetObject *so, PyObject *key) static int set_discard_key(PySetObject *so, PyObject *key) { - Py_hash_t hash; - - if (!PyUnicode_CheckExact(key) || - (hash = _PyASCIIObject_CAST(key)->hash) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return -1; + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + return -1; } return set_discard_entry(so, key, hash); } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d374a8e6393176..b042e64a188d9d 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5251,15 +5251,10 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) { ASSERT_TYPE_LOCK_HELD(); - Py_hash_t hash; - if (!PyUnicode_CheckExact(name) || - (hash = _PyASCIIObject_CAST(name)->hash) == -1) - { - hash = PyObject_Hash(name); - if (hash == -1) { - *error = -1; - return NULL; - } + Py_hash_t hash = _PyObject_HashFast(name); + if (hash == -1) { + *error = -1; + return NULL; } /* Look in tp_dict of types in MRO */ diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index 0e17237eaa331d..534a0cedb743dd 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -30,7 +30,6 @@ race_top:assign_version_tag race_top:insertdict race_top:lookup_tp_dict race_top:new_reference -race_top:set_contains_key # https://gist.github.com/colesbury/d13d033f413b4ad07929d044bed86c35 race_top:set_discard_entry race_top:_PyDict_CheckConsistency From 33903c53dbdb768e1ef7c46d347869577f2173ce Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Mon, 1 Jul 2024 13:17:40 -0700 Subject: [PATCH 06/41] GH-116017: Get rid of _COLD_EXITs (GH-120960) --- Include/internal/pycore_backoff.h | 8 +- Include/internal/pycore_code.h | 2 +- Include/internal/pycore_optimizer.h | 22 +- Include/internal/pycore_uop_ids.h | 225 +++++++++--------- Include/internal/pycore_uop_metadata.h | 6 +- ...-06-24-08-39-23.gh-issue-116017.-Bw2UY.rst | 3 + Python/bytecodes.c | 93 +++++--- Python/ceval.c | 23 +- Python/ceval_macros.h | 3 +- Python/executor_cases.c.h | 93 +++++--- Python/jit.c | 2 +- Python/optimizer.c | 86 +------ Python/optimizer_cases.c.h | 4 - Tools/c-analyzer/cpython/ignored.tsv | 2 - Tools/jit/_stencils.py | 3 - Tools/jit/template.c | 8 - 16 files changed, 246 insertions(+), 337 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-24-08-39-23.gh-issue-116017.-Bw2UY.rst diff --git a/Include/internal/pycore_backoff.h b/Include/internal/pycore_backoff.h index 0bcca1e769b341..3db3aa3eb77879 100644 --- a/Include/internal/pycore_backoff.h +++ b/Include/internal/pycore_backoff.h @@ -122,14 +122,14 @@ initial_jump_backoff_counter(void) * otherwise when a side exit warms up we may construct * a new trace before the Tier 1 code has properly re-specialized. * Backoff sequence 64, 128, 256, 512, 1024, 2048, 4096. */ -#define COLD_EXIT_INITIAL_VALUE 64 -#define COLD_EXIT_INITIAL_BACKOFF 6 +#define SIDE_EXIT_INITIAL_VALUE 64 +#define SIDE_EXIT_INITIAL_BACKOFF 6 static inline _Py_BackoffCounter initial_temperature_backoff_counter(void) { - return make_backoff_counter(COLD_EXIT_INITIAL_VALUE, - COLD_EXIT_INITIAL_BACKOFF); + return make_backoff_counter(SIDE_EXIT_INITIAL_VALUE, + SIDE_EXIT_INITIAL_BACKOFF); } /* Unreachable backoff counter. */ diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index 81caaa5abb2e93..cf2cc7f5b4ac92 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -535,7 +535,7 @@ write_location_entry_start(uint8_t *ptr, int code, int length) #define ADAPTIVE_COOLDOWN_BACKOFF 0 // Can't assert this in pycore_backoff.h because of header order dependencies -static_assert(COLD_EXIT_INITIAL_VALUE > ADAPTIVE_COOLDOWN_VALUE, +static_assert(SIDE_EXIT_INITIAL_VALUE > ADAPTIVE_COOLDOWN_VALUE, "Cold exit value should be larger than adaptive cooldown value"); static inline _Py_BackoffCounter diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 1007f838b7e374..bcbb8b73706359 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -41,24 +41,18 @@ typedef struct { * the 32 bits between the oparg and operand are: * UOP_FORMAT_TARGET: * uint32_t target; - * UOP_FORMAT_EXIT - * uint16_t exit_index; - * uint16_t error_target; * UOP_FORMAT_JUMP * uint16_t jump_target; * uint16_t error_target; */ typedef struct { - uint16_t opcode:14; - uint16_t format:2; + uint16_t opcode:15; + uint16_t format:1; uint16_t oparg; union { uint32_t target; struct { - union { - uint16_t exit_index; - uint16_t jump_target; - }; + uint16_t jump_target; uint16_t error_target; }; }; @@ -160,9 +154,7 @@ struct _Py_UopsSymbol { }; #define UOP_FORMAT_TARGET 0 -#define UOP_FORMAT_EXIT 1 -#define UOP_FORMAT_JUMP 2 -#define UOP_FORMAT_UNUSED 3 +#define UOP_FORMAT_JUMP 1 static inline uint32_t uop_get_target(const _PyUOpInstruction *inst) { @@ -170,12 +162,6 @@ static inline uint32_t uop_get_target(const _PyUOpInstruction *inst) return inst->target; } -static inline uint16_t uop_get_exit_index(const _PyUOpInstruction *inst) -{ - assert(inst->format == UOP_FORMAT_EXIT); - return inst->exit_index; -} - static inline uint16_t uop_get_jump_target(const _PyUOpInstruction *inst) { assert(inst->format == UOP_FORMAT_JUMP); diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 85e689c096949f..bd1d27b03b3d00 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -72,12 +72,11 @@ extern "C" { #define _CHECK_STACK_SPACE_OPERAND 337 #define _CHECK_VALIDITY 338 #define _CHECK_VALIDITY_AND_SET_IP 339 -#define _COLD_EXIT 340 -#define _COMPARE_OP 341 -#define _COMPARE_OP_FLOAT 342 -#define _COMPARE_OP_INT 343 -#define _COMPARE_OP_STR 344 -#define _CONTAINS_OP 345 +#define _COMPARE_OP 340 +#define _COMPARE_OP_FLOAT 341 +#define _COMPARE_OP_INT 342 +#define _COMPARE_OP_STR 343 +#define _CONTAINS_OP 344 #define _CONTAINS_OP_DICT CONTAINS_OP_DICT #define _CONTAINS_OP_SET CONTAINS_OP_SET #define _CONVERT_VALUE CONVERT_VALUE @@ -89,53 +88,53 @@ extern "C" { #define _DELETE_GLOBAL DELETE_GLOBAL #define _DELETE_NAME DELETE_NAME #define _DELETE_SUBSCR DELETE_SUBSCR -#define _DEOPT 346 +#define _DEOPT 345 #define _DICT_MERGE DICT_MERGE #define _DICT_UPDATE DICT_UPDATE -#define _DYNAMIC_EXIT 347 +#define _DYNAMIC_EXIT 346 #define _END_SEND END_SEND -#define _ERROR_POP_N 348 +#define _ERROR_POP_N 347 #define _EXIT_INIT_CHECK EXIT_INIT_CHECK -#define _EXPAND_METHOD 349 -#define _FATAL_ERROR 350 +#define _EXPAND_METHOD 348 +#define _FATAL_ERROR 349 #define _FORMAT_SIMPLE FORMAT_SIMPLE #define _FORMAT_WITH_SPEC FORMAT_WITH_SPEC -#define _FOR_ITER 351 -#define _FOR_ITER_GEN_FRAME 352 -#define _FOR_ITER_TIER_TWO 353 +#define _FOR_ITER 350 +#define _FOR_ITER_GEN_FRAME 351 +#define _FOR_ITER_TIER_TWO 352 #define _GET_AITER GET_AITER #define _GET_ANEXT GET_ANEXT #define _GET_AWAITABLE GET_AWAITABLE #define _GET_ITER GET_ITER #define _GET_LEN GET_LEN #define _GET_YIELD_FROM_ITER GET_YIELD_FROM_ITER -#define _GUARD_BOTH_FLOAT 354 -#define _GUARD_BOTH_INT 355 -#define _GUARD_BOTH_UNICODE 356 -#define _GUARD_BUILTINS_VERSION 357 -#define _GUARD_DORV_NO_DICT 358 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 359 -#define _GUARD_GLOBALS_VERSION 360 -#define _GUARD_IS_FALSE_POP 361 -#define _GUARD_IS_NONE_POP 362 -#define _GUARD_IS_NOT_NONE_POP 363 -#define _GUARD_IS_TRUE_POP 364 -#define _GUARD_KEYS_VERSION 365 -#define _GUARD_NOS_FLOAT 366 -#define _GUARD_NOS_INT 367 -#define _GUARD_NOT_EXHAUSTED_LIST 368 -#define _GUARD_NOT_EXHAUSTED_RANGE 369 -#define _GUARD_NOT_EXHAUSTED_TUPLE 370 -#define _GUARD_TOS_FLOAT 371 -#define _GUARD_TOS_INT 372 -#define _GUARD_TYPE_VERSION 373 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 374 -#define _INIT_CALL_PY_EXACT_ARGS 375 -#define _INIT_CALL_PY_EXACT_ARGS_0 376 -#define _INIT_CALL_PY_EXACT_ARGS_1 377 -#define _INIT_CALL_PY_EXACT_ARGS_2 378 -#define _INIT_CALL_PY_EXACT_ARGS_3 379 -#define _INIT_CALL_PY_EXACT_ARGS_4 380 +#define _GUARD_BOTH_FLOAT 353 +#define _GUARD_BOTH_INT 354 +#define _GUARD_BOTH_UNICODE 355 +#define _GUARD_BUILTINS_VERSION 356 +#define _GUARD_DORV_NO_DICT 357 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 358 +#define _GUARD_GLOBALS_VERSION 359 +#define _GUARD_IS_FALSE_POP 360 +#define _GUARD_IS_NONE_POP 361 +#define _GUARD_IS_NOT_NONE_POP 362 +#define _GUARD_IS_TRUE_POP 363 +#define _GUARD_KEYS_VERSION 364 +#define _GUARD_NOS_FLOAT 365 +#define _GUARD_NOS_INT 366 +#define _GUARD_NOT_EXHAUSTED_LIST 367 +#define _GUARD_NOT_EXHAUSTED_RANGE 368 +#define _GUARD_NOT_EXHAUSTED_TUPLE 369 +#define _GUARD_TOS_FLOAT 370 +#define _GUARD_TOS_INT 371 +#define _GUARD_TYPE_VERSION 372 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 373 +#define _INIT_CALL_PY_EXACT_ARGS 374 +#define _INIT_CALL_PY_EXACT_ARGS_0 375 +#define _INIT_CALL_PY_EXACT_ARGS_1 376 +#define _INIT_CALL_PY_EXACT_ARGS_2 377 +#define _INIT_CALL_PY_EXACT_ARGS_3 378 +#define _INIT_CALL_PY_EXACT_ARGS_4 379 #define _INSTRUMENTED_CALL INSTRUMENTED_CALL #define _INSTRUMENTED_CALL_FUNCTION_EX INSTRUMENTED_CALL_FUNCTION_EX #define _INSTRUMENTED_CALL_KW INSTRUMENTED_CALL_KW @@ -152,65 +151,65 @@ extern "C" { #define _INSTRUMENTED_RETURN_CONST INSTRUMENTED_RETURN_CONST #define _INSTRUMENTED_RETURN_VALUE INSTRUMENTED_RETURN_VALUE #define _INSTRUMENTED_YIELD_VALUE INSTRUMENTED_YIELD_VALUE -#define _INTERNAL_INCREMENT_OPT_COUNTER 381 -#define _IS_NONE 382 +#define _INTERNAL_INCREMENT_OPT_COUNTER 380 +#define _IS_NONE 381 #define _IS_OP IS_OP -#define _ITER_CHECK_LIST 383 -#define _ITER_CHECK_RANGE 384 -#define _ITER_CHECK_TUPLE 385 -#define _ITER_JUMP_LIST 386 -#define _ITER_JUMP_RANGE 387 -#define _ITER_JUMP_TUPLE 388 -#define _ITER_NEXT_LIST 389 -#define _ITER_NEXT_RANGE 390 -#define _ITER_NEXT_TUPLE 391 -#define _JUMP_TO_TOP 392 +#define _ITER_CHECK_LIST 382 +#define _ITER_CHECK_RANGE 383 +#define _ITER_CHECK_TUPLE 384 +#define _ITER_JUMP_LIST 385 +#define _ITER_JUMP_RANGE 386 +#define _ITER_JUMP_TUPLE 387 +#define _ITER_NEXT_LIST 388 +#define _ITER_NEXT_RANGE 389 +#define _ITER_NEXT_TUPLE 390 +#define _JUMP_TO_TOP 391 #define _LIST_APPEND LIST_APPEND #define _LIST_EXTEND LIST_EXTEND -#define _LOAD_ATTR 393 -#define _LOAD_ATTR_CLASS 394 -#define _LOAD_ATTR_CLASS_0 395 -#define _LOAD_ATTR_CLASS_1 396 +#define _LOAD_ATTR 392 +#define _LOAD_ATTR_CLASS 393 +#define _LOAD_ATTR_CLASS_0 394 +#define _LOAD_ATTR_CLASS_1 395 #define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN -#define _LOAD_ATTR_INSTANCE_VALUE 397 -#define _LOAD_ATTR_INSTANCE_VALUE_0 398 -#define _LOAD_ATTR_INSTANCE_VALUE_1 399 -#define _LOAD_ATTR_METHOD_LAZY_DICT 400 -#define _LOAD_ATTR_METHOD_NO_DICT 401 -#define _LOAD_ATTR_METHOD_WITH_VALUES 402 -#define _LOAD_ATTR_MODULE 403 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 404 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 405 +#define _LOAD_ATTR_INSTANCE_VALUE 396 +#define _LOAD_ATTR_INSTANCE_VALUE_0 397 +#define _LOAD_ATTR_INSTANCE_VALUE_1 398 +#define _LOAD_ATTR_METHOD_LAZY_DICT 399 +#define _LOAD_ATTR_METHOD_NO_DICT 400 +#define _LOAD_ATTR_METHOD_WITH_VALUES 401 +#define _LOAD_ATTR_MODULE 402 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 403 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 404 #define _LOAD_ATTR_PROPERTY LOAD_ATTR_PROPERTY -#define _LOAD_ATTR_SLOT 406 -#define _LOAD_ATTR_SLOT_0 407 -#define _LOAD_ATTR_SLOT_1 408 -#define _LOAD_ATTR_WITH_HINT 409 +#define _LOAD_ATTR_SLOT 405 +#define _LOAD_ATTR_SLOT_0 406 +#define _LOAD_ATTR_SLOT_1 407 +#define _LOAD_ATTR_WITH_HINT 408 #define _LOAD_BUILD_CLASS LOAD_BUILD_CLASS #define _LOAD_COMMON_CONSTANT LOAD_COMMON_CONSTANT #define _LOAD_CONST LOAD_CONST -#define _LOAD_CONST_INLINE 410 -#define _LOAD_CONST_INLINE_BORROW 411 -#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 412 -#define _LOAD_CONST_INLINE_WITH_NULL 413 +#define _LOAD_CONST_INLINE 409 +#define _LOAD_CONST_INLINE_BORROW 410 +#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 411 +#define _LOAD_CONST_INLINE_WITH_NULL 412 #define _LOAD_DEREF LOAD_DEREF -#define _LOAD_FAST 414 -#define _LOAD_FAST_0 415 -#define _LOAD_FAST_1 416 -#define _LOAD_FAST_2 417 -#define _LOAD_FAST_3 418 -#define _LOAD_FAST_4 419 -#define _LOAD_FAST_5 420 -#define _LOAD_FAST_6 421 -#define _LOAD_FAST_7 422 +#define _LOAD_FAST 413 +#define _LOAD_FAST_0 414 +#define _LOAD_FAST_1 415 +#define _LOAD_FAST_2 416 +#define _LOAD_FAST_3 417 +#define _LOAD_FAST_4 418 +#define _LOAD_FAST_5 419 +#define _LOAD_FAST_6 420 +#define _LOAD_FAST_7 421 #define _LOAD_FAST_AND_CLEAR LOAD_FAST_AND_CLEAR #define _LOAD_FAST_CHECK LOAD_FAST_CHECK #define _LOAD_FAST_LOAD_FAST LOAD_FAST_LOAD_FAST #define _LOAD_FROM_DICT_OR_DEREF LOAD_FROM_DICT_OR_DEREF #define _LOAD_FROM_DICT_OR_GLOBALS LOAD_FROM_DICT_OR_GLOBALS -#define _LOAD_GLOBAL 423 -#define _LOAD_GLOBAL_BUILTINS 424 -#define _LOAD_GLOBAL_MODULE 425 +#define _LOAD_GLOBAL 422 +#define _LOAD_GLOBAL_BUILTINS 423 +#define _LOAD_GLOBAL_MODULE 424 #define _LOAD_LOCALS LOAD_LOCALS #define _LOAD_NAME LOAD_NAME #define _LOAD_SPECIAL LOAD_SPECIAL @@ -225,51 +224,51 @@ extern "C" { #define _MATCH_SEQUENCE MATCH_SEQUENCE #define _NOP NOP #define _POP_EXCEPT POP_EXCEPT -#define _POP_JUMP_IF_FALSE 426 -#define _POP_JUMP_IF_TRUE 427 +#define _POP_JUMP_IF_FALSE 425 +#define _POP_JUMP_IF_TRUE 426 #define _POP_TOP POP_TOP -#define _POP_TOP_LOAD_CONST_INLINE_BORROW 428 +#define _POP_TOP_LOAD_CONST_INLINE_BORROW 427 #define _PUSH_EXC_INFO PUSH_EXC_INFO -#define _PUSH_FRAME 429 +#define _PUSH_FRAME 428 #define _PUSH_NULL PUSH_NULL -#define _PY_FRAME_GENERAL 430 -#define _REPLACE_WITH_TRUE 431 +#define _PY_FRAME_GENERAL 429 +#define _REPLACE_WITH_TRUE 430 #define _RESUME_CHECK RESUME_CHECK #define _RETURN_GENERATOR RETURN_GENERATOR #define _RETURN_VALUE RETURN_VALUE -#define _SAVE_RETURN_OFFSET 432 -#define _SEND 433 +#define _SAVE_RETURN_OFFSET 431 +#define _SEND 432 #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 434 -#define _STORE_ATTR 435 -#define _STORE_ATTR_INSTANCE_VALUE 436 -#define _STORE_ATTR_SLOT 437 -#define _STORE_ATTR_WITH_HINT 438 +#define _START_EXECUTOR 433 +#define _STORE_ATTR 434 +#define _STORE_ATTR_INSTANCE_VALUE 435 +#define _STORE_ATTR_SLOT 436 +#define _STORE_ATTR_WITH_HINT 437 #define _STORE_DEREF STORE_DEREF -#define _STORE_FAST 439 -#define _STORE_FAST_0 440 -#define _STORE_FAST_1 441 -#define _STORE_FAST_2 442 -#define _STORE_FAST_3 443 -#define _STORE_FAST_4 444 -#define _STORE_FAST_5 445 -#define _STORE_FAST_6 446 -#define _STORE_FAST_7 447 +#define _STORE_FAST 438 +#define _STORE_FAST_0 439 +#define _STORE_FAST_1 440 +#define _STORE_FAST_2 441 +#define _STORE_FAST_3 442 +#define _STORE_FAST_4 443 +#define _STORE_FAST_5 444 +#define _STORE_FAST_6 445 +#define _STORE_FAST_7 446 #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 448 +#define _STORE_SUBSCR 447 #define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT #define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT #define _SWAP SWAP -#define _TIER2_RESUME_CHECK 449 -#define _TO_BOOL 450 +#define _TIER2_RESUME_CHECK 448 +#define _TO_BOOL 449 #define _TO_BOOL_BOOL TO_BOOL_BOOL #define _TO_BOOL_INT TO_BOOL_INT #define _TO_BOOL_LIST TO_BOOL_LIST @@ -279,13 +278,13 @@ extern "C" { #define _UNARY_NEGATIVE UNARY_NEGATIVE #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 451 +#define _UNPACK_SEQUENCE 450 #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 _YIELD_VALUE YIELD_VALUE -#define MAX_UOP_ID 451 +#define MAX_UOP_ID 450 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 5aef6ba6825933..2a2d6e923b7617 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -244,7 +244,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_SET_IP] = 0, [_CHECK_STACK_SPACE_OPERAND] = HAS_DEOPT_FLAG, [_SAVE_RETURN_OFFSET] = HAS_ARG_FLAG, - [_EXIT_TRACE] = 0, + [_EXIT_TRACE] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, [_LOAD_CONST_INLINE] = HAS_PURE_FLAG, [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, @@ -253,7 +253,6 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_CONST_INLINE_BORROW_WITH_NULL] = HAS_PURE_FLAG, [_CHECK_FUNCTION] = HAS_DEOPT_FLAG, [_INTERNAL_INCREMENT_OPT_COUNTER] = 0, - [_COLD_EXIT] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, [_DYNAMIC_EXIT] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, [_START_EXECUTOR] = HAS_DEOPT_FLAG, [_FATAL_ERROR] = 0, @@ -325,7 +324,6 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_CHECK_STACK_SPACE_OPERAND] = "_CHECK_STACK_SPACE_OPERAND", [_CHECK_VALIDITY] = "_CHECK_VALIDITY", [_CHECK_VALIDITY_AND_SET_IP] = "_CHECK_VALIDITY_AND_SET_IP", - [_COLD_EXIT] = "_COLD_EXIT", [_COMPARE_OP] = "_COMPARE_OP", [_COMPARE_OP_FLOAT] = "_COMPARE_OP_FLOAT", [_COMPARE_OP_INT] = "_COMPARE_OP_INT", @@ -984,8 +982,6 @@ int _PyUop_num_popped(int opcode, int oparg) return 0; case _INTERNAL_INCREMENT_OPT_COUNTER: return 1; - case _COLD_EXIT: - return 0; case _DYNAMIC_EXIT: return 0; case _START_EXECUTOR: diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-24-08-39-23.gh-issue-116017.-Bw2UY.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-24-08-39-23.gh-issue-116017.-Bw2UY.rst new file mode 100644 index 00000000000000..3ca1b37f701e46 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-24-08-39-23.gh-issue-116017.-Bw2UY.rst @@ -0,0 +1,3 @@ +Simplify the warmup mechanism used for "side exits" in JIT code, resulting +in slightly better performance and slightly lower memory usage for most +platforms. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 50978a0dc87694..343481e9313de4 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4618,7 +4618,50 @@ dummy_func( } tier2 op(_EXIT_TRACE, (--)) { - EXIT_TO_TRACE(); + _PyExitData *exit = ¤t_executor->exits[oparg]; + PyCodeObject *code = _PyFrame_GetCode(frame); + _Py_CODEUNIT *target = _PyCode_CODE(code) + exit->target; + #if defined(Py_DEBUG) && !defined(_Py_JIT) + OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); + if (lltrace >= 2) { + printf("SIDE EXIT: [UOp "); + _PyUOpPrint(&next_uop[-1]); + printf(", exit %u, temp %d, target %d -> %s]\n", + oparg, exit->temperature.as_counter, + (int)(target - _PyCode_CODE(code)), + _PyOpcode_OpName[target->op.code]); + } + #endif + if (exit->executor == NULL) { + _Py_BackoffCounter temperature = exit->temperature; + if (!backoff_counter_triggers(temperature)) { + exit->temperature = advance_backoff_counter(temperature); + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_ONE(target); + } + _PyExecutorObject *executor; + if (target->op.code == ENTER_EXECUTOR) { + executor = code->co_executors->executors[target->op.arg]; + Py_INCREF(executor); + } + else { + int optimized = _PyOptimizer_Optimize(frame, target, stack_pointer, &executor); + if (optimized <= 0) { + exit->temperature = restart_backoff_counter(temperature); + if (optimized < 0) { + Py_DECREF(current_executor); + tstate->previous_executor = Py_None; + GOTO_UNWIND(); + } + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_ONE(target); + } + } + exit->executor = executor; + } + Py_INCREF(exit->executor); + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_TWO(exit->executor); } tier2 op(_CHECK_VALIDITY, (--)) { @@ -4659,47 +4702,21 @@ dummy_func( exe->count++; } - /* Only used for handling cold side exits, should never appear in - * a normal trace or as part of an instruction. - */ - tier2 op(_COLD_EXIT, (--)) { - _PyExecutorObject *previous = (_PyExecutorObject *)tstate->previous_executor; - _PyExitData *exit = &previous->exits[oparg]; - PyCodeObject *code = _PyFrame_GetCode(frame); - _Py_CODEUNIT *target = _PyCode_CODE(code) + exit->target; - _Py_BackoffCounter temperature = exit->temperature; - if (!backoff_counter_triggers(temperature)) { - exit->temperature = advance_backoff_counter(temperature); - GOTO_TIER_ONE(target); - } - _PyExecutorObject *executor; - if (target->op.code == ENTER_EXECUTOR) { - executor = code->co_executors->executors[target->op.arg]; - Py_INCREF(executor); - } - else { - int optimized = _PyOptimizer_Optimize(frame, target, stack_pointer, &executor); - if (optimized <= 0) { - exit->temperature = restart_backoff_counter(temperature); - if (optimized < 0) { - Py_DECREF(previous); - tstate->previous_executor = Py_None; - GOTO_UNWIND(); - } - GOTO_TIER_ONE(target); - } - } - /* We need two references. One to store in exit->executor and - * one to keep the executor alive when executing. */ - Py_INCREF(executor); - exit->executor = executor; - GOTO_TIER_TWO(executor); - } - tier2 op(_DYNAMIC_EXIT, (--)) { tstate->previous_executor = (PyObject *)current_executor; _PyExitData *exit = (_PyExitData *)¤t_executor->exits[oparg]; _Py_CODEUNIT *target = frame->instr_ptr; + #if defined(Py_DEBUG) && !defined(_Py_JIT) + OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); + if (lltrace >= 2) { + printf("DYNAMIC EXIT: [UOp "); + _PyUOpPrint(&next_uop[-1]); + printf(", exit %u, temp %d, target %d -> %s]\n", + oparg, exit->temperature.as_counter, + (int)(target - _PyCode_CODE(_PyFrame_GetCode(frame))), + _PyOpcode_OpName[target->op.code]); + } + #endif _PyExecutorObject *executor; if (target->op.code == ENTER_EXECUTOR) { PyCodeObject *code = (PyCodeObject *)frame->f_executable; diff --git a/Python/ceval.c b/Python/ceval.c index f4b3a417025c14..a71244676f3029 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1054,13 +1054,13 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int uint64_t trace_uop_execution_counter = 0; #endif - assert(next_uop->opcode == _START_EXECUTOR || next_uop->opcode == _COLD_EXIT); + assert(next_uop->opcode == _START_EXECUTOR); tier2_dispatch: for (;;) { uopcode = next_uop->opcode; #ifdef Py_DEBUG if (lltrace >= 3) { - if (next_uop->opcode == _START_EXECUTOR || next_uop->opcode == _COLD_EXIT) { + if (next_uop->opcode == _START_EXECUTOR) { printf("%4d uop: ", 0); } else { @@ -1148,25 +1148,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int tstate->previous_executor = NULL; DISPATCH(); -exit_to_trace: - assert(next_uop[-1].format == UOP_FORMAT_EXIT); - OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); - uint32_t exit_index = next_uop[-1].exit_index; - assert(exit_index < current_executor->exit_count); - _PyExitData *exit = ¤t_executor->exits[exit_index]; -#ifdef Py_DEBUG - if (lltrace >= 2) { - printf("SIDE EXIT: [UOp "); - _PyUOpPrint(&next_uop[-1]); - printf(", exit %u, temp %d, target %d -> %s]\n", - exit_index, exit->temperature.as_counter, exit->target, - _PyOpcode_OpName[_PyCode_CODE(_PyFrame_GetCode(frame))[exit->target].op.code]); - } -#endif - Py_INCREF(exit->executor); - tstate->previous_executor = (PyObject *)current_executor; - GOTO_TIER_TWO(exit->executor); - #endif // _Py_JIT #endif // _Py_TIER2 diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index f6d055a1dfaa9f..595b72bfaf9613 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -426,7 +426,7 @@ do { \ do { \ OPT_STAT_INC(traces_executed); \ next_uop = (EXECUTOR)->trace; \ - assert(next_uop->opcode == _START_EXECUTOR || next_uop->opcode == _COLD_EXIT); \ + assert(next_uop->opcode == _START_EXECUTOR); \ goto enter_tier_two; \ } while (0) #endif @@ -446,7 +446,6 @@ do { \ #define JUMP_TO_JUMP_TARGET() goto jump_to_jump_target #define JUMP_TO_ERROR() goto jump_to_error_target #define GOTO_UNWIND() goto error_tier_two -#define EXIT_TO_TRACE() goto exit_to_trace #define EXIT_TO_TIER1() goto exit_to_tier1 #define EXIT_TO_TIER1_DYNAMIC() goto exit_to_tier1_dynamic; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 76b7a9b4b15ae9..d70a57a9a8ffbe 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -4824,7 +4824,51 @@ } case _EXIT_TRACE: { - EXIT_TO_TRACE(); + oparg = CURRENT_OPARG(); + _PyExitData *exit = ¤t_executor->exits[oparg]; + PyCodeObject *code = _PyFrame_GetCode(frame); + _Py_CODEUNIT *target = _PyCode_CODE(code) + exit->target; + #if defined(Py_DEBUG) && !defined(_Py_JIT) + OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); + if (lltrace >= 2) { + printf("SIDE EXIT: [UOp "); + _PyUOpPrint(&next_uop[-1]); + printf(", exit %u, temp %d, target %d -> %s]\n", + oparg, exit->temperature.as_counter, + (int)(target - _PyCode_CODE(code)), + _PyOpcode_OpName[target->op.code]); + } + #endif + if (exit->executor == NULL) { + _Py_BackoffCounter temperature = exit->temperature; + if (!backoff_counter_triggers(temperature)) { + exit->temperature = advance_backoff_counter(temperature); + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_ONE(target); + } + _PyExecutorObject *executor; + if (target->op.code == ENTER_EXECUTOR) { + executor = code->co_executors->executors[target->op.arg]; + Py_INCREF(executor); + } + else { + int optimized = _PyOptimizer_Optimize(frame, target, stack_pointer, &executor); + if (optimized <= 0) { + exit->temperature = restart_backoff_counter(temperature); + if (optimized < 0) { + Py_DECREF(current_executor); + tstate->previous_executor = Py_None; + GOTO_UNWIND(); + } + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_ONE(target); + } + } + exit->executor = executor; + } + Py_INCREF(exit->executor); + tstate->previous_executor = (PyObject *)current_executor; + GOTO_TIER_TWO(exit->executor); break; } @@ -4913,47 +4957,22 @@ break; } - case _COLD_EXIT: { - oparg = CURRENT_OPARG(); - _PyExecutorObject *previous = (_PyExecutorObject *)tstate->previous_executor; - _PyExitData *exit = &previous->exits[oparg]; - PyCodeObject *code = _PyFrame_GetCode(frame); - _Py_CODEUNIT *target = _PyCode_CODE(code) + exit->target; - _Py_BackoffCounter temperature = exit->temperature; - if (!backoff_counter_triggers(temperature)) { - exit->temperature = advance_backoff_counter(temperature); - GOTO_TIER_ONE(target); - } - _PyExecutorObject *executor; - if (target->op.code == ENTER_EXECUTOR) { - executor = code->co_executors->executors[target->op.arg]; - Py_INCREF(executor); - } - else { - int optimized = _PyOptimizer_Optimize(frame, target, stack_pointer, &executor); - if (optimized <= 0) { - exit->temperature = restart_backoff_counter(temperature); - if (optimized < 0) { - Py_DECREF(previous); - tstate->previous_executor = Py_None; - GOTO_UNWIND(); - } - GOTO_TIER_ONE(target); - } - } - /* We need two references. One to store in exit->executor and - * one to keep the executor alive when executing. */ - Py_INCREF(executor); - exit->executor = executor; - GOTO_TIER_TWO(executor); - break; - } - case _DYNAMIC_EXIT: { oparg = CURRENT_OPARG(); tstate->previous_executor = (PyObject *)current_executor; _PyExitData *exit = (_PyExitData *)¤t_executor->exits[oparg]; _Py_CODEUNIT *target = frame->instr_ptr; + #if defined(Py_DEBUG) && !defined(_Py_JIT) + OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); + if (lltrace >= 2) { + printf("DYNAMIC EXIT: [UOp "); + _PyUOpPrint(&next_uop[-1]); + printf(", exit %u, temp %d, target %d -> %s]\n", + oparg, exit->temperature.as_counter, + (int)(target - _PyCode_CODE(_PyFrame_GetCode(frame))), + _PyOpcode_OpName[target->op.code]); + } + #endif _PyExecutorObject *executor; if (target->op.code == ENTER_EXECUTOR) { PyCodeObject *code = (PyCodeObject *)frame->f_executable; diff --git a/Python/jit.c b/Python/jit.c index d0c0d24f4539e2..33320761621c4c 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -439,7 +439,7 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz group->emit(code, data, executor, NULL, instruction_starts); code += group->code_size; data += group->data_size; - assert(trace[0].opcode == _START_EXECUTOR || trace[0].opcode == _COLD_EXIT); + assert(trace[0].opcode == _START_EXECUTOR); for (size_t i = 0; i < length; i++) { const _PyUOpInstruction *instruction = &trace[i]; group = &stencil_groups[instruction->opcode]; diff --git a/Python/optimizer.c b/Python/optimizer.c index f7387dc0b27532..561ec4efa4ee2a 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -144,18 +144,6 @@ _Py_GetOptimizer(void) static _PyExecutorObject * make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFilter *dependencies); -static int -init_cold_exit_executor(_PyExecutorObject *executor, int oparg); - -/* It is impossible for the number of exits to reach 1/4 of the total length, - * as the number of exits cannot reach 1/3 of the number of non-exits, due to - * the presence of CHECK_VALIDITY checks and instructions to produce the values - * being checked in exits. */ -#define COLD_EXIT_COUNT (UOP_MAX_TRACE_LENGTH/4) - -static int cold_exits_initialized = 0; -static _PyExecutorObject COLD_EXITS[COLD_EXIT_COUNT] = { 0 }; - static const _PyBloomFilter EMPTY_FILTER = { 0 }; _PyOptimizerObject * @@ -164,14 +152,6 @@ _Py_SetOptimizer(PyInterpreterState *interp, _PyOptimizerObject *optimizer) if (optimizer == NULL) { optimizer = &_PyOptimizer_Default; } - else if (cold_exits_initialized == 0) { - cold_exits_initialized = 1; - for (int i = 0; i < COLD_EXIT_COUNT; i++) { - if (init_cold_exit_executor(&COLD_EXITS[i], i)) { - return NULL; - } - } - } _PyOptimizerObject *old = interp->optimizer; if (old == NULL) { old = &_PyOptimizer_Default; @@ -317,12 +297,6 @@ _PyUOpPrint(const _PyUOpInstruction *uop) uop->jump_target, (uint64_t)uop->operand); break; - case UOP_FORMAT_EXIT: - printf(" (%d, exit_index=%d, operand=%#" PRIx64, - uop->oparg, - uop->exit_index, - (uint64_t)uop->operand); - break; default: printf(" (%d, Unknown format)", uop->oparg); } @@ -1094,7 +1068,7 @@ sanity_check(_PyExecutorObject *executor) } bool ended = false; uint32_t i = 0; - CHECK(executor->trace[0].opcode == _START_EXECUTOR || executor->trace[0].opcode == _COLD_EXIT); + CHECK(executor->trace[0].opcode == _START_EXECUTOR); for (; i < executor->code_size; i++) { const _PyUOpInstruction *inst = &executor->trace[i]; uint16_t opcode = inst->opcode; @@ -1104,22 +1078,15 @@ sanity_check(_PyExecutorObject *executor) case UOP_FORMAT_TARGET: CHECK(target_unused(opcode)); break; - case UOP_FORMAT_EXIT: - CHECK(opcode == _EXIT_TRACE); - CHECK(inst->exit_index < executor->exit_count); - break; case UOP_FORMAT_JUMP: CHECK(inst->jump_target < executor->code_size); break; - case UOP_FORMAT_UNUSED: - CHECK(0); - break; } if (_PyUop_Flags[opcode] & HAS_ERROR_FLAG) { CHECK(inst->format == UOP_FORMAT_JUMP); CHECK(inst->error_target < executor->code_size); } - if (opcode == _JUMP_TO_TOP || opcode == _EXIT_TRACE || opcode == _COLD_EXIT) { + if (opcode == _JUMP_TO_TOP || opcode == _EXIT_TRACE) { ended = true; i++; break; @@ -1133,9 +1100,6 @@ sanity_check(_PyExecutorObject *executor) opcode == _DEOPT || opcode == _EXIT_TRACE || opcode == _ERROR_POP_N); - if (opcode == _EXIT_TRACE) { - CHECK(inst->format == UOP_FORMAT_EXIT); - } } } @@ -1157,9 +1121,8 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil } /* Initialize exits */ - assert(exit_count < COLD_EXIT_COUNT); for (int i = 0; i < exit_count; i++) { - executor->exits[i].executor = &COLD_EXITS[i]; + executor->exits[i].executor = NULL; executor->exits[i].temperature = initial_temperature_backoff_counter(); } int next_exit = exit_count-1; @@ -1173,8 +1136,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil assert(opcode != _POP_JUMP_IF_FALSE && opcode != _POP_JUMP_IF_TRUE); if (opcode == _EXIT_TRACE) { executor->exits[next_exit].target = buffer[i].target; - dest->exit_index = next_exit; - dest->format = UOP_FORMAT_EXIT; + dest->oparg = next_exit; next_exit--; } if (opcode == _DYNAMIC_EXIT) { @@ -1216,36 +1178,6 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil return executor; } -static int -init_cold_exit_executor(_PyExecutorObject *executor, int oparg) -{ - _Py_SetImmortalUntracked((PyObject *)executor); - Py_SET_TYPE(executor, &_PyUOpExecutor_Type); - executor->trace = (_PyUOpInstruction *)executor->exits; - executor->code_size = 1; - executor->exit_count = 0; - _PyUOpInstruction *inst = (_PyUOpInstruction *)&executor->trace[0]; - inst->opcode = _COLD_EXIT; - inst->oparg = oparg; - executor->vm_data.valid = true; - executor->vm_data.linked = false; - for (int i = 0; i < _Py_BLOOM_FILTER_WORDS; i++) { - assert(executor->vm_data.bloom.bits[i] == 0); - } -#ifdef Py_DEBUG - sanity_check(executor); -#endif -#ifdef _Py_JIT - executor->jit_code = NULL; - executor->jit_side_entry = NULL; - executor->jit_size = 0; - if (_PyJIT_Compile(executor, executor->trace, 1)) { - return -1; - } -#endif - return 0; -} - #ifdef Py_STATS /* Returns the effective trace length. * Ignores NOPs and trailing exit and error handling.*/ @@ -1258,8 +1190,7 @@ int effective_trace_length(_PyUOpInstruction *buffer, int length) nop_count++; } if (opcode == _EXIT_TRACE || - opcode == _JUMP_TO_TOP || - opcode == _COLD_EXIT) { + opcode == _JUMP_TO_TOP) { return i+1-nop_count; } } @@ -1624,13 +1555,8 @@ executor_clear(_PyExecutorObject *executor) */ Py_INCREF(executor); for (uint32_t i = 0; i < executor->exit_count; i++) { - const _PyExecutorObject *cold = &COLD_EXITS[i]; - const _PyExecutorObject *side = executor->exits[i].executor; executor->exits[i].temperature = initial_unreachable_backoff_counter(); - if (side != cold) { - executor->exits[i].executor = cold; - Py_DECREF(side); - } + Py_CLEAR(executor->exits[i].executor); } _Py_ExecutorDetach(executor); Py_DECREF(executor); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index a414b04fb6a5b1..978aa911b52efc 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2180,10 +2180,6 @@ break; } - case _COLD_EXIT: { - break; - } - case _DYNAMIC_EXIT: { break; } diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 466f25daa14dc6..63b640e465ac6b 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -388,10 +388,8 @@ Python/optimizer.c - _PyUOpExecutor_Type - Python/optimizer.c - _PyUOpOptimizer_Type - Python/optimizer.c - _PyOptimizer_Default - Python/optimizer.c - _ColdExit_Type - -Python/optimizer.c - COLD_EXITS - Python/optimizer.c - Py_FatalErrorExecutor - Python/optimizer.c - EMPTY_FILTER - -Python/optimizer.c - cold_exits_initialized - ##----------------------- ## test code diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py index 755649dea54570..ede5a9930e7316 100644 --- a/Tools/jit/_stencils.py +++ b/Tools/jit/_stencils.py @@ -40,8 +40,6 @@ class HoleValue(enum.Enum): JUMP_TARGET = enum.auto() # The base address of the machine code for the error jump target (exposed as _JIT_ERROR_TARGET): ERROR_TARGET = enum.auto() - # The index of the exit to be jumped through (exposed as _JIT_EXIT_INDEX): - EXIT_INDEX = enum.auto() # A hardcoded value of zero (used for symbol lookups): ZERO = enum.auto() @@ -107,7 +105,6 @@ class HoleValue(enum.Enum): HoleValue.TARGET: "instruction->target", HoleValue.JUMP_TARGET: "instruction_starts[instruction->jump_target]", HoleValue.ERROR_TARGET: "instruction_starts[instruction->error_target]", - HoleValue.EXIT_INDEX: "instruction->exit_index", HoleValue.ZERO: "", } diff --git a/Tools/jit/template.c b/Tools/jit/template.c index 2bcbf8d615bd8a..ec7d033e89deff 100644 --- a/Tools/jit/template.c +++ b/Tools/jit/template.c @@ -103,7 +103,6 @@ _JIT_ENTRY(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState uint64_t _operand = ((uint64_t)_operand_hi << 32) | _operand_lo; #endif PATCH_VALUE(uint32_t, _target, _JIT_TARGET) - PATCH_VALUE(uint16_t, _exit_index, _JIT_EXIT_INDEX) OPT_STAT_INC(uops_executed); UOP_STAT_INC(uopcode, execution_count); @@ -126,11 +125,4 @@ _JIT_ENTRY(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState exit_to_tier1_dynamic: tstate->previous_executor = (PyObject *)current_executor; GOTO_TIER_ONE(frame->instr_ptr); -exit_to_trace: - { - _PyExitData *exit = ¤t_executor->exits[_exit_index]; - Py_INCREF(exit->executor); - tstate->previous_executor = (PyObject *)current_executor; - GOTO_TIER_TWO(exit->executor); - } } From 1dc9a4f6b20148fd4ef2eb2800a6c65224828181 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 1 Jul 2024 23:27:04 +0300 Subject: [PATCH 07/41] gh-121196: Document `dict.fromkeys` params as pos-only (#121197) --- Doc/library/stdtypes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 34f6e44babe596..54cc7d1333d34e 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -4565,7 +4565,7 @@ can be used interchangeably to index the same dictionary entry. Return a shallow copy of the dictionary. - .. classmethod:: fromkeys(iterable, value=None) + .. classmethod:: fromkeys(iterable, value=None, /) Create a new dictionary with keys from *iterable* and values set to *value*. From 966260841b69480708f8efa1f8f5738b45fca68d Mon Sep 17 00:00:00 2001 From: Diego Russo Date: Mon, 1 Jul 2024 23:52:33 +0100 Subject: [PATCH 08/41] GH-119726: Use LDR for AArch64 trampolines (GH-121001) --- ...-06-25-16-26-44.gh-issue-119726.WqvHxB.rst | 2 ++ Tools/jit/_stencils.py | 31 ++++++------------- 2 files changed, 11 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-25-16-26-44.gh-issue-119726.WqvHxB.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-25-16-26-44.gh-issue-119726.WqvHxB.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-25-16-26-44.gh-issue-119726.WqvHxB.rst new file mode 100644 index 00000000000000..2e5132f61e504f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-25-16-26-44.gh-issue-119726.WqvHxB.rst @@ -0,0 +1,2 @@ +Improve the speed and memory use of C function calls from JIT code on AArch64. +Patch by Diego Russo diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py index ede5a9930e7316..68eb1d13394170 100644 --- a/Tools/jit/_stencils.py +++ b/Tools/jit/_stencils.py @@ -204,33 +204,20 @@ def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> None: return self.disassembly += [ - f"{base + 4 * 0:x}: d2800008 mov x8, #0x0", - f"{base + 4 * 0:016x}: R_AARCH64_MOVW_UABS_G0_NC {hole.symbol}", - f"{base + 4 * 1:x}: f2a00008 movk x8, #0x0, lsl #16", - f"{base + 4 * 1:016x}: R_AARCH64_MOVW_UABS_G1_NC {hole.symbol}", - f"{base + 4 * 2:x}: f2c00008 movk x8, #0x0, lsl #32", - f"{base + 4 * 2:016x}: R_AARCH64_MOVW_UABS_G2_NC {hole.symbol}", - f"{base + 4 * 3:x}: f2e00008 movk x8, #0x0, lsl #48", - f"{base + 4 * 3:016x}: R_AARCH64_MOVW_UABS_G3 {hole.symbol}", - f"{base + 4 * 4:x}: d61f0100 br x8", + f"{base + 4 * 0:x}: 58000048 ldr x8, 8", + f"{base + 4 * 1:x}: d61f0100 br x8", + f"{base + 4 * 2:x}: 00000000", + f"{base + 4 * 2:016x}: R_AARCH64_ABS64 {hole.symbol}", + f"{base + 4 * 3:x}: 00000000", ] for code in [ - 0xD2800008.to_bytes(4, sys.byteorder), - 0xF2A00008.to_bytes(4, sys.byteorder), - 0xF2C00008.to_bytes(4, sys.byteorder), - 0xF2E00008.to_bytes(4, sys.byteorder), + 0x58000048.to_bytes(4, sys.byteorder), 0xD61F0100.to_bytes(4, sys.byteorder), + 0x00000000.to_bytes(4, sys.byteorder), + 0x00000000.to_bytes(4, sys.byteorder), ]: self.body.extend(code) - for i, kind in enumerate( - [ - "R_AARCH64_MOVW_UABS_G0_NC", - "R_AARCH64_MOVW_UABS_G1_NC", - "R_AARCH64_MOVW_UABS_G2_NC", - "R_AARCH64_MOVW_UABS_G3", - ] - ): - self.holes.append(hole.replace(offset=base + 4 * i, kind=kind)) + self.holes.append(hole.replace(offset=base + 8, kind="R_AARCH64_ABS64")) self.trampolines[hole.symbol] = base def remove_jump(self, *, alignment: int = 1) -> None: From 4f1e1dff8928e626924ee1ff70a77af1a14f0f2a Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Mon, 1 Jul 2024 21:36:27 -0300 Subject: [PATCH 09/41] Fix phrasing in paragraphs with leading "similar" (#121135) --- Doc/c-api/dict.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/dict.rst b/Doc/c-api/dict.rst index b4fdf47bc915bd..ce73fa0cc60ebb 100644 --- a/Doc/c-api/dict.rst +++ b/Doc/c-api/dict.rst @@ -156,7 +156,7 @@ Dictionary Objects .. c:function:: int PyDict_GetItemStringRef(PyObject *p, const char *key, PyObject **result) - Similar than :c:func:`PyDict_GetItemRef`, but *key* is specified as a + Similar to :c:func:`PyDict_GetItemRef`, but *key* is specified as a :c:expr:`const char*` UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`. @@ -206,7 +206,7 @@ Dictionary Objects ``NULL``, and return ``0``. - On error, raise an exception and return ``-1``. - This is similar to :meth:`dict.pop`, but without the default value and + Similar to :meth:`dict.pop`, but without the default value and not raising :exc:`KeyError` if the key missing. .. versionadded:: 3.13 From bfe0e4d7696647a546110328510bdb98146ad2f2 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Tue, 2 Jul 2024 09:13:37 +0100 Subject: [PATCH 10/41] gh-121035: Improve logging flow diagram for dark/light modes. (GH-121254) --- Doc/howto/logging.rst | 38 ++++++++++++++++++++++++++++++++++++++ Doc/howto/logging_flow.svg | 35 ++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/Doc/howto/logging.rst b/Doc/howto/logging.rst index 316b16aa796af4..9c55233e910f17 100644 --- a/Doc/howto/logging.rst +++ b/Doc/howto/logging.rst @@ -385,6 +385,44 @@ following diagram. .. raw:: html :file: logging_flow.svg +.. raw:: html + + + Loggers ^^^^^^^ diff --git a/Doc/howto/logging_flow.svg b/Doc/howto/logging_flow.svg index a5f656b1df0b42..9807323b7190d6 100644 --- a/Doc/howto/logging_flow.svg +++ b/Doc/howto/logging_flow.svg @@ -1,9 +1,9 @@ @@ -57,7 +69,7 @@ Create - LogRecord + LogRecord @@ -100,7 +112,7 @@ - Pass to + Pass record to handlers of current logger @@ -135,16 +147,17 @@ - - Use lastResort - handler + + Use + lastResort + handler Handler enabled for - level of LogRecord? + level of record? @@ -292,7 +305,7 @@ Yes - LogRecord passed + Record passed to handler From 7435f053b4a54372a2c43dee7a15c4b973f09209 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 2 Jul 2024 10:34:13 +0200 Subject: [PATCH 11/41] Move get_signal_name() to test.support (#121251) * Move get_signal_name() from test.libregrtest to test.support. * Use get_signal_name() in support.script_helper. * support.script_helper now decodes stdout and stderr from UTF-8, instead of ASCII, if a command failed. --- Lib/test/libregrtest/run_workers.py | 4 ++-- Lib/test/libregrtest/utils.py | 29 ----------------------- Lib/test/support/__init__.py | 32 +++++++++++++++++++++++++ Lib/test/support/script_helper.py | 36 +++++++++++++++-------------- Lib/test/test_regrtest.py | 10 -------- Lib/test/test_support.py | 12 ++++++++++ 6 files changed, 65 insertions(+), 58 deletions(-) diff --git a/Lib/test/libregrtest/run_workers.py b/Lib/test/libregrtest/run_workers.py index a71050e66db3bd..387ddf9614cf79 100644 --- a/Lib/test/libregrtest/run_workers.py +++ b/Lib/test/libregrtest/run_workers.py @@ -22,7 +22,7 @@ from .single import PROGRESS_MIN_TIME from .utils import ( StrPath, TestName, - format_duration, print_warning, count, plural, get_signal_name) + format_duration, print_warning, count, plural) from .worker import create_worker_process, USE_PROCESS_GROUP if MS_WINDOWS: @@ -366,7 +366,7 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult: err_msg=None, state=State.TIMEOUT) if retcode != 0: - name = get_signal_name(retcode) + name = support.get_signal_name(retcode) if name: retcode = f"{retcode} ({name})" raise WorkerError(self.test_name, f"Exit code {retcode}", stdout, diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 0167742d388a2c..0197e50125d96e 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -685,35 +685,6 @@ def cleanup_temp_dir(tmp_dir: StrPath): print("Remove file: %s" % name) os_helper.unlink(name) -WINDOWS_STATUS = { - 0xC0000005: "STATUS_ACCESS_VIOLATION", - 0xC00000FD: "STATUS_STACK_OVERFLOW", - 0xC000013A: "STATUS_CONTROL_C_EXIT", -} - -def get_signal_name(exitcode): - if exitcode < 0: - signum = -exitcode - try: - return signal.Signals(signum).name - except ValueError: - pass - - # Shell exit code (ex: WASI build) - if 128 < exitcode < 256: - signum = exitcode - 128 - try: - return signal.Signals(signum).name - except ValueError: - pass - - try: - return WINDOWS_STATUS[exitcode] - except KeyError: - pass - - return None - ILLEGAL_XML_CHARS_RE = re.compile( '[' diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index dbea070929be9b..18455bb6e0ff52 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2632,3 +2632,35 @@ def initialized_with_pyrepl(): """Detect whether PyREPL was used during Python initialization.""" # If the main module has a __file__ attribute it's a Python module, which means PyREPL. return hasattr(sys.modules["__main__"], "__file__") + + +WINDOWS_STATUS = { + 0xC0000005: "STATUS_ACCESS_VIOLATION", + 0xC00000FD: "STATUS_STACK_OVERFLOW", + 0xC000013A: "STATUS_CONTROL_C_EXIT", +} + +def get_signal_name(exitcode): + import signal + + if exitcode < 0: + signum = -exitcode + try: + return signal.Signals(signum).name + except ValueError: + pass + + # Shell exit code (ex: WASI build) + if 128 < exitcode < 256: + signum = exitcode - 128 + try: + return signal.Signals(signum).name + except ValueError: + pass + + try: + return WINDOWS_STATUS[exitcode] + except KeyError: + pass + + return None diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py index 65e0bc199e7f0b..d0be3179b0efa3 100644 --- a/Lib/test/support/script_helper.py +++ b/Lib/test/support/script_helper.py @@ -70,23 +70,25 @@ def fail(self, cmd_line): out = b'(... truncated stdout ...)' + out[-maxlen:] if len(err) > maxlen: err = b'(... truncated stderr ...)' + err[-maxlen:] - out = out.decode('ascii', 'replace').rstrip() - err = err.decode('ascii', 'replace').rstrip() - raise AssertionError("Process return code is %d\n" - "command line: %r\n" - "\n" - "stdout:\n" - "---\n" - "%s\n" - "---\n" - "\n" - "stderr:\n" - "---\n" - "%s\n" - "---" - % (self.rc, cmd_line, - out, - err)) + out = out.decode('utf8', 'replace').rstrip() + err = err.decode('utf8', 'replace').rstrip() + + exitcode = self.rc + signame = support.get_signal_name(exitcode) + if signame: + exitcode = f"{exitcode} ({signame})" + raise AssertionError(f"Process return code is {exitcode}\n" + f"command line: {cmd_line!r}\n" + f"\n" + f"stdout:\n" + f"---\n" + f"{out}\n" + f"---\n" + f"\n" + f"stderr:\n" + f"---\n" + f"{err}\n" + f"---") # Executing the interpreter in a subprocess diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index 44fd11bfdc3fcb..d4f4a69a7a38c1 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -2329,16 +2329,6 @@ def test_normalize_test_name(self): self.assertIsNone(normalize('setUpModule (test.test_x)', is_error=True)) self.assertIsNone(normalize('tearDownModule (test.test_module)', is_error=True)) - def test_get_signal_name(self): - for exitcode, expected in ( - (-int(signal.SIGINT), 'SIGINT'), - (-int(signal.SIGSEGV), 'SIGSEGV'), - (128 + int(signal.SIGABRT), 'SIGABRT'), - (3221225477, "STATUS_ACCESS_VIOLATION"), - (0xC00000FD, "STATUS_STACK_OVERFLOW"), - ): - self.assertEqual(utils.get_signal_name(exitcode), expected, exitcode) - def test_format_resources(self): format_resources = utils.format_resources ALL_RESOURCES = utils.ALL_RESOURCES diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index d6f024a476920c..e60e5477d32e1f 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -3,6 +3,7 @@ import io import os import shutil +import signal import socket import stat import subprocess @@ -732,6 +733,17 @@ def test_copy_python_src_ignore(self): self.assertEqual(support.copy_python_src_ignore(path, os.listdir(path)), ignored) + def test_get_signal_name(self): + for exitcode, expected in ( + (-int(signal.SIGINT), 'SIGINT'), + (-int(signal.SIGSEGV), 'SIGSEGV'), + (128 + int(signal.SIGABRT), 'SIGABRT'), + (3221225477, "STATUS_ACCESS_VIOLATION"), + (0xC00000FD, "STATUS_STACK_OVERFLOW"), + ): + self.assertEqual(support.get_signal_name(exitcode), expected, + exitcode) + # XXX -follows a list of untested API # make_legacy_pyc # is_resource_enabled From 7a807c3efaa83f1e4fb9b791579b47a0a1fd47de Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 2 Jul 2024 12:40:01 +0300 Subject: [PATCH 12/41] gh-121245: Amend d611c4c8e9 (correct import) (#121255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Miro Hrončok --- Lib/site.py | 3 +-- .../Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst diff --git a/Lib/site.py b/Lib/site.py index 9381f6f510eb46..daa56e158949db 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -526,8 +526,7 @@ def register_readline(): def write_history(): try: - # _pyrepl.__main__ is executed as the __main__ module - from __main__ import CAN_USE_PYREPL + from _pyrepl.main import CAN_USE_PYREPL except ImportError: CAN_USE_PYREPL = False diff --git a/Misc/NEWS.d/next/Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst b/Misc/NEWS.d/next/Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst new file mode 100644 index 00000000000000..6e9dec2545166f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-02-11-34-06.gh-issue-121245.sSkDAr.rst @@ -0,0 +1,2 @@ +Fix a bug in the handling of the command history of the new :term:`REPL` that caused +the history file to be wiped at REPL exit. From 15232a0819a2f7e0f448f28f2e6081912d10e7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:53:17 +0200 Subject: [PATCH 13/41] gh-121210: handle nodes with missing attributes/fields in `ast.compare` (#121211) --- Lib/ast.py | 19 +++++++++++++++---- Lib/test/test_ast.py | 19 +++++++++++++++++++ ...-07-01-11-23-18.gh-issue-121210.cD0zfn.rst | 2 ++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst diff --git a/Lib/ast.py b/Lib/ast.py index fb4d21b87d8bd0..a954d4a97d3c22 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -422,6 +422,8 @@ def compare( might differ in whitespace or similar details. """ + sentinel = object() # handle the possibility of a missing attribute/field + def _compare(a, b): # Compare two fields on an AST object, which may themselves be # AST objects, lists of AST objects, or primitive ASDL types @@ -449,8 +451,14 @@ def _compare_fields(a, b): if a._fields != b._fields: return False for field in a._fields: - a_field = getattr(a, field) - b_field = getattr(b, field) + a_field = getattr(a, field, sentinel) + b_field = getattr(b, field, sentinel) + if a_field is sentinel and b_field is sentinel: + # both nodes are missing a field at runtime + continue + if a_field is sentinel or b_field is sentinel: + # one of the node is missing a field + return False if not _compare(a_field, b_field): return False else: @@ -461,8 +469,11 @@ def _compare_attributes(a, b): return False # Attributes are always ints. for attr in a._attributes: - a_attr = getattr(a, attr) - b_attr = getattr(b, attr) + a_attr = getattr(a, attr, sentinel) + b_attr = getattr(b, attr, sentinel) + if a_attr is sentinel and b_attr is sentinel: + # both nodes are missing an attribute at runtime + continue if a_attr != b_attr: return False else: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 5d71d524516df2..fbd19620311159 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -948,6 +948,15 @@ def test_compare_fieldless(self): self.assertTrue(ast.compare(ast.Add(), ast.Add())) self.assertFalse(ast.compare(ast.Sub(), ast.Add())) + # test that missing runtime fields is handled in ast.compare() + a1, a2 = ast.Name('a'), ast.Name('a') + self.assertTrue(ast.compare(a1, a2)) + self.assertTrue(ast.compare(a1, a2)) + del a1.id + self.assertFalse(ast.compare(a1, a2)) + del a2.id + self.assertTrue(ast.compare(a1, a2)) + def test_compare_modes(self): for mode, sources in ( ("exec", exec_tests), @@ -970,6 +979,16 @@ def parse(a, b): self.assertTrue(ast.compare(a, b, compare_attributes=False)) self.assertFalse(ast.compare(a, b, compare_attributes=True)) + def test_compare_attributes_option_missing_attribute(self): + # test that missing runtime attributes is handled in ast.compare() + a1, a2 = ast.Name('a', lineno=1), ast.Name('a', lineno=1) + self.assertTrue(ast.compare(a1, a2)) + self.assertTrue(ast.compare(a1, a2, compare_attributes=True)) + del a1.lineno + self.assertFalse(ast.compare(a1, a2, compare_attributes=True)) + del a2.lineno + self.assertTrue(ast.compare(a1, a2, compare_attributes=True)) + def test_positional_only_feature_version(self): ast.parse('def foo(x, /): ...', feature_version=(3, 8)) ast.parse('def bar(x=1, /): ...', feature_version=(3, 8)) diff --git a/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst b/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst new file mode 100644 index 00000000000000..55d5b221bf0765 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-01-11-23-18.gh-issue-121210.cD0zfn.rst @@ -0,0 +1,2 @@ +Handle AST nodes with missing runtime fields or attributes in +:func:`ast.compare`. Patch by Bénédikt Tran. From 6343486eb60ac5a9e15402a592298259c5afdee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:57:51 +0200 Subject: [PATCH 14/41] gh-121165: protect macro expansion of `ADJUST_INDICES` with do-while(0) (#121166) --- Objects/bytes_methods.c | 31 ++++++++++++++++++------------- Objects/unicodeobject.c | 31 ++++++++++++++++++------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/Objects/bytes_methods.c b/Objects/bytes_methods.c index 55252406578774..c239ae18a593e3 100644 --- a/Objects/bytes_methods.c +++ b/Objects/bytes_methods.c @@ -432,19 +432,24 @@ parse_args_finds_byte(const char *function_name, PyObject **subobj, char *byte) } /* helper macro to fixup start/end slice values */ -#define ADJUST_INDICES(start, end, len) \ - if (end > len) \ - end = len; \ - else if (end < 0) { \ - end += len; \ - if (end < 0) \ - end = 0; \ - } \ - if (start < 0) { \ - start += len; \ - if (start < 0) \ - start = 0; \ - } +#define ADJUST_INDICES(start, end, len) \ + do { \ + if (end > len) { \ + end = len; \ + } \ + else if (end < 0) { \ + end += len; \ + if (end < 0) { \ + end = 0; \ + } \ + } \ + if (start < 0) { \ + start += len; \ + if (start < 0) { \ + start = 0; \ + } \ + } \ + } while (0) Py_LOCAL_INLINE(Py_ssize_t) find_internal(const char *str, Py_ssize_t len, diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 9738442ab962b0..394ea888fc9231 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -9315,19 +9315,24 @@ _PyUnicode_TransformDecimalAndSpaceToASCII(PyObject *unicode) /* --- Helpers ------------------------------------------------------------ */ /* helper macro to fixup start/end slice values */ -#define ADJUST_INDICES(start, end, len) \ - if (end > len) \ - end = len; \ - else if (end < 0) { \ - end += len; \ - if (end < 0) \ - end = 0; \ - } \ - if (start < 0) { \ - start += len; \ - if (start < 0) \ - start = 0; \ - } +#define ADJUST_INDICES(start, end, len) \ + do { \ + if (end > len) { \ + end = len; \ + } \ + else if (end < 0) { \ + end += len; \ + if (end < 0) { \ + end = 0; \ + } \ + } \ + if (start < 0) { \ + start += len; \ + if (start < 0) { \ + start = 0; \ + } \ + } \ + } while (0) static Py_ssize_t any_find_slice(PyObject* s1, PyObject* s2, From 1ac273224a85126c4356e355f7445206fadde7ec Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:22:08 +0100 Subject: [PATCH 15/41] gh-121272: move __future__ import validation from compiler to symtable (#121273) --- Python/compile.c | 16 ---------------- Python/symtable.c | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 69de0ec2996e00..d33db69f425361 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -77,14 +77,6 @@ typedef struct _PyCfgBuilder cfg_builder; #define LOCATION(LNO, END_LNO, COL, END_COL) \ ((const _Py_SourceLocation){(LNO), (END_LNO), (COL), (END_COL)}) -/* Return true if loc1 starts after loc2 ends. */ -static inline bool -location_is_after(location loc1, location loc2) { - return (loc1.lineno > loc2.end_lineno) || - ((loc1.lineno == loc2.end_lineno) && - (loc1.col_offset > loc2.end_col_offset)); -} - #define LOC(x) SRC_LOCATION_FROM_AST(x) typedef _PyJumpTargetLabel jump_target_label; @@ -3847,14 +3839,6 @@ compiler_from_import(struct compiler *c, stmt_ty s) PyTuple_SET_ITEM(names, i, Py_NewRef(alias->name)); } - if (location_is_after(LOC(s), c->c_future.ff_location) && - s->v.ImportFrom.module && s->v.ImportFrom.level == 0 && - _PyUnicode_EqualToASCIIString(s->v.ImportFrom.module, "__future__")) - { - Py_DECREF(names); - return compiler_error(c, LOC(s), "from __future__ imports must occur " - "at the beginning of the file"); - } ADDOP_LOAD_CONST_NEW(c, LOC(s), names); if (s->v.ImportFrom.module) { diff --git a/Python/symtable.c b/Python/symtable.c index 2e56ea6e830846..61fa5c6fdf923c 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1660,6 +1660,27 @@ has_kwonlydefaults(asdl_arg_seq *kwonlyargs, asdl_expr_seq *kw_defaults) return 0; } +static int +check_import_from(struct symtable *st, stmt_ty s) +{ + assert(s->kind == ImportFrom_kind); + _Py_SourceLocation fut = st->st_future->ff_location; + if (s->v.ImportFrom.module && s->v.ImportFrom.level == 0 && + _PyUnicode_EqualToASCIIString(s->v.ImportFrom.module, "__future__") && + ((s->lineno > fut.lineno) || + ((s->lineno == fut.end_lineno) && (s->col_offset > fut.end_col_offset)))) + { + PyErr_SetString(PyExc_SyntaxError, + "from __future__ imports must occur " + "at the beginning of the file"); + PyErr_RangedSyntaxLocationObject(st->st_filename, + s->lineno, s->col_offset + 1, + s->end_lineno, s->end_col_offset + 1); + return 0; + } + return 1; +} + static int symtable_visit_stmt(struct symtable *st, stmt_ty s) { @@ -1914,6 +1935,9 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) break; case ImportFrom_kind: VISIT_SEQ(st, alias, s->v.ImportFrom.names); + if (!check_import_from(st, s)) { + VISIT_QUIT(st, 0); + } break; case Global_kind: { Py_ssize_t i; From 8e8d202f552c993f40913b628139a39a5abe6a03 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 2 Jul 2024 12:30:14 -0400 Subject: [PATCH 16/41] gh-117139: Add _PyTuple_FromStackRefSteal and use it (#121244) Avoids the extra conversion from stack refs to PyObjects. --- Include/internal/pycore_stackref.h | 2 +- Include/internal/pycore_tuple.h | 1 + Objects/tupleobject.c | 21 +++++++++++++++++++++ Python/bytecodes.c | 8 +------- Python/ceval.c | 8 +------- Python/executor_cases.c.h | 10 +--------- Python/generated_cases.c.h | 10 +--------- Tools/cases_generator/analyzer.py | 1 + 8 files changed, 28 insertions(+), 33 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 32e445dd96f9a1..4301c6a7cb40b0 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -48,7 +48,7 @@ extern "C" { CPython refcounting operations on it! */ -typedef union { +typedef union _PyStackRef { uintptr_t bits; } _PyStackRef; diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index 14a9e42c3a324c..dfbbd6fd0c7de5 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -21,6 +21,7 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *); #define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item) extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t); +PyAPI_FUNC(PyObject *)_PyTuple_FromStackRefSteal(const union _PyStackRef *, Py_ssize_t); PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); typedef struct { diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 5ae1ee9a89af84..994258f20b495d 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -390,6 +390,27 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n) return (PyObject *)tuple; } +PyObject * +_PyTuple_FromStackRefSteal(const _PyStackRef *src, Py_ssize_t n) +{ + if (n == 0) { + return tuple_get_empty(); + } + PyTupleObject *tuple = tuple_alloc(n); + if (tuple == NULL) { + for (Py_ssize_t i = 0; i < n; i++) { + PyStackRef_CLOSE(src[i]); + } + return NULL; + } + PyObject **dst = tuple->ob_item; + for (Py_ssize_t i = 0; i < n; i++) { + dst[i] = PyStackRef_AsPyObjectSteal(src[i]); + } + _PyObject_GC_TRACK(tuple); + return (PyObject *)tuple; +} + PyObject * _PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 343481e9313de4..76587a4f0dc695 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1780,13 +1780,7 @@ dummy_func( } inst(BUILD_TUPLE, (values[oparg] -- tup)) { - STACKREFS_TO_PYOBJECTS(values, oparg, values_o); - if (CONVERSION_FAILED(values_o)) { - DECREF_INPUTS(); - ERROR_IF(true, error); - } - PyObject *tup_o = _PyTuple_FromArraySteal(values_o, oparg); - STACKREFS_TO_PYOBJECTS_CLEANUP(values_o); + PyObject *tup_o = _PyTuple_FromStackRefSteal(values, oparg); ERROR_IF(tup_o == NULL, error); tup = PyStackRef_FromPyObjectSteal(tup_o); } diff --git a/Python/ceval.c b/Python/ceval.c index a71244676f3029..a240ed4321f7ee 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1500,13 +1500,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func, u = (PyObject *)&_Py_SINGLETON(tuple_empty); } else { - assert(args != NULL); - STACKREFS_TO_PYOBJECTS((_PyStackRef *)args, argcount, args_o); - if (args_o == NULL) { - goto fail_pre_positional; - } - u = _PyTuple_FromArraySteal((args_o + n), argcount - n); - STACKREFS_TO_PYOBJECTS_CLEANUP(args_o); + u = _PyTuple_FromStackRefSteal(args + n, argcount - n); } if (u == NULL) { goto fail_post_positional; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index d70a57a9a8ffbe..3b999465aac815 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1816,15 +1816,7 @@ _PyStackRef tup; oparg = CURRENT_OPARG(); values = &stack_pointer[-oparg]; - STACKREFS_TO_PYOBJECTS(values, oparg, values_o); - if (CONVERSION_FAILED(values_o)) { - for (int _i = oparg; --_i >= 0;) { - PyStackRef_CLOSE(values[_i]); - } - if (true) JUMP_TO_ERROR(); - } - PyObject *tup_o = _PyTuple_FromArraySteal(values_o, oparg); - STACKREFS_TO_PYOBJECTS_CLEANUP(values_o); + PyObject *tup_o = _PyTuple_FromStackRefSteal(values, oparg); if (tup_o == NULL) JUMP_TO_ERROR(); tup = PyStackRef_FromPyObjectSteal(tup_o); stack_pointer[-oparg] = tup; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 32b22aff14a768..61057221291c0a 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -769,15 +769,7 @@ _PyStackRef *values; _PyStackRef tup; values = &stack_pointer[-oparg]; - STACKREFS_TO_PYOBJECTS(values, oparg, values_o); - if (CONVERSION_FAILED(values_o)) { - for (int _i = oparg; --_i >= 0;) { - PyStackRef_CLOSE(values[_i]); - } - if (true) { stack_pointer += -oparg; goto error; } - } - PyObject *tup_o = _PyTuple_FromArraySteal(values_o, oparg); - STACKREFS_TO_PYOBJECTS_CLEANUP(values_o); + PyObject *tup_o = _PyTuple_FromStackRefSteal(values, oparg); if (tup_o == NULL) { stack_pointer += -oparg; goto error; } tup = PyStackRef_FromPyObjectSteal(tup_o); stack_pointer[-oparg] = tup; diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 6b1af1b59f14d8..f92560bd2b76b3 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -431,6 +431,7 @@ def has_error_without_pop(op: parser.InstDef) -> bool: "CONVERSION_FAILED", "_PyList_FromArraySteal", "_PyTuple_FromArraySteal", + "_PyTuple_FromStackRefSteal", ) ESCAPING_FUNCTIONS = ( From b180788d4a927d23af54f4b4702ccaf254f64854 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Tue, 2 Jul 2024 18:54:33 +0100 Subject: [PATCH 17/41] gh-115773: Add sizes to debug offset structure (#120112) --- Include/internal/pycore_runtime.h | 10 ++++++++++ Include/internal/pycore_runtime_init.h | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index f58eccf729cb2a..341fe29a68af16 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -55,12 +55,14 @@ typedef struct _Py_DebugOffsets { uint64_t version; // Runtime state offset; struct _runtime_state { + uint64_t size; uint64_t finalizing; uint64_t interpreters_head; } runtime_state; // Interpreter state offset; struct _interpreter_state { + uint64_t size; uint64_t next; uint64_t threads_head; uint64_t gc; @@ -74,6 +76,7 @@ typedef struct _Py_DebugOffsets { // Thread state offset; struct _thread_state{ + uint64_t size; uint64_t prev; uint64_t next; uint64_t interp; @@ -84,6 +87,7 @@ typedef struct _Py_DebugOffsets { // InterpreterFrame offset; struct _interpreter_frame { + uint64_t size; uint64_t previous; uint64_t executable; uint64_t instr_ptr; @@ -93,12 +97,14 @@ typedef struct _Py_DebugOffsets { // CFrame offset; struct _cframe { + uint64_t size; uint64_t current_frame; uint64_t previous; } cframe; // Code object offset; struct _code_object { + uint64_t size; uint64_t filename; uint64_t name; uint64_t linetable; @@ -111,21 +117,25 @@ typedef struct _Py_DebugOffsets { // PyObject offset; struct _pyobject { + uint64_t size; uint64_t ob_type; } pyobject; // PyTypeObject object offset; struct _type_object { + uint64_t size; uint64_t tp_name; } type_object; // PyTuple object offset; struct _tuple_object { + uint64_t size; uint64_t ob_item; } tuple_object; // Unicode object offset; struct _unicode_object { + uint64_t size; uint64_t state; uint64_t length; size_t asciiobject_size; diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 98920dbb7c7a92..33e39c2edbe541 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -35,10 +35,12 @@ extern PyTypeObject _PyExc_MemoryError; .cookie = "xdebugpy", \ .version = PY_VERSION_HEX, \ .runtime_state = { \ + .size = sizeof(_PyRuntimeState), \ .finalizing = offsetof(_PyRuntimeState, _finalizing), \ .interpreters_head = offsetof(_PyRuntimeState, interpreters.head), \ }, \ .interpreter_state = { \ + .size = sizeof(PyInterpreterState), \ .next = offsetof(PyInterpreterState, next), \ .threads_head = offsetof(PyInterpreterState, threads.head), \ .gc = offsetof(PyInterpreterState, gc), \ @@ -50,6 +52,7 @@ extern PyTypeObject _PyExc_MemoryError; .gil_runtime_state_holder = offsetof(PyInterpreterState, _gil.last_holder), \ }, \ .thread_state = { \ + .size = sizeof(PyThreadState), \ .prev = offsetof(PyThreadState, prev), \ .next = offsetof(PyThreadState, next), \ .interp = offsetof(PyThreadState, interp), \ @@ -58,6 +61,7 @@ extern PyTypeObject _PyExc_MemoryError; .native_thread_id = offsetof(PyThreadState, native_thread_id), \ }, \ .interpreter_frame = { \ + .size = sizeof(_PyInterpreterFrame), \ .previous = offsetof(_PyInterpreterFrame, previous), \ .executable = offsetof(_PyInterpreterFrame, f_executable), \ .instr_ptr = offsetof(_PyInterpreterFrame, instr_ptr), \ @@ -65,6 +69,7 @@ extern PyTypeObject _PyExc_MemoryError; .owner = offsetof(_PyInterpreterFrame, owner), \ }, \ .code_object = { \ + .size = sizeof(PyCodeObject), \ .filename = offsetof(PyCodeObject, co_filename), \ .name = offsetof(PyCodeObject, co_name), \ .linetable = offsetof(PyCodeObject, co_linetable), \ @@ -75,15 +80,19 @@ extern PyTypeObject _PyExc_MemoryError; .co_code_adaptive = offsetof(PyCodeObject, co_code_adaptive), \ }, \ .pyobject = { \ + .size = sizeof(PyObject), \ .ob_type = offsetof(PyObject, ob_type), \ }, \ .type_object = { \ + .size = sizeof(PyTypeObject), \ .tp_name = offsetof(PyTypeObject, tp_name), \ }, \ .tuple_object = { \ + .size = sizeof(PyTupleObject), \ .ob_item = offsetof(PyTupleObject, ob_item), \ }, \ .unicode_object = { \ + .size = sizeof(PyUnicodeObject), \ .state = offsetof(PyUnicodeObject, _base._base.state), \ .length = offsetof(PyUnicodeObject, _base._base.length), \ .asciiobject_size = sizeof(PyASCIIObject), \ From 089835469d5efbea4793cd611b43cb8387f2e7e5 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Tue, 2 Jul 2024 18:57:34 +0100 Subject: [PATCH 18/41] gh-121035: Further improve logging flow diagram with respect to dark/light modes. (GH-121265) --- Doc/howto/logging.rst | 6 ++++-- Doc/howto/logging_flow.svg | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Doc/howto/logging.rst b/Doc/howto/logging.rst index 9c55233e910f17..cbfe93319ddaa4 100644 --- a/Doc/howto/logging.rst +++ b/Doc/howto/logging.rst @@ -403,11 +403,13 @@ following diagram. function updateBody(theme) { let elem = document.body; + elem.classList.remove('dark-theme'); + elem.classList.remove('light-theme'); if (theme === 'dark') { elem.classList.add('dark-theme'); } - else { - elem.classList.remove('dark-theme'); + else if (theme === 'light') { + elem.classList.add('light-theme'); } } diff --git a/Doc/howto/logging_flow.svg b/Doc/howto/logging_flow.svg index 9807323b7190d6..4974994ac6b400 100644 --- a/Doc/howto/logging_flow.svg +++ b/Doc/howto/logging_flow.svg @@ -46,6 +46,7 @@ filter: invert(100%) hue-rotate(180deg) saturate(1.25); } } + /* These rules are for when the theme selector is used, perhaps in contrast to the browser theme. */ body.dark-theme polygon, body.dark-theme rect, body.dark-theme polyline, body.dark-theme line { stroke: #ffffff; } @@ -55,6 +56,15 @@ body.dark-theme text { fill: #ffffff; } + body.light-theme polygon, body.light-theme rect, body.light-theme polyline, body.light-theme line { + stroke: #000000; + } + body.light-theme polygon.filled { + fill: #000000; + } + body.light-theme text { + fill: #000000; + } From f09d184821efd9438d092643881e28bdf8de4de5 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Wed, 3 Jul 2024 04:30:29 +0100 Subject: [PATCH 19/41] GH-73991: Support copying directory symlinks on older Windows (#120807) Check for `ERROR_INVALID_PARAMETER` when calling `_winapi.CopyFile2()` and raise `UnsupportedOperation`. In `Path.copy()`, handle this exception and fall back to the `PathBase.copy()` implementation. --- Doc/library/pathlib.rst | 5 ----- Lib/pathlib/__init__.py | 4 ++-- Lib/pathlib/_abc.py | 11 +-------- Lib/pathlib/_local.py | 19 +++++++++------- Lib/pathlib/_os.py | 27 ++++++++++++++++++++--- Lib/test/test_pathlib/test_pathlib_abc.py | 3 ++- 6 files changed, 40 insertions(+), 29 deletions(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 0918bbb47e9ea6..d7fd56f4c4ff7f 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1554,11 +1554,6 @@ Copying, renaming and deleting permissions. After the copy is complete, users may wish to call :meth:`Path.chmod` to set the permissions of the target file. - .. warning:: - On old builds of Windows (before Windows 10 build 19041), this method - raises :exc:`OSError` when a symlink to a directory is encountered and - *follow_symlinks* is false. - .. versionadded:: 3.14 diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 4b3edf535a61aa..2298a249529460 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -5,8 +5,8 @@ operating systems. """ -from ._abc import * +from ._os import * from ._local import * -__all__ = (_abc.__all__ + +__all__ = (_os.__all__ + _local.__all__) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 71973913921169..b5f903ec1f03ce 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -16,10 +16,7 @@ import posixpath from glob import _GlobberBase, _no_recurse_symlinks from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO -from ._os import copyfileobj - - -__all__ = ["UnsupportedOperation"] +from ._os import UnsupportedOperation, copyfileobj @functools.cache @@ -27,12 +24,6 @@ def _is_case_sensitive(parser): return parser.normcase('Aa') == 'Aa' -class UnsupportedOperation(NotImplementedError): - """An exception that is raised when an unsupported operation is called on - a path object. - """ - pass - class ParserBase: """Base class for path parsers, which do low-level path manipulation. diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index 0105ea3042422e..acb57214b81865 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -17,8 +17,8 @@ except ImportError: grp = None -from ._abc import UnsupportedOperation, PurePathBase, PathBase -from ._os import copyfile +from ._os import UnsupportedOperation, copyfile +from ._abc import PurePathBase, PathBase __all__ = [ @@ -791,12 +791,15 @@ def copy(self, target, follow_symlinks=True): try: target = os.fspath(target) except TypeError: - if isinstance(target, PathBase): - # Target is an instance of PathBase but not os.PathLike. - # Use generic implementation from PathBase. - return PathBase.copy(self, target, follow_symlinks=follow_symlinks) - raise - copyfile(os.fspath(self), target, follow_symlinks) + if not isinstance(target, PathBase): + raise + else: + try: + copyfile(os.fspath(self), target, follow_symlinks) + return + except UnsupportedOperation: + pass # Fall through to generic code. + PathBase.copy(self, target, follow_symlinks=follow_symlinks) def chmod(self, mode, *, follow_symlinks=True): """ diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index bbb019b6534503..61923b5e410b5c 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -20,6 +20,15 @@ _winapi = None +__all__ = ["UnsupportedOperation"] + + +class UnsupportedOperation(NotImplementedError): + """An exception that is raised when an unsupported operation is attempted. + """ + pass + + def get_copy_blocksize(infd): """Determine blocksize for fastcopying on Linux. Hopefully the whole file will be copied in a single call. @@ -106,18 +115,30 @@ def copyfile(source, target, follow_symlinks): Copy from one file to another using CopyFile2 (Windows only). """ if follow_symlinks: - flags = 0 + _winapi.CopyFile2(source, target, 0) else: + # Use COPY_FILE_COPY_SYMLINK to copy a file symlink. flags = _winapi.COPY_FILE_COPY_SYMLINK try: _winapi.CopyFile2(source, target, flags) return except OSError as err: # Check for ERROR_ACCESS_DENIED - if err.winerror != 5 or not _is_dirlink(source): + if err.winerror == 5 and _is_dirlink(source): + pass + else: raise + + # Add COPY_FILE_DIRECTORY to copy a directory symlink. flags |= _winapi.COPY_FILE_DIRECTORY - _winapi.CopyFile2(source, target, flags) + try: + _winapi.CopyFile2(source, target, flags) + except OSError as err: + # Check for ERROR_INVALID_PARAMETER + if err.winerror == 87: + raise UnsupportedOperation(err) from None + else: + raise else: copyfile = None diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index ad692e872ede0b..28c9664cc90fe1 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -5,7 +5,8 @@ import stat import unittest -from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase +from pathlib._os import UnsupportedOperation +from pathlib._abc import ParserBase, PurePathBase, PathBase import posixpath from test.support import is_wasi From ff5806c78edda1feed61254ac55193772dc9ec48 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 3 Jul 2024 09:02:15 +0300 Subject: [PATCH 20/41] gh-121027: Make the functools.partial object a method descriptor (GH-121089) Co-authored-by: d.grigonis --- Doc/whatsnew/3.14.rst | 6 +++ Lib/functools.py | 10 ++--- Lib/test/test_functools.py | 4 +- Lib/test/test_inspect/test_inspect.py | 38 ++++++++----------- ...-06-27-12-27-52.gh-issue-121027.D4K1OX.rst | 1 + Modules/_functoolsmodule.c | 9 +---- 6 files changed, 28 insertions(+), 40 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 6ebadd75092fac..9578ba0c9c9657 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -305,6 +305,12 @@ Porting to Python 3.14 This section lists previously described changes and other bugfixes that may require changes to your code. +Changes in the Python API +------------------------- + +* :class:`functools.partial` is now a method descriptor. + Wrap it in :func:`staticmethod` if you want to preserve the old behavior. + (Contributed by Serhiy Storchaka and Dominykas Grigonis in :gh:`121027`.) Build Changes ============= diff --git a/Lib/functools.py b/Lib/functools.py index d04957c555295e..a10493f0e25360 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -18,6 +18,7 @@ from collections import namedtuple # import types, weakref # Deferred to single_dispatch() from reprlib import recursive_repr +from types import MethodType from _thread import RLock # Avoid importing types, so we can speedup import time @@ -314,12 +315,7 @@ def __repr__(self): def __get__(self, obj, objtype=None): if obj is None: return self - import warnings - warnings.warn('functools.partial will be a method descriptor in ' - 'future Python versions; wrap it in staticmethod() ' - 'if you want to preserve the old behavior', - FutureWarning, 2) - return self + return MethodType(self, obj) def __reduce__(self): return type(self), (self.func,), (self.func, self.args, @@ -402,7 +398,7 @@ def _method(cls_or_self, /, *args, **keywords): def __get__(self, obj, cls=None): get = getattr(self.func, "__get__", None) result = None - if get is not None and not isinstance(self.func, partial): + if get is not None: new_func = get(obj, cls) if new_func is not self.func: # Assume __get__ returning something new indicates the diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 1ce0f4d0aea6ee..492a16a8c7ff45 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -405,9 +405,7 @@ class A: self.assertEqual(A.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) self.assertEqual(A.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4})) self.assertEqual(A.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) - with self.assertWarns(FutureWarning) as w: - self.assertEqual(a.meth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) - self.assertEqual(w.filename, __file__) + self.assertEqual(a.meth(3, b=4), ((1, a, 3), {'a': 2, 'b': 4})) self.assertEqual(a.cmeth(3, b=4), ((1, A, 3), {'a': 2, 'b': 4})) self.assertEqual(a.smeth(3, b=4), ((1, 3), {'a': 2, 'b': 4})) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 308c09874fe2ac..d39c3ccdc847bd 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -3868,17 +3868,15 @@ def __init__(self, b): with self.subTest('partial'): class CM(type): - __call__ = functools.partial(lambda x, a: (x, a), 2) + __call__ = functools.partial(lambda x, a, b: (x, a, b), 2) class C(metaclass=CM): - def __init__(self, b): + def __init__(self, c): pass - with self.assertWarns(FutureWarning): - self.assertEqual(C(1), (2, 1)) - with self.assertWarns(FutureWarning): - self.assertEqual(self.signature(C), - ((('a', ..., ..., "positional_or_keyword"),), - ...)) + self.assertEqual(C(1), (2, C, 1)) + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) with self.subTest('partialmethod'): class CM(type): @@ -4024,14 +4022,12 @@ class C: with self.subTest('partial'): class C: - __init__ = functools.partial(lambda x, a: None, 2) + __init__ = functools.partial(lambda x, a, b: None, 2) - with self.assertWarns(FutureWarning): - C(1) # does not raise - with self.assertWarns(FutureWarning): - self.assertEqual(self.signature(C), - ((('a', ..., ..., "positional_or_keyword"),), - ...)) + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) with self.subTest('partialmethod'): class C: @@ -4284,15 +4280,13 @@ class C: with self.subTest('partial'): class C: - __call__ = functools.partial(lambda x, a: (x, a), 2) + __call__ = functools.partial(lambda x, a, b: (x, a, b), 2) c = C() - with self.assertWarns(FutureWarning): - self.assertEqual(c(1), (2, 1)) - with self.assertWarns(FutureWarning): - self.assertEqual(self.signature(c), - ((('a', ..., ..., "positional_or_keyword"),), - ...)) + self.assertEqual(c(1), (2, c, 1)) + self.assertEqual(self.signature(C()), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) with self.subTest('partialmethod'): class C: diff --git a/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst b/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst new file mode 100644 index 00000000000000..a450726d9afed9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-27-12-27-52.gh-issue-121027.D4K1OX.rst @@ -0,0 +1 @@ +Make the :class:`functools.partial` object a method descriptor. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 564c271915959a..64766b474514bf 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -203,14 +203,7 @@ partial_descr_get(PyObject *self, PyObject *obj, PyObject *type) if (obj == Py_None || obj == NULL) { return Py_NewRef(self); } - if (PyErr_WarnEx(PyExc_FutureWarning, - "functools.partial will be a method descriptor in " - "future Python versions; wrap it in staticmethod() " - "if you want to preserve the old behavior", 1) < 0) - { - return NULL; - } - return Py_NewRef(self); + return PyMethod_New(self, obj); } /* Merging keyword arguments using the vectorcall convention is messy, so From 705a123898f1394b62076c00ab6008c18fd8e115 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 3 Jul 2024 15:35:05 +0800 Subject: [PATCH 21/41] gh-116181: Remove Py_BUILD_CORE_BUILTIN and Py_BUILD_CORE_MODULE in rotatingtree.c (#121260) --- Modules/rotatingtree.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Modules/rotatingtree.c b/Modules/rotatingtree.c index 217e495b3d2a9d..5910e25bed6389 100644 --- a/Modules/rotatingtree.c +++ b/Modules/rotatingtree.c @@ -1,9 +1,4 @@ -#ifndef Py_BUILD_CORE_BUILTIN -# define Py_BUILD_CORE_MODULE 1 -#endif - #include "Python.h" -#include "pycore_lock.h" #include "rotatingtree.h" #define KEY_LOWER_THAN(key1, key2) ((char*)(key1) < (char*)(key2)) From ff5751a208e05f9d054b6df44f7651b64d415908 Mon Sep 17 00:00:00 2001 From: AN Long Date: Wed, 3 Jul 2024 15:46:57 +0800 Subject: [PATCH 22/41] gh-111872: Document the max_children attribute for `socketserver.ForkingMixIn` (#118134) --- Doc/library/socketserver.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst index f1f87ea975ca42..69f06e6cf4d923 100644 --- a/Doc/library/socketserver.rst +++ b/Doc/library/socketserver.rst @@ -126,6 +126,12 @@ server is the address family. waits until all non-daemon threads complete, except if :attr:`block_on_close` attribute is ``False``. + .. attribute:: max_children + + Specify how many child processes will exist to handle requests at a time + for :class:`ForkingMixIn`. If the limit is reached, + new requests will wait until one child process has finished. + .. attribute:: daemon_threads For :class:`ThreadingMixIn` use daemonic threads by setting From f65d17bf471ae5932ebd8baacedca83bfc85bdb1 Mon Sep 17 00:00:00 2001 From: byundojin <103907292+byundojin@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:51:25 +0900 Subject: [PATCH 23/41] updated tp_flags initialization to use inplace or (#120625) --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b042e64a188d9d..447e561c0d4440 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -8395,7 +8395,7 @@ type_ready(PyTypeObject *type, int initial) } /* All done -- set the ready flag */ - type->tp_flags = type->tp_flags | Py_TPFLAGS_READY; + type->tp_flags |= Py_TPFLAGS_READY; stop_readying(type); assert(_PyType_CheckConsistency(type)); From f49c83aa6683915fe6ba0a1177c3d4e873429703 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:22:59 +0530 Subject: [PATCH 24/41] build(deps): bump hypothesis from 6.100.2 to 6.104.2 in /Tools (#121218) Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.100.2 to 6.104.2. - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.100.2...hypothesis-python-6.104.2) --- updated-dependencies: - dependency-name: hypothesis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Tools/requirements-hypothesis.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/requirements-hypothesis.txt b/Tools/requirements-hypothesis.txt index 9d5a18c881bf36..ab3f39ac6ee087 100644 --- a/Tools/requirements-hypothesis.txt +++ b/Tools/requirements-hypothesis.txt @@ -1,4 +1,4 @@ # Requirements file for hypothesis that # we use to run our property-based tests in CI. -hypothesis==6.100.2 +hypothesis==6.104.2 From 4232976b02cb999335c6bfdec3315520b21954f2 Mon Sep 17 00:00:00 2001 From: da-woods Date: Wed, 3 Jul 2024 09:05:02 +0100 Subject: [PATCH 25/41] docs: Fix "Py_TPFLAGS_MANAGED_WEAKREF is set in tp_flags" (#112237) --- Doc/c-api/typeobj.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index c9ef076c78c66a..0091e084308245 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -1592,7 +1592,7 @@ and :c:data:`PyType_Type` effectively act as defaults.) weak references to the type object itself. It is an error to set both the :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` bit and - :c:member:`~PyTypeObject.tp_weaklist`. + :c:member:`~PyTypeObject.tp_weaklistoffset`. **Inheritance:** @@ -1604,7 +1604,7 @@ and :c:data:`PyType_Type` effectively act as defaults.) **Default:** If the :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF` bit is set in the - :c:member:`~PyTypeObject.tp_dict` field, then + :c:member:`~PyTypeObject.tp_flags` field, then :c:member:`~PyTypeObject.tp_weaklistoffset` will be set to a negative value, to indicate that it is unsafe to use this field. From 9d3c9b822ce3c52cd747efe93b172f02c0d09289 Mon Sep 17 00:00:00 2001 From: Amin Alaee Date: Wed, 3 Jul 2024 10:10:57 +0200 Subject: [PATCH 26/41] Docs: Add `os.splice` flags argument (#109847) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Co-authored-by: Blaise Pabon --- Doc/library/os.rst | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 8d95d01fe55ed9..2878d425310d75 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1724,10 +1724,27 @@ or `the MSDN `_ on Windo Added support for pipes on Windows. -.. function:: splice(src, dst, count, offset_src=None, offset_dst=None) +.. function:: splice(src, dst, count, offset_src=None, offset_dst=None, flags=0) Transfer *count* bytes from file descriptor *src*, starting from offset *offset_src*, to file descriptor *dst*, starting from offset *offset_dst*. + + The splicing behaviour can be modified by specifying a *flags* value. + Any of the following variables may used, combined using bitwise OR + (the ``|`` operator): + + * If :const:`SPLICE_F_MOVE` is specified, + the kernel is asked to move pages instead of copying, + but pages may still be copied if the kernel cannot move the pages from the pipe. + + * If :const:`SPLICE_F_NONBLOCK` is specified, + the kernel is asked to not block on I/O. + This makes the splice pipe operations nonblocking, + but splice may nevertheless block because the spliced file descriptors may block. + + * If :const:`SPLICE_F_MORE` is specified, + it hints to the kernel that more data will be coming in a subsequent splice. + At least one of the file descriptors must refer to a pipe. If *offset_src* is ``None``, then *src* is read from the current position; respectively for *offset_dst*. The offset associated to the file descriptor that refers to a @@ -1746,6 +1763,8 @@ or `the MSDN `_ on Windo make sense to block because there are no writers connected to the write end of the pipe. + .. seealso:: The :manpage:`splice(2)` man page. + .. availability:: Linux >= 2.6.17 with glibc >= 2.5 .. versionadded:: 3.10 From c9bdfbe86853fcf5f2b7dce3a50b383e23384ed2 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 3 Jul 2024 09:53:44 +0100 Subject: [PATCH 27/41] gh-106597: Add more offsets to _Py_DebugOffsets (#121311) Add more offsets to _Py_DebugOffsets We add a few more offsets that are required by some out-of-process tools, such as [Austin](https://github.com/p403n1x87/austin). --- Include/internal/pycore_runtime.h | 10 ++++++++++ Include/internal/pycore_runtime_init.h | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 341fe29a68af16..bc67377a89c17f 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -63,6 +63,7 @@ typedef struct _Py_DebugOffsets { // Interpreter state offset; struct _interpreter_state { uint64_t size; + uint64_t id; uint64_t next; uint64_t threads_head; uint64_t gc; @@ -83,6 +84,8 @@ typedef struct _Py_DebugOffsets { uint64_t current_frame; uint64_t thread_id; uint64_t native_thread_id; + uint64_t datastack_chunk; + uint64_t status; } thread_state; // InterpreterFrame offset; @@ -107,6 +110,7 @@ typedef struct _Py_DebugOffsets { uint64_t size; uint64_t filename; uint64_t name; + uint64_t qualname; uint64_t linetable; uint64_t firstlineno; uint64_t argcount; @@ -140,6 +144,12 @@ typedef struct _Py_DebugOffsets { uint64_t length; size_t asciiobject_size; } unicode_object; + + // GC runtime state offset; + struct _gc { + uint64_t size; + uint64_t collecting; + } gc; } _Py_DebugOffsets; /* Reference tracer state */ diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 33e39c2edbe541..da2b8d5570de62 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -41,6 +41,7 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .interpreter_state = { \ .size = sizeof(PyInterpreterState), \ + .id = offsetof(PyInterpreterState, id), \ .next = offsetof(PyInterpreterState, next), \ .threads_head = offsetof(PyInterpreterState, threads.head), \ .gc = offsetof(PyInterpreterState, gc), \ @@ -59,6 +60,8 @@ extern PyTypeObject _PyExc_MemoryError; .current_frame = offsetof(PyThreadState, current_frame), \ .thread_id = offsetof(PyThreadState, thread_id), \ .native_thread_id = offsetof(PyThreadState, native_thread_id), \ + .datastack_chunk = offsetof(PyThreadState, datastack_chunk), \ + .status = offsetof(PyThreadState, _status), \ }, \ .interpreter_frame = { \ .size = sizeof(_PyInterpreterFrame), \ @@ -72,6 +75,7 @@ extern PyTypeObject _PyExc_MemoryError; .size = sizeof(PyCodeObject), \ .filename = offsetof(PyCodeObject, co_filename), \ .name = offsetof(PyCodeObject, co_name), \ + .qualname = offsetof(PyCodeObject, co_qualname), \ .linetable = offsetof(PyCodeObject, co_linetable), \ .firstlineno = offsetof(PyCodeObject, co_firstlineno), \ .argcount = offsetof(PyCodeObject, co_argcount), \ @@ -97,6 +101,10 @@ extern PyTypeObject _PyExc_MemoryError; .length = offsetof(PyUnicodeObject, _base._base.length), \ .asciiobject_size = sizeof(PyASCIIObject), \ }, \ + .gc = { \ + .size = sizeof(struct _gc_runtime_state), \ + .collecting = offsetof(struct _gc_runtime_state, collecting), \ + }, \ }, \ .allocators = { \ .standard = _pymem_allocators_standard_INIT(runtime), \ From 51c4a324c037fb2e31640202243fd1c8b33800d5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 3 Jul 2024 12:08:11 +0300 Subject: [PATCH 28/41] gh-61103: Support float and long double complex types in ctypes module (#121248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This amends 6988ff02a5: memory allocation for stginfo->ffi_type_pointer.elements in PyCSimpleType_init() should be more generic (perhaps someday fmt->pffi_type->elements will be not a two-elements array). It should finally resolve #61103. Co-authored-by: Victor Stinner Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/ctypes.rst | 20 ++++++++ Lib/ctypes/__init__.py | 4 ++ Lib/test/test_ctypes/test_libc.py | 14 ++++++ Lib/test/test_ctypes/test_numbers.py | 10 ++-- ...4-06-23-07-23-08.gh-issue-61103.ca_U_l.rst | 8 ++-- Modules/_complex.h | 20 ++++++++ Modules/_ctypes/_ctypes.c | 7 +-- Modules/_ctypes/_ctypes_test.c | 10 ++++ Modules/_ctypes/callproc.c | 2 + Modules/_ctypes/cfield.c | 48 +++++++++++++++++++ Modules/_ctypes/ctypes.h | 2 + 11 files changed, 135 insertions(+), 10 deletions(-) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index a56e0eef5d11b1..e3d74d7dc0d91c 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -272,8 +272,12 @@ complex types are available: +----------------------------------+---------------------------------+-----------------+ | ctypes type | C type | Python type | +==================================+=================================+=================+ +| :class:`c_float_complex` | :c:expr:`float complex` | complex | ++----------------------------------+---------------------------------+-----------------+ | :class:`c_double_complex` | :c:expr:`double complex` | complex | +----------------------------------+---------------------------------+-----------------+ +| :class:`c_longdouble_complex` | :c:expr:`long double complex` | complex | ++----------------------------------+---------------------------------+-----------------+ All these types can be created by calling them with an optional initializer of @@ -2302,6 +2306,22 @@ These are the fundamental ctypes data types: .. versionadded:: 3.14 +.. class:: c_float_complex + + Represents the C :c:expr:`float complex` datatype, if available. The + constructor accepts an optional :class:`complex` initializer. + + .. versionadded:: 3.14 + + +.. class:: c_longdouble_complex + + Represents the C :c:expr:`long double complex` datatype, if available. The + constructor accepts an optional :class:`complex` initializer. + + .. versionadded:: 3.14 + + .. class:: c_int Represents the C :c:expr:`signed int` datatype. The constructor accepts an diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index d2e6a8bfc8c9d4..721522caeeac92 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -208,6 +208,10 @@ class c_longdouble(_SimpleCData): try: class c_double_complex(_SimpleCData): _type_ = "C" + class c_float_complex(_SimpleCData): + _type_ = "E" + class c_longdouble_complex(_SimpleCData): + _type_ = "F" except AttributeError: pass diff --git a/Lib/test/test_ctypes/test_libc.py b/Lib/test/test_ctypes/test_libc.py index dec0afff4b38fd..cab3cc9f46003a 100644 --- a/Lib/test/test_ctypes/test_libc.py +++ b/Lib/test/test_ctypes/test_libc.py @@ -33,6 +33,20 @@ def test_csqrt(self): self.assertAlmostEqual(lib.my_csqrt(-1-0.01j), 0.004999937502734214-1.0000124996093955j) + lib.my_csqrtf.argtypes = ctypes.c_float_complex, + lib.my_csqrtf.restype = ctypes.c_float_complex + self.assertAlmostEqual(lib.my_csqrtf(-1+0.01j), + 0.004999937502734214+1.0000124996093955j) + self.assertAlmostEqual(lib.my_csqrtf(-1-0.01j), + 0.004999937502734214-1.0000124996093955j) + + lib.my_csqrtl.argtypes = ctypes.c_longdouble_complex, + lib.my_csqrtl.restype = ctypes.c_longdouble_complex + self.assertAlmostEqual(lib.my_csqrtl(-1+0.01j), + 0.004999937502734214+1.0000124996093955j) + self.assertAlmostEqual(lib.my_csqrtl(-1-0.01j), + 0.004999937502734214-1.0000124996093955j) + def test_qsort(self): comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char)) lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc diff --git a/Lib/test/test_ctypes/test_numbers.py b/Lib/test/test_ctypes/test_numbers.py index b3816f61a6e7aa..1dd3f2a234b1ee 100644 --- a/Lib/test/test_ctypes/test_numbers.py +++ b/Lib/test/test_ctypes/test_numbers.py @@ -146,7 +146,8 @@ def test_floats(self): @unittest.skipUnless(hasattr(ctypes, "c_double_complex"), "requires C11 complex type") def test_complex(self): - for t in [ctypes.c_double_complex]: + for t in [ctypes.c_double_complex, ctypes.c_float_complex, + ctypes.c_longdouble_complex]: self.assertEqual(t(1).value, 1+0j) self.assertEqual(t(1.0).value, 1+0j) self.assertEqual(t(1+0.125j).value, 1+0.125j) @@ -162,9 +163,10 @@ def test_complex_round_trip(self): values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, -3, INF, -INF, NAN], 2)] for z in values: - with self.subTest(z=z): - z2 = ctypes.c_double_complex(z).value - self.assertComplexesAreIdentical(z, z2) + for t in [ctypes.c_double_complex, ctypes.c_float_complex, + ctypes.c_longdouble_complex]: + with self.subTest(z=z, type=t): + self.assertComplexesAreIdentical(z, t(z).value) def test_integers(self): f = FloatLike() diff --git a/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst b/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst index 7b11d8c303c2f6..890eb62010eb33 100644 --- a/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst +++ b/Misc/NEWS.d/next/Library/2024-06-23-07-23-08.gh-issue-61103.ca_U_l.rst @@ -1,3 +1,5 @@ -Support :c:expr:`double complex` C type in :mod:`ctypes` via -:class:`~ctypes.c_double_complex` if compiler has C11 complex -arithmetic. Patch by Sergey B Kirpichev. +Support :c:expr:`float complex`, :c:expr:`double complex` and +:c:expr:`long double complex` C types in :mod:`ctypes` as +:class:`~ctypes.c_float_complex`, :class:`~ctypes.c_double_complex` and +:class:`~ctypes.c_longdouble_complex` if the compiler has C11 complex arithmetic. +Patch by Sergey B Kirpichev. diff --git a/Modules/_complex.h b/Modules/_complex.h index 1c1d1c8cae51b9..28d4a32794b97c 100644 --- a/Modules/_complex.h +++ b/Modules/_complex.h @@ -21,6 +21,8 @@ #if !defined(CMPLX) # if defined(__clang__) && __has_builtin(__builtin_complex) # define CMPLX(x, y) __builtin_complex ((double) (x), (double) (y)) +# define CMPLXF(x, y) __builtin_complex ((float) (x), (float) (y)) +# define CMPLXL(x, y) __builtin_complex ((long double) (x), (long double) (y)) # else static inline double complex CMPLX(double real, double imag) @@ -30,5 +32,23 @@ CMPLX(double real, double imag) ((double *)(&z))[1] = imag; return z; } + +static inline float complex +CMPLXF(float real, float imag) +{ + float complex z; + ((float *)(&z))[0] = real; + ((float *)(&z))[1] = imag; + return z; +} + +static inline long double complex +CMPLXL(long double real, long double imag) +{ + long double complex z; + ((long double *)(&z))[0] = real; + ((long double *)(&z))[1] = imag; + return z; +} # endif #endif diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 3647361b13a52c..db58f33511c166 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1751,7 +1751,7 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type" /*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd4d9646c56f43a9]*/ #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) -static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCfuzZqQPXOv?g"; +static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCEFfuzZqQPXOv?g"; #else static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g"; #endif @@ -2234,12 +2234,13 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds) stginfo->ffi_type_pointer = *fmt->pffi_type; } else { + const size_t els_size = sizeof(fmt->pffi_type->elements); stginfo->ffi_type_pointer.size = fmt->pffi_type->size; stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; stginfo->ffi_type_pointer.type = fmt->pffi_type->type; - stginfo->ffi_type_pointer.elements = PyMem_Malloc(2 * sizeof(ffi_type)); + stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size); memcpy(stginfo->ffi_type_pointer.elements, - fmt->pffi_type->elements, 2 * sizeof(ffi_type)); + fmt->pffi_type->elements, els_size); } stginfo->align = fmt->pffi_type->alignment; stginfo->length = 0; diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index cbc8f8b0b453af..b8e613fd669d1b 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -454,6 +454,16 @@ EXPORT(double complex) my_csqrt(double complex a) { return csqrt(a); } + +EXPORT(float complex) my_csqrtf(float complex a) +{ + return csqrtf(a); +} + +EXPORT(long double complex) my_csqrtl(long double complex a) +{ + return csqrtl(a); +} #endif EXPORT(void) my_qsort(void *base, size_t num, size_t width, int(*compare)(const void*, const void*)) diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index a83fa19af32402..fd89d9c67b3fc0 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -657,6 +657,8 @@ union result { void *p; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) double complex C; + float complex E; + long double complex F; #endif }; diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index 40b72d83d16aeb..2c1fb9b862e12d 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -1112,6 +1112,50 @@ C_get(void *ptr, Py_ssize_t size) memcpy(&x, ptr, sizeof(x)); return PyComplex_FromDoubles(creal(x), cimag(x)); } + +static PyObject * +E_set(void *ptr, PyObject *value, Py_ssize_t size) +{ + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + float complex x = CMPLXF((float)c.real, (float)c.imag); + memcpy(ptr, &x, sizeof(x)); + _RET(value); +} + +static PyObject * +E_get(void *ptr, Py_ssize_t size) +{ + float complex x; + + memcpy(&x, ptr, sizeof(x)); + return PyComplex_FromDoubles(crealf(x), cimagf(x)); +} + +static PyObject * +F_set(void *ptr, PyObject *value, Py_ssize_t size) +{ + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + long double complex x = CMPLXL(c.real, c.imag); + memcpy(ptr, &x, sizeof(x)); + _RET(value); +} + +static PyObject * +F_get(void *ptr, Py_ssize_t size) +{ + long double complex x; + + memcpy(&x, ptr, sizeof(x)); + return PyComplex_FromDoubles((double)creall(x), (double)cimagl(x)); +} #endif static PyObject * @@ -1621,6 +1665,8 @@ static struct fielddesc formattable[] = { { 'd', d_set, d_get, NULL, d_set_sw, d_get_sw}, #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) { 'C', C_set, C_get, NULL}, + { 'E', E_set, E_get, NULL}, + { 'F', F_set, F_get, NULL}, #endif { 'g', g_set, g_get, NULL}, { 'f', f_set, f_get, NULL, f_set_sw, f_get_sw}, @@ -1674,6 +1720,8 @@ _ctypes_init_fielddesc(void) case 'd': fd->pffi_type = &ffi_type_double; break; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) case 'C': fd->pffi_type = &ffi_type_complex_double; break; + case 'E': fd->pffi_type = &ffi_type_complex_float; break; + case 'F': fd->pffi_type = &ffi_type_complex_longdouble; break; #endif case 'g': fd->pffi_type = &ffi_type_longdouble; break; case 'f': fd->pffi_type = &ffi_type_float; break; diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 5ba5eb3851a690..a794cfe86b5f42 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -401,6 +401,8 @@ struct tagPyCArgObject { void *p; #if defined(Py_HAVE_C_COMPLEX) && defined(FFI_TARGET_HAS_COMPLEX_TYPE) double complex C; + float complex E; + long double complex F; #endif } value; PyObject *obj; From 93156880efd14ad7adc7d3512552b434f5543890 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 3 Jul 2024 10:18:34 +0100 Subject: [PATCH 29/41] gh-121272: set ste_coroutine during symtable construction (#121297) compiler no longer modifies the symtable after this. --- Python/compile.c | 6 +++--- Python/symtable.c | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index d33db69f425361..30708e1dda9d43 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -3059,7 +3059,7 @@ compiler_async_for(struct compiler *c, stmt_ty s) { location loc = LOC(s); if (IS_TOP_LEVEL_AWAIT(c)){ - c->u->u_ste->ste_coroutine = 1; + assert(c->u->u_ste->ste_coroutine == 1); } else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) { return compiler_error(c, loc, "'async for' outside async function"); } @@ -5782,7 +5782,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, co = optimize_and_assemble(c, 1); compiler_exit_scope(c); if (is_top_level_await && is_async_generator){ - c->u->u_ste->ste_coroutine = 1; + assert(c->u->u_ste->ste_coroutine == 1); } if (co == NULL) { goto error; @@ -5926,7 +5926,7 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos) assert(s->kind == AsyncWith_kind); if (IS_TOP_LEVEL_AWAIT(c)){ - c->u->u_ste->ste_coroutine = 1; + assert(c->u->u_ste->ste_coroutine == 1); } else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION){ return compiler_error(c, loc, "'async with' outside async function"); } diff --git a/Python/symtable.c b/Python/symtable.c index 61fa5c6fdf923c..65677f86092b0b 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1681,6 +1681,16 @@ check_import_from(struct symtable *st, stmt_ty s) return 1; } +static void +maybe_set_ste_coroutine_for_module(struct symtable *st, stmt_ty s) +{ + if ((st->st_future->ff_features & PyCF_ALLOW_TOP_LEVEL_AWAIT) && + (st->st_cur->ste_type == ModuleBlock)) + { + st->st_cur->ste_coroutine = 1; + } +} + static int symtable_visit_stmt(struct symtable *st, stmt_ty s) { @@ -2074,10 +2084,12 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) break; } case AsyncWith_kind: + maybe_set_ste_coroutine_for_module(st, s); VISIT_SEQ(st, withitem, s->v.AsyncWith.items); VISIT_SEQ(st, stmt, s->v.AsyncWith.body); break; case AsyncFor_kind: + maybe_set_ste_coroutine_for_module(st, s); VISIT(st, expr, s->v.AsyncFor.target); VISIT(st, expr, s->v.AsyncFor.iter); VISIT_SEQ(st, stmt, s->v.AsyncFor.body); From 722229e5dc1e499664966e50bb98065670033300 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Wed, 3 Jul 2024 17:49:31 +0800 Subject: [PATCH 30/41] gh-121263: Macro-ify most stackref functions for MSVC (GH-121270) Macro-ify most stackref functions for MSVC --- Include/internal/pycore_stackref.h | 88 ++++++++++-------------------- 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 4301c6a7cb40b0..8d3d559814bfd9 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -85,81 +85,67 @@ typedef union _PyStackRef { # define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) }) #endif +// Note: the following are all macros because MSVC (Windows) has trouble inlining them. -static inline int -PyStackRef_Is(_PyStackRef a, _PyStackRef b) { - return a.bits == b.bits; -} +#define PyStackRef_Is(a, b) ((a).bits == (b).bits) + +#define PyStackRef_IsDeferred(ref) (((ref).bits & Py_TAG_BITS) == Py_TAG_DEFERRED) -static inline int -PyStackRef_IsDeferred(_PyStackRef ref) -{ - return ((ref.bits & Py_TAG_BITS) == Py_TAG_DEFERRED); -} +#ifdef Py_GIL_DISABLED // Gets a PyObject * from a _PyStackRef static inline PyObject * PyStackRef_AsPyObjectBorrow(_PyStackRef stackref) { -#ifdef Py_GIL_DISABLED PyObject *cleared = ((PyObject *)((stackref).bits & (~Py_TAG_BITS))); return cleared; +} #else - return ((PyObject *)(stackref).bits); +# define PyStackRef_AsPyObjectBorrow(stackref) ((PyObject *)(stackref).bits) #endif -} // Converts a PyStackRef back to a PyObject *, stealing the // PyStackRef. +#ifdef Py_GIL_DISABLED static inline PyObject * PyStackRef_AsPyObjectSteal(_PyStackRef stackref) { -#ifdef Py_GIL_DISABLED if (!PyStackRef_IsNull(stackref) && PyStackRef_IsDeferred(stackref)) { return Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)); } return PyStackRef_AsPyObjectBorrow(stackref); +} #else - return PyStackRef_AsPyObjectBorrow(stackref); +# define PyStackRef_AsPyObjectSteal(stackref) PyStackRef_AsPyObjectBorrow(stackref) #endif -} // Converts a PyStackRef back to a PyObject *, converting the // stackref to a new reference. -static inline PyObject * -PyStackRef_AsPyObjectNew(_PyStackRef stackref) -{ - return Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)); -} +#define PyStackRef_AsPyObjectNew(stackref) Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)) -static inline PyTypeObject * -PyStackRef_TYPE(_PyStackRef stackref) -{ - return Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref)); -} +#define PyStackRef_TYPE(stackref) Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref)) // Converts a PyObject * to a PyStackRef, stealing the reference +#ifdef Py_GIL_DISABLED static inline _PyStackRef _PyStackRef_FromPyObjectSteal(PyObject *obj) { -#ifdef Py_GIL_DISABLED // Make sure we don't take an already tagged value. assert(((uintptr_t)obj & Py_TAG_BITS) == 0); int tag = (obj == NULL || _Py_IsImmortal(obj)) ? (Py_TAG_DEFERRED) : Py_TAG_PTR; return ((_PyStackRef){.bits = ((uintptr_t)(obj)) | tag}); +} +# define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj)) #else - return ((_PyStackRef){.bits = ((uintptr_t)(obj))}); +# define PyStackRef_FromPyObjectSteal(obj) ((_PyStackRef){.bits = ((uintptr_t)(obj))}) #endif -} - -#define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj)) // Converts a PyObject * to a PyStackRef, with a new reference +#ifdef Py_GIL_DISABLED static inline _PyStackRef PyStackRef_FromPyObjectNew(PyObject *obj) { -#ifdef Py_GIL_DISABLED // Make sure we don't take an already tagged value. assert(((uintptr_t)obj & Py_TAG_BITS) == 0); assert(obj != NULL); @@ -170,30 +156,27 @@ PyStackRef_FromPyObjectNew(PyObject *obj) else { return (_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) | Py_TAG_PTR }; } +} +# define PyStackRef_FromPyObjectNew(obj) PyStackRef_FromPyObjectNew(_PyObject_CAST(obj)) #else - return ((_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) }); +# define PyStackRef_FromPyObjectNew(obj) ((_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) }) #endif -} - -#define PyStackRef_FromPyObjectNew(obj) PyStackRef_FromPyObjectNew(_PyObject_CAST(obj)) +#ifdef Py_GIL_DISABLED // Same as PyStackRef_FromPyObjectNew but only for immortal objects. static inline _PyStackRef PyStackRef_FromPyObjectImmortal(PyObject *obj) { -#ifdef Py_GIL_DISABLED // Make sure we don't take an already tagged value. assert(((uintptr_t)obj & Py_TAG_BITS) == 0); assert(obj != NULL); assert(_Py_IsImmortal(obj)); return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED }; +} +# define PyStackRef_FromPyObjectImmortal(obj) PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj)) #else - assert(_Py_IsImmortal(obj)); - return ((_PyStackRef){ .bits = (uintptr_t)(obj) }); +# define PyStackRef_FromPyObjectImmortal(obj) ((_PyStackRef){ .bits = (uintptr_t)(obj) }) #endif -} - -#define PyStackRef_FromPyObjectImmortal(obj) PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj)) #define PyStackRef_CLEAR(op) \ @@ -206,20 +189,20 @@ PyStackRef_FromPyObjectImmortal(PyObject *obj) } \ } while (0) +#ifdef Py_GIL_DISABLED static inline void PyStackRef_CLOSE(_PyStackRef stackref) { -#ifdef Py_GIL_DISABLED if (PyStackRef_IsDeferred(stackref)) { // No assert for being immortal or deferred here. // The GC unsets deferred objects right before clearing. return; } Py_DECREF(PyStackRef_AsPyObjectBorrow(stackref)); +} #else - Py_DECREF(PyStackRef_AsPyObjectBorrow(stackref)); +# define PyStackRef_CLOSE(stackref) Py_DECREF(PyStackRef_AsPyObjectBorrow(stackref)); #endif -} #define PyStackRef_XCLOSE(stackref) \ do { \ @@ -230,10 +213,10 @@ PyStackRef_CLOSE(_PyStackRef stackref) } while (0); +#ifdef Py_GIL_DISABLED static inline _PyStackRef PyStackRef_DUP(_PyStackRef stackref) { -#ifdef Py_GIL_DISABLED if (PyStackRef_IsDeferred(stackref)) { assert(PyStackRef_IsNull(stackref) || _Py_IsImmortal(PyStackRef_AsPyObjectBorrow(stackref))); @@ -241,21 +224,10 @@ PyStackRef_DUP(_PyStackRef stackref) } Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref)); return stackref; +} #else - Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref)); - return stackref; +# define PyStackRef_DUP(stackref) PyStackRef_FromPyObjectSteal(Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref))); #endif -} - -static inline _PyStackRef -PyStackRef_XDUP(_PyStackRef stackref) -{ - if (!PyStackRef_IsNull(stackref)) { - return PyStackRef_DUP(stackref); - } - return stackref; -} - static inline void _PyObjectStack_FromStackRefStack(PyObject **dst, const _PyStackRef *src, size_t length) From afee76b6ebeefbbc2935ab4f5320355c6fa390dd Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 3 Jul 2024 13:45:43 +0300 Subject: [PATCH 31/41] gh-121245: a regression test for site.register_readline() (#121259) --- Lib/test/test_pyrepl/test_pyrepl.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index b189d3291e8181..93c80467a04546 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1,14 +1,17 @@ import io import itertools import os +import pathlib import rlcompleter import select import subprocess import sys +import tempfile from unittest import TestCase, skipUnless from unittest.mock import patch from test.support import force_not_colorized from test.support import SHORT_TIMEOUT +from test.support.os_helper import unlink from .support import ( FakeConsole, @@ -898,6 +901,30 @@ def test_python_basic_repl(self): self.assertNotIn("Exception", output) self.assertNotIn("Traceback", output) + def test_not_wiping_history_file(self): + hfile = tempfile.NamedTemporaryFile(delete=False) + self.addCleanup(unlink, hfile.name) + env = os.environ.copy() + env["PYTHON_HISTORY"] = hfile.name + commands = "123\nspam\nexit()\n" + + env.pop("PYTHON_BASIC_REPL", None) + output, exit_code = self.run_repl(commands, env=env) + self.assertEqual(exit_code, 0) + self.assertIn("123", output) + self.assertIn("spam", output) + self.assertNotEqual(pathlib.Path(hfile.name).stat().st_size, 0) + + hfile.file.truncate() + hfile.close() + + env["PYTHON_BASIC_REPL"] = "1" + output, exit_code = self.run_repl(commands, env=env) + self.assertEqual(exit_code, 0) + self.assertIn("123", output) + self.assertIn("spam", output) + self.assertNotEqual(pathlib.Path(hfile.name).stat().st_size, 0) + def run_repl(self, repl_input: str | list[str], env: dict | None = None) -> tuple[str, int]: master_fd, slave_fd = pty.openpty() process = subprocess.Popen( From 26d24eeb90d781e381b97d64b4dcb1ee4dd891fe Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Wed, 3 Jul 2024 12:33:28 +0100 Subject: [PATCH 32/41] gh-121035: Update PNG image for logging flow diagram. (GH-121323) --- Doc/howto/logging_flow.png | Bin 110388 -> 39133 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Doc/howto/logging_flow.png b/Doc/howto/logging_flow.png index c2d0befe27326c140a3cd72b60088e5c69b9e53e..d60ed7c031585a426e344961ca9e09c2b51d70a7 100644 GIT binary patch literal 39133 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 literal 110388 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 Date: Wed, 3 Jul 2024 14:22:21 +0100 Subject: [PATCH 33/41] GH-119726: Emit AArch64 trampolines out-of-line (GH-121280) --- .../2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst | 2 ++ Tools/jit/_stencils.py | 14 ++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst new file mode 100644 index 00000000000000..cf5d61450aa3ae --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-28-18-34-49.gh-issue-119726.Fjv_Ab.rst @@ -0,0 +1,2 @@ +Optimize code layout for calls to C functions from the JIT on AArch64. +Patch by Diego Russo. diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py index 68eb1d13394170..1c6a9edb39840d 100644 --- a/Tools/jit/_stencils.py +++ b/Tools/jit/_stencils.py @@ -184,7 +184,7 @@ def pad(self, alignment: int) -> None: self.disassembly.append(f"{offset:x}: {' '.join(['00'] * padding)}") self.body.extend([0] * padding) - def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> None: + def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> Hole: """Even with the large code model, AArch64 Linux insists on 28-bit jumps.""" assert hole.symbol is not None reuse_trampoline = hole.symbol in self.trampolines @@ -194,14 +194,10 @@ def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> None: else: self.pad(alignment) base = len(self.body) - where = slice(hole.offset, hole.offset + 4) - instruction = int.from_bytes(self.body[where], sys.byteorder) - instruction &= 0xFC000000 - instruction |= ((base - hole.offset) >> 2) & 0x03FFFFFF - self.body[where] = instruction.to_bytes(4, sys.byteorder) + new_hole = hole.replace(addend=base, symbol=None, value=HoleValue.DATA) if reuse_trampoline: - return + return new_hole self.disassembly += [ f"{base + 4 * 0:x}: 58000048 ldr x8, 8", @@ -219,6 +215,7 @@ def emit_aarch64_trampoline(self, hole: Hole, alignment: int) -> None: self.body.extend(code) self.holes.append(hole.replace(offset=base + 8, kind="R_AARCH64_ABS64")) self.trampolines[hole.symbol] = base + return new_hole def remove_jump(self, *, alignment: int = 1) -> None: """Remove a zero-length continuation jump, if it exists.""" @@ -294,8 +291,9 @@ def process_relocations(self, *, alignment: int = 1) -> None: in {"R_AARCH64_CALL26", "R_AARCH64_JUMP26", "ARM64_RELOC_BRANCH26"} and hole.value is HoleValue.ZERO ): - self.code.emit_aarch64_trampoline(hole, alignment) + new_hole = self.data.emit_aarch64_trampoline(hole, alignment) self.code.holes.remove(hole) + self.code.holes.append(new_hole) self.code.remove_jump(alignment=alignment) self.code.pad(alignment) self.data.pad(8) From ca2e8765009d0d3eb9fe6c75465825c50808f4dd Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Wed, 3 Jul 2024 07:44:34 -0700 Subject: [PATCH 34/41] gh-121201: Disable perf_trampoline on riscv64 for now (#121328) Disable perf_trampoline on riscv64 for now Until support is added in perf_jit_trampoline.c gh-120089 was incomplete. --- .../NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- | 1 - .../2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst | 1 - configure | 2 -- configure.ac | 1 - 4 files changed, 5 deletions(-) delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- b/Misc/NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- deleted file mode 100644 index 29f06d43c3598c..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-06-05-06-26-04.gh-issue- +++ /dev/null @@ -1 +0,0 @@ -Support Linux perf profiler to see Python calls on RISC-V architecture diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst deleted file mode 100644 index 8c86d4750e39a8..00000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-06-12-12-29-45.gh-issue-120400.lZYHVS.rst +++ /dev/null @@ -1 +0,0 @@ -Support Linux perf profiler to see Python calls on RISC-V architecture. diff --git a/configure b/configure index 922d33edc00cb5..131ca5f7f897a7 100755 --- a/configure +++ b/configure @@ -13292,8 +13292,6 @@ case $PLATFORM_TRIPLET in #( perf_trampoline=yes ;; #( aarch64-linux-gnu) : perf_trampoline=yes ;; #( - riscv64-linux-gnu) : - perf_trampoline=yes ;; #( *) : perf_trampoline=no ;; diff --git a/configure.ac b/configure.ac index a70e673623de81..705f8752597b96 100644 --- a/configure.ac +++ b/configure.ac @@ -3709,7 +3709,6 @@ AC_MSG_CHECKING([perf trampoline]) AS_CASE([$PLATFORM_TRIPLET], [x86_64-linux-gnu], [perf_trampoline=yes], [aarch64-linux-gnu], [perf_trampoline=yes], - [riscv64-linux-gnu], [perf_trampoline=yes], [perf_trampoline=no] ) AC_MSG_RESULT([$perf_trampoline]) From 7c66906802cd8534b05264bd47acf9eb9db6d09e Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Wed, 3 Jul 2024 10:03:56 -0500 Subject: [PATCH 35/41] gh-121300: Add `replace` to `copy.__all__` (#121302) --- Lib/copy.py | 7 ++++--- Lib/test/test_copy.py | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 7a1907d75494d7..a79976d3a658f0 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -4,8 +4,9 @@ import copy - x = copy.copy(y) # make a shallow copy of y - x = copy.deepcopy(y) # make a deep copy of y + x = copy.copy(y) # make a shallow copy of y + x = copy.deepcopy(y) # make a deep copy of y + x = copy.replace(y, a=1, b=2) # new object with fields replaced, as defined by `__replace__` For module specific errors, copy.Error is raised. @@ -56,7 +57,7 @@ class Error(Exception): pass error = Error # backward compatibility -__all__ = ["Error", "copy", "deepcopy"] +__all__ = ["Error", "copy", "deepcopy", "replace"] def copy(x): """Shallow copy operation on arbitrary Python objects. diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 89102373759ca0..3dec64cc9a2414 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -972,6 +972,10 @@ class C: copy.replace(c, x=1, error=2) +class MiscTestCase(unittest.TestCase): + def test__all__(self): + support.check__all__(self, copy, not_exported={"dispatch_table", "error"}) + def global_foo(x, y): return x+y From f8373db153920b890c2e2dd8def249e8df63bcc6 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 3 Jul 2024 18:36:57 +0200 Subject: [PATCH 36/41] gh-112136: Restore removed _PyArg_Parser (#121262) Restore the private _PyArg_Parser structure and the private _PyArg_ParseTupleAndKeywordsFast() function, previously removed in Python 3.13 alpha 1. Recreate Include/cpython/modsupport.h header file. --- Include/cpython/modsupport.h | 26 +++++++++++++++++++ Include/internal/pycore_lock.h | 6 ----- Include/internal/pycore_modsupport.h | 18 ------------- Include/modsupport.h | 6 +++++ Makefile.pre.in | 1 + ...-07-02-11-03-40.gh-issue-112136.f3fiY8.rst | 3 +++ PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 +++ 8 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 Include/cpython/modsupport.h create mode 100644 Misc/NEWS.d/next/C API/2024-07-02-11-03-40.gh-issue-112136.f3fiY8.rst diff --git a/Include/cpython/modsupport.h b/Include/cpython/modsupport.h new file mode 100644 index 00000000000000..d3b88f58c82ca3 --- /dev/null +++ b/Include/cpython/modsupport.h @@ -0,0 +1,26 @@ +#ifndef Py_CPYTHON_MODSUPPORT_H +# error "this header file must not be included directly" +#endif + +// A data structure that can be used to run initialization code once in a +// thread-safe manner. The C++11 equivalent is std::call_once. +typedef struct { + uint8_t v; +} _PyOnceFlag; + +typedef struct _PyArg_Parser { + const char *format; + const char * const *keywords; + const char *fname; + const char *custom_msg; + _PyOnceFlag once; /* atomic one-time initialization flag */ + int is_kwtuple_owned; /* does this parser own the kwtuple object? */ + int pos; /* number of positional-only arguments */ + int min; /* minimal number of arguments */ + int max; /* maximal number of positional arguments */ + PyObject *kwtuple; /* tuple of keyword parameter names */ + struct _PyArg_Parser *next; +} _PyArg_Parser; + +PyAPI_FUNC(int) _PyArg_ParseTupleAndKeywordsFast(PyObject *, PyObject *, + struct _PyArg_Parser *, ...); diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 8aa73946e2c645..3824434f3f375d 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -128,12 +128,6 @@ _PyRawMutex_Unlock(_PyRawMutex *m) _PyRawMutex_UnlockSlow(m); } -// A data structure that can be used to run initialization code once in a -// thread-safe manner. The C++11 equivalent is std::call_once. -typedef struct { - uint8_t v; -} _PyOnceFlag; - // Type signature for one-time initialization functions. The function should // return 0 on success and -1 on failure. typedef int _Py_once_fn_t(void *arg); diff --git a/Include/internal/pycore_modsupport.h b/Include/internal/pycore_modsupport.h index 3d3cd6722528e9..11fde814875938 100644 --- a/Include/internal/pycore_modsupport.h +++ b/Include/internal/pycore_modsupport.h @@ -67,24 +67,6 @@ PyAPI_FUNC(void) _PyArg_BadArgument( // --- _PyArg_Parser API --------------------------------------------------- -typedef struct _PyArg_Parser { - const char *format; - const char * const *keywords; - const char *fname; - const char *custom_msg; - _PyOnceFlag once; /* atomic one-time initialization flag */ - int is_kwtuple_owned; /* does this parser own the kwtuple object? */ - int pos; /* number of positional-only arguments */ - int min; /* minimal number of arguments */ - int max; /* maximal number of positional arguments */ - PyObject *kwtuple; /* tuple of keyword parameter names */ - struct _PyArg_Parser *next; -} _PyArg_Parser; - -// Export for '_testclinic' shared extension -PyAPI_FUNC(int) _PyArg_ParseTupleAndKeywordsFast(PyObject *, PyObject *, - struct _PyArg_Parser *, ...); - // Export for '_dbm' shared extension PyAPI_FUNC(int) _PyArg_ParseStackAndKeywords( PyObject *const *args, diff --git a/Include/modsupport.h b/Include/modsupport.h index ea4c0fce9f4562..af995f567b004c 100644 --- a/Include/modsupport.h +++ b/Include/modsupport.h @@ -134,6 +134,12 @@ PyAPI_FUNC(PyObject *) PyModule_FromDefAndSpec2(PyModuleDef *def, #endif /* New in 3.5 */ +#ifndef Py_LIMITED_API +# define Py_CPYTHON_MODSUPPORT_H +# include "cpython/modsupport.h" +# undef Py_CPYTHON_MODSUPPORT_H +#endif + #ifdef __cplusplus } #endif diff --git a/Makefile.pre.in b/Makefile.pre.in index e1c793ce629b02..94cfb74138a3d9 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1115,6 +1115,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/cpython/longobject.h \ $(srcdir)/Include/cpython/memoryobject.h \ $(srcdir)/Include/cpython/methodobject.h \ + $(srcdir)/Include/cpython/modsupport.h \ $(srcdir)/Include/cpython/monitoring.h \ $(srcdir)/Include/cpython/object.h \ $(srcdir)/Include/cpython/objimpl.h \ diff --git a/Misc/NEWS.d/next/C API/2024-07-02-11-03-40.gh-issue-112136.f3fiY8.rst b/Misc/NEWS.d/next/C API/2024-07-02-11-03-40.gh-issue-112136.f3fiY8.rst new file mode 100644 index 00000000000000..a240b4e852c4d1 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-07-02-11-03-40.gh-issue-112136.f3fiY8.rst @@ -0,0 +1,3 @@ +Restore the private ``_PyArg_Parser`` structure and the private +``_PyArg_ParseTupleAndKeywordsFast()`` function, previously removed in Python +3.13 alpha 1. Patch by Victor Stinner. diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 3378ed54203f18..f36fcb8caece33 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -163,6 +163,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 742d88d9e1fa7a..a1b43addf9e36a 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -432,6 +432,9 @@ Include\cpython + + Include\cpython + Include\cpython From e245ed7d1e23b5c8bc0d568bd1a2f06ae92d631a Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 3 Jul 2024 11:30:20 -0700 Subject: [PATCH 37/41] gh-118714: Make the pdb post-mortem restart/quit behavior more reasonable (#118725) --- Lib/pdb.py | 9 ++++++--- Lib/test/test_pdb.py | 17 +++++++++++++++++ ...24-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst | 2 ++ 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 4af16d0a087c8c..85a3aa2e37996f 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -2481,9 +2481,12 @@ def main(): traceback.print_exception(e, colorize=_colorize.can_colorize()) print("Uncaught exception. Entering post mortem debugging") print("Running 'cont' or 'step' will restart the program") - pdb.interaction(None, e) - print(f"Post mortem debugger finished. The {target} will " - "be restarted") + try: + pdb.interaction(None, e) + except Restart: + print("Restarting", target, "with arguments:") + print("\t" + " ".join(sys.argv[1:])) + continue if pdb._user_requested_quit: break print("The program finished and will be restarted") diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 71240157e324a1..5c7445574f5d75 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3545,6 +3545,23 @@ def change_file(content, filename): # the file as up to date self.assertNotIn("WARNING:", stdout) + def test_post_mortem_restart(self): + script = """ + def foo(): + raise ValueError("foo") + foo() + """ + + commands = """ + continue + restart + continue + quit + """ + + stdout, stderr = self.run_pdb_script(script, commands) + self.assertIn("Restarting", stdout) + def test_relative_imports(self): self.module_name = 't_main' os_helper.rmtree(self.module_name) diff --git a/Misc/NEWS.d/next/Library/2024-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst b/Misc/NEWS.d/next/Library/2024-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst new file mode 100644 index 00000000000000..f41baee303482a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-05-07-17-38-53.gh-issue-118714.XXKpVZ.rst @@ -0,0 +1,2 @@ +Allow ``restart`` in post-mortem debugging of :mod:`pdb`. Removed restart message +when the user quits pdb from post-mortem mode. From 94f50f8ee6872007d46c385f7af253497273255a Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Wed, 3 Jul 2024 16:50:46 -0400 Subject: [PATCH 38/41] gh-117983: Defer import of threading for lazy module loading (#120233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As noted in gh-117983, the import importlib.util can be triggered at interpreter startup under some circumstances, so adding threading makes it a potentially obligatory load. Lazy loading is not used in the stdlib, so this removes an unnecessary load for the majority of users and slightly increases the cost of the first lazily loaded module. An obligatory threading load breaks gevent, which monkeypatches the stdlib. Although unsupported, there doesn't seem to be an offsetting benefit to breaking their use case. For reference, here are benchmarks for the current main branch: ``` ❯ hyperfine -w 8 './python -c "import importlib.util"' Benchmark 1: ./python -c "import importlib.util" Time (mean ± σ): 9.7 ms ± 0.7 ms [User: 7.7 ms, System: 1.8 ms] Range (min … max): 8.4 ms … 13.1 ms 313 runs ``` And with this patch: ``` ❯ hyperfine -w 8 './python -c "import importlib.util"' Benchmark 1: ./python -c "import importlib.util" Time (mean ± σ): 8.4 ms ± 0.7 ms [User: 6.8 ms, System: 1.4 ms] Range (min … max): 7.2 ms … 11.7 ms 352 runs ``` Compare to: ``` ❯ hyperfine -w 8 './python -c pass' Benchmark 1: ./python -c pass Time (mean ± σ): 7.6 ms ± 0.6 ms [User: 5.9 ms, System: 1.6 ms] Range (min … max): 6.7 ms … 11.3 ms 390 runs ``` This roughly halves the import time of importlib.util. --- Lib/importlib/util.py | 4 +++- .../Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 7243d052cc27f3..8403ef9b44ad1a 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -13,7 +13,6 @@ import _imp import sys -import threading import types @@ -257,6 +256,9 @@ def create_module(self, spec): def exec_module(self, module): """Make the module load lazily.""" + # Threading is only needed for lazy loading, and importlib.util can + # be pulled in at interpreter startup, so defer until needed. + import threading module.__spec__.loader = self.loader module.__loader__ = self.loader # Don't need to worry about deep-copying as trying to set an attribute diff --git a/Misc/NEWS.d/next/Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst b/Misc/NEWS.d/next/Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst new file mode 100644 index 00000000000000..cca97f50a20496 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-07-10-10-32.gh-issue-117983.NeMR9n.rst @@ -0,0 +1,2 @@ +Defer the ``threading`` import in ``importlib.util`` until lazy loading is +used. From 9728ead36181fb3f0a4b2e8a7291a3e0a702b952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 4 Jul 2024 05:10:54 +0200 Subject: [PATCH 39/41] gh-121141: add support for `copy.replace` to AST nodes (#121162) --- Doc/whatsnew/3.14.rst | 8 +- Lib/test/test_ast.py | 272 +++++++++++++++++ ...-06-29-15-21-12.gh-issue-121141.4evD6q.rst | 1 + Parser/asdl_c.py | 279 ++++++++++++++++++ Python/Python-ast.c | 279 ++++++++++++++++++ 5 files changed, 837 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-06-29-15-21-12.gh-issue-121141.4evD6q.rst diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 9578ba0c9c9657..d02c10ec9cf3f3 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -89,8 +89,12 @@ Improved Modules ast --- -Added :func:`ast.compare` for comparing two ASTs. -(Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.) +* Added :func:`ast.compare` for comparing two ASTs. + (Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.) + +* Add support for :func:`copy.replace` for AST nodes. + + (Contributed by Bénédikt Tran in :gh:`121141`.) os -- diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index fbd19620311159..eb3aefd5c262f6 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1149,6 +1149,25 @@ def test_none_checks(self) -> None: class CopyTests(unittest.TestCase): """Test copying and pickling AST nodes.""" + @staticmethod + def iter_ast_classes(): + """Iterate over the (native) subclasses of ast.AST recursively. + + This excludes the special class ast.Index since its constructor + returns an integer. + """ + def do(cls): + if cls.__module__ != 'ast': + return + if cls is ast.Index: + return + + yield cls + for sub in cls.__subclasses__(): + yield from do(sub) + + yield from do(ast.AST) + def test_pickling(self): import pickle @@ -1218,6 +1237,259 @@ def test_copy_with_parents(self): )): self.assertEqual(to_tuple(child.parent), to_tuple(node)) + def test_replace_interface(self): + for klass in self.iter_ast_classes(): + with self.subTest(klass=klass): + self.assertTrue(hasattr(klass, '__replace__')) + + fields = set(klass._fields) + with self.subTest(klass=klass, fields=fields): + node = klass(**dict.fromkeys(fields)) + # forbid positional arguments in replace() + self.assertRaises(TypeError, copy.replace, node, 1) + self.assertRaises(TypeError, node.__replace__, 1) + + def test_replace_native(self): + for klass in self.iter_ast_classes(): + fields = set(klass._fields) + attributes = set(klass._attributes) + + with self.subTest(klass=klass, fields=fields, attributes=attributes): + # use of object() to ensure that '==' and 'is' + # behave similarly in ast.compare(node, repl) + old_fields = {field: object() for field in fields} + old_attrs = {attr: object() for attr in attributes} + + # check shallow copy + node = klass(**old_fields) + repl = copy.replace(node) + self.assertTrue(ast.compare(node, repl, compare_attributes=True)) + # check when passing using attributes (they may be optional!) + node = klass(**old_fields, **old_attrs) + repl = copy.replace(node) + self.assertTrue(ast.compare(node, repl, compare_attributes=True)) + + for field in fields: + # check when we sometimes have attributes and sometimes not + for init_attrs in [{}, old_attrs]: + node = klass(**old_fields, **init_attrs) + # only change a single field (do not change attributes) + new_value = object() + repl = copy.replace(node, **{field: new_value}) + for f in fields: + old_value = old_fields[f] + # assert that there is no side-effect + self.assertIs(getattr(node, f), old_value) + # check the changes + if f != field: + self.assertIs(getattr(repl, f), old_value) + else: + self.assertIs(getattr(repl, f), new_value) + self.assertFalse(ast.compare(node, repl, compare_attributes=True)) + + for attribute in attributes: + node = klass(**old_fields, **old_attrs) + # only change a single attribute (do not change fields) + new_attr = object() + repl = copy.replace(node, **{attribute: new_attr}) + for a in attributes: + old_attr = old_attrs[a] + # assert that there is no side-effect + self.assertIs(getattr(node, a), old_attr) + # check the changes + if a != attribute: + self.assertIs(getattr(repl, a), old_attr) + else: + self.assertIs(getattr(repl, a), new_attr) + self.assertFalse(ast.compare(node, repl, compare_attributes=True)) + + def test_replace_accept_known_class_fields(self): + nid, ctx = object(), object() + + node = ast.Name(id=nid, ctx=ctx) + self.assertIs(node.id, nid) + self.assertIs(node.ctx, ctx) + + new_nid = object() + repl = copy.replace(node, id=new_nid) + # assert that there is no side-effect + self.assertIs(node.id, nid) + self.assertIs(node.ctx, ctx) + # check the changes + self.assertIs(repl.id, new_nid) + self.assertIs(repl.ctx, node.ctx) # no changes + + def test_replace_accept_known_class_attributes(self): + node = ast.parse('x').body[0].value + self.assertEqual(node.id, 'x') + self.assertEqual(node.lineno, 1) + + # constructor allows any type so replace() should do the same + lineno = object() + repl = copy.replace(node, lineno=lineno) + # assert that there is no side-effect + self.assertEqual(node.lineno, 1) + # check the changes + self.assertEqual(repl.id, node.id) + self.assertEqual(repl.ctx, node.ctx) + self.assertEqual(repl.lineno, lineno) + + _, _, state = node.__reduce__() + self.assertEqual(state['id'], 'x') + self.assertEqual(state['ctx'], node.ctx) + self.assertEqual(state['lineno'], 1) + + _, _, state = repl.__reduce__() + self.assertEqual(state['id'], 'x') + self.assertEqual(state['ctx'], node.ctx) + self.assertEqual(state['lineno'], lineno) + + def test_replace_accept_known_custom_class_fields(self): + class MyNode(ast.AST): + _fields = ('name', 'data') + __annotations__ = {'name': str, 'data': object} + __match_args__ = ('name', 'data') + + name, data = 'name', object() + + node = MyNode(name, data) + self.assertIs(node.name, name) + self.assertIs(node.data, data) + # check shallow copy + repl = copy.replace(node) + # assert that there is no side-effect + self.assertIs(node.name, name) + self.assertIs(node.data, data) + # check the shallow copy + self.assertIs(repl.name, name) + self.assertIs(repl.data, data) + + node = MyNode(name, data) + repl_data = object() + # replace custom but known field + repl = copy.replace(node, data=repl_data) + # assert that there is no side-effect + self.assertIs(node.name, name) + self.assertIs(node.data, data) + # check the changes + self.assertIs(repl.name, node.name) + self.assertIs(repl.data, repl_data) + + def test_replace_accept_known_custom_class_attributes(self): + class MyNode(ast.AST): + x = 0 + y = 1 + _attributes = ('x', 'y') + + node = MyNode() + self.assertEqual(node.x, 0) + self.assertEqual(node.y, 1) + + y = object() + # custom attributes are currently not supported and raise a warning + # because the allowed attributes are hard-coded ! + msg = ( + "MyNode.__init__ got an unexpected keyword argument 'y'. " + "Support for arbitrary keyword arguments is deprecated and " + "will be removed in Python 3.15" + ) + with self.assertWarnsRegex(DeprecationWarning, re.escape(msg)): + repl = copy.replace(node, y=y) + # assert that there is no side-effect + self.assertEqual(node.x, 0) + self.assertEqual(node.y, 1) + # check the changes + self.assertEqual(repl.x, 0) + self.assertEqual(repl.y, y) + + def test_replace_ignore_known_custom_instance_fields(self): + node = ast.parse('x').body[0].value + node.extra = extra = object() # add instance 'extra' field + context = node.ctx + + # assert initial values + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertIs(node.extra, extra) + # shallow copy, but drops extra fields + repl = copy.replace(node) + # assert that there is no side-effect + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertIs(node.extra, extra) + # verify that the 'extra' field is not kept + self.assertIs(repl.id, 'x') + self.assertIs(repl.ctx, context) + self.assertRaises(AttributeError, getattr, repl, 'extra') + + # change known native field + repl = copy.replace(node, id='y') + # assert that there is no side-effect + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertIs(node.extra, extra) + # verify that the 'extra' field is not kept + self.assertIs(repl.id, 'y') + self.assertIs(repl.ctx, context) + self.assertRaises(AttributeError, getattr, repl, 'extra') + + def test_replace_reject_missing_field(self): + # case: warn if deleted field is not replaced + node = ast.parse('x').body[0].value + context = node.ctx + del node.id + + self.assertRaises(AttributeError, getattr, node, 'id') + self.assertIs(node.ctx, context) + msg = "Name.__replace__ missing 1 keyword argument: 'id'." + with self.assertRaisesRegex(TypeError, re.escape(msg)): + copy.replace(node) + # assert that there is no side-effect + self.assertRaises(AttributeError, getattr, node, 'id') + self.assertIs(node.ctx, context) + + # case: do not raise if deleted field is replaced + node = ast.parse('x').body[0].value + context = node.ctx + del node.id + + self.assertRaises(AttributeError, getattr, node, 'id') + self.assertIs(node.ctx, context) + repl = copy.replace(node, id='y') + # assert that there is no side-effect + self.assertRaises(AttributeError, getattr, node, 'id') + self.assertIs(node.ctx, context) + self.assertIs(repl.id, 'y') + self.assertIs(repl.ctx, context) + + def test_replace_reject_known_custom_instance_fields_commits(self): + node = ast.parse('x').body[0].value + node.extra = extra = object() # add instance 'extra' field + context = node.ctx + + # explicit rejection of known instance fields + self.assertTrue(hasattr(node, 'extra')) + msg = "Name.__replace__ got an unexpected keyword argument 'extra'." + with self.assertRaisesRegex(TypeError, re.escape(msg)): + copy.replace(node, extra=1) + # assert that there is no side-effect + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertIs(node.extra, extra) + + def test_replace_reject_unknown_instance_fields(self): + node = ast.parse('x').body[0].value + context = node.ctx + + # explicit rejection of unknown extra fields + self.assertRaises(AttributeError, getattr, node, 'unknown') + msg = "Name.__replace__ got an unexpected keyword argument 'unknown'." + with self.assertRaisesRegex(TypeError, re.escape(msg)): + copy.replace(node, unknown=1) + # assert that there is no side-effect + self.assertIs(node.id, 'x') + self.assertIs(node.ctx, context) + self.assertRaises(AttributeError, getattr, node, 'unknown') class ASTHelpers_Test(unittest.TestCase): maxDiff = None diff --git a/Misc/NEWS.d/next/Library/2024-06-29-15-21-12.gh-issue-121141.4evD6q.rst b/Misc/NEWS.d/next/Library/2024-06-29-15-21-12.gh-issue-121141.4evD6q.rst new file mode 100644 index 00000000000000..f2dc621050ff4b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-29-15-21-12.gh-issue-121141.4evD6q.rst @@ -0,0 +1 @@ +Add support for :func:`copy.replace` to AST nodes. Patch by Bénédikt Tran. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index e338656a5b1eb9..f3667801782f2b 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -1132,6 +1132,279 @@ def visitModule(self, mod): return result; } +/* + * Perform the following validations: + * + * - All keyword arguments are known 'fields' or 'attributes'. + * - No field or attribute would be left unfilled after copy.replace(). + * + * On success, this returns 1. Otherwise, set a TypeError + * exception and returns -1 (no exception is set if some + * other internal errors occur). + * + * Parameters + * + * self The AST node instance. + * dict The AST node instance dictionary (self.__dict__). + * fields The list of fields (self._fields). + * attributes The list of attributes (self._attributes). + * kwargs Keyword arguments passed to ast_type_replace(). + * + * The 'dict', 'fields', 'attributes' and 'kwargs' arguments can be NULL. + * + * Note: this function can be removed in 3.15 since the verification + * will be done inside the constructor. + */ +static inline int +ast_type_replace_check(PyObject *self, + PyObject *dict, + PyObject *fields, + PyObject *attributes, + PyObject *kwargs) +{ + // While it is possible to make some fast paths that would avoid + // allocating objects on the stack, this would cost us readability. + // For instance, if 'fields' and 'attributes' are both empty, and + // 'kwargs' is not empty, we could raise a TypeError immediately. + PyObject *expecting = PySet_New(fields); + if (expecting == NULL) { + return -1; + } + if (attributes) { + if (_PySet_Update(expecting, attributes) < 0) { + Py_DECREF(expecting); + return -1; + } + } + // Any keyword argument that is neither a field nor attribute is rejected. + // We first need to check whether a keyword argument is accepted or not. + // If all keyword arguments are accepted, we compute the required fields + // and attributes. A field or attribute is not needed if: + // + // 1) it is given in 'kwargs', or + // 2) it already exists on 'self'. + if (kwargs) { + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(kwargs, &pos, &key, &value)) { + int rc = PySet_Discard(expecting, key); + if (rc < 0) { + Py_DECREF(expecting); + return -1; + } + if (rc == 0) { + PyErr_Format(PyExc_TypeError, + "%.400s.__replace__ got an unexpected keyword " + "argument '%U'.", Py_TYPE(self)->tp_name, key); + Py_DECREF(expecting); + return -1; + } + } + } + // check that the remaining fields or attributes would be filled + if (dict) { + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(dict, &pos, &key, &value)) { + // Mark fields or attributes that are found on the instance + // as non-mandatory. If they are not given in 'kwargs', they + // will be shallow-coied; otherwise, they would be replaced + // (not in this function). + if (PySet_Discard(expecting, key) < 0) { + Py_DECREF(expecting); + return -1; + } + } + if (attributes) { + // Some attributes may or may not be present at runtime. + // In particular, now that we checked whether 'kwargs' + // is correct or not, we allow any attribute to be missing. + // + // Note that fields must still be entirely determined when + // calling the constructor later. + PyObject *unused = PyObject_CallMethodOneArg(expecting, + &_Py_ID(difference_update), + attributes); + if (unused == NULL) { + Py_DECREF(expecting); + return -1; + } + Py_DECREF(unused); + } + } + // Now 'expecting' contains the fields or attributes + // that would not be filled inside ast_type_replace(). + Py_ssize_t m = PySet_GET_SIZE(expecting); + if (m > 0) { + PyObject *names = PyList_New(m); + if (names == NULL) { + Py_DECREF(expecting); + return -1; + } + Py_ssize_t i = 0, pos = 0; + PyObject *item; + Py_hash_t hash; + while (_PySet_NextEntry(expecting, &pos, &item, &hash)) { + PyObject *name = PyObject_Repr(item); + if (name == NULL) { + Py_DECREF(expecting); + Py_DECREF(names); + return -1; + } + // steal the reference 'name' + PyList_SET_ITEM(names, i++, name); + } + Py_DECREF(expecting); + if (PyList_Sort(names) < 0) { + Py_DECREF(names); + return -1; + } + PyObject *sep = PyUnicode_FromString(", "); + if (sep == NULL) { + Py_DECREF(names); + return -1; + } + PyObject *str_names = PyUnicode_Join(sep, names); + Py_DECREF(sep); + Py_DECREF(names); + if (str_names == NULL) { + return -1; + } + PyErr_Format(PyExc_TypeError, + "%.400s.__replace__ missing %ld keyword argument%s: %U.", + Py_TYPE(self)->tp_name, m, m == 1 ? "" : "s", str_names); + Py_DECREF(str_names); + return -1; + } + else { + Py_DECREF(expecting); + return 1; + } +} + +/* + * Python equivalent: + * + * for key in keys: + * if hasattr(self, key): + * payload[key] = getattr(self, key) + * + * The 'keys' argument is a sequence corresponding to + * the '_fields' or the '_attributes' of an AST node. + * + * This returns -1 if an error occurs and 0 otherwise. + * + * Parameters + * + * payload A dictionary to fill. + * keys A sequence of keys or NULL for an empty sequence. + * dict The AST node instance dictionary (must not be NULL). + */ +static inline int +ast_type_replace_update_payload(PyObject *payload, + PyObject *keys, + PyObject *dict) +{ + assert(dict != NULL); + if (keys == NULL) { + return 0; + } + Py_ssize_t n = PySequence_Size(keys); + if (n == -1) { + return -1; + } + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *key = PySequence_GetItem(keys, i); + if (key == NULL) { + return -1; + } + PyObject *value; + if (PyDict_GetItemRef(dict, key, &value) < 0) { + Py_DECREF(key); + return -1; + } + if (value == NULL) { + Py_DECREF(key); + // If a field or attribute is not present at runtime, it should + // be explicitly given in 'kwargs'. If not, the constructor will + // issue a warning (which becomes an error in 3.15). + continue; + } + int rc = PyDict_SetItem(payload, key, value); + Py_DECREF(key); + Py_DECREF(value); + if (rc < 0) { + return -1; + } + } + return 0; +} + +/* copy.replace() support (shallow copy) */ +static PyObject * +ast_type_replace(PyObject *self, PyObject *args, PyObject *kwargs) +{ + if (!_PyArg_NoPositional("__replace__", args)) { + return NULL; + } + + struct ast_state *state = get_ast_state(); + if (state == NULL) { + return NULL; + } + + PyObject *result = NULL; + // known AST class fields and attributes + PyObject *fields = NULL, *attributes = NULL; + // current instance dictionary + PyObject *dict = NULL; + // constructor positional and keyword arguments + PyObject *empty_tuple = NULL, *payload = NULL; + + PyObject *type = (PyObject *)Py_TYPE(self); + if (PyObject_GetOptionalAttr(type, state->_fields, &fields) < 0) { + goto cleanup; + } + if (PyObject_GetOptionalAttr(type, state->_attributes, &attributes) < 0) { + goto cleanup; + } + if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { + goto cleanup; + } + if (ast_type_replace_check(self, dict, fields, attributes, kwargs) < 0) { + goto cleanup; + } + empty_tuple = PyTuple_New(0); + if (empty_tuple == NULL) { + goto cleanup; + } + payload = PyDict_New(); + if (payload == NULL) { + goto cleanup; + } + if (dict) { // in case __dict__ is missing (for some obscure reason) + // copy the instance's fields (possibly NULL) + if (ast_type_replace_update_payload(payload, fields, dict) < 0) { + goto cleanup; + } + // copy the instance's attributes (possibly NULL) + if (ast_type_replace_update_payload(payload, attributes, dict) < 0) { + goto cleanup; + } + } + if (kwargs && PyDict_Update(payload, kwargs) < 0) { + goto cleanup; + } + result = PyObject_Call(type, empty_tuple, payload); +cleanup: + Py_XDECREF(payload); + Py_XDECREF(empty_tuple); + Py_XDECREF(dict); + Py_XDECREF(attributes); + Py_XDECREF(fields); + return result; +} + static PyMemberDef ast_type_members[] = { {"__dictoffset__", Py_T_PYSSIZET, offsetof(AST_object, dict), Py_READONLY}, {NULL} /* Sentinel */ @@ -1139,6 +1412,10 @@ def visitModule(self, mod): static PyMethodDef ast_type_methods[] = { {"__reduce__", ast_type_reduce, METH_NOARGS, NULL}, + {"__replace__", _PyCFunction_CAST(ast_type_replace), METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("__replace__($self, /, **fields)\\n--\\n\\n" + "Return a copy of the AST node with new values " + "for the specified fields.")}, {NULL} }; @@ -1773,7 +2050,9 @@ def generate_module_def(mod, metadata, f, internal_h): #include "pycore_ceval.h" // _Py_EnterRecursiveCall #include "pycore_lock.h" // _PyOnceFlag #include "pycore_interp.h" // _PyInterpreterState.ast + #include "pycore_modsupport.h" // _PyArg_NoPositional() #include "pycore_pystate.h" // _PyInterpreterState_GET() + #include "pycore_setobject.h" // _PySet_NextEntry(), _PySet_Update() #include "pycore_unionobject.h" // _Py_union_type_or #include "structmember.h" #include diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 01ffea1869350b..cca2ee409e7978 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -6,7 +6,9 @@ #include "pycore_ceval.h" // _Py_EnterRecursiveCall #include "pycore_lock.h" // _PyOnceFlag #include "pycore_interp.h" // _PyInterpreterState.ast +#include "pycore_modsupport.h" // _PyArg_NoPositional() #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_setobject.h" // _PySet_NextEntry(), _PySet_Update() #include "pycore_unionobject.h" // _Py_union_type_or #include "structmember.h" #include @@ -5331,6 +5333,279 @@ ast_type_reduce(PyObject *self, PyObject *unused) return result; } +/* + * Perform the following validations: + * + * - All keyword arguments are known 'fields' or 'attributes'. + * - No field or attribute would be left unfilled after copy.replace(). + * + * On success, this returns 1. Otherwise, set a TypeError + * exception and returns -1 (no exception is set if some + * other internal errors occur). + * + * Parameters + * + * self The AST node instance. + * dict The AST node instance dictionary (self.__dict__). + * fields The list of fields (self._fields). + * attributes The list of attributes (self._attributes). + * kwargs Keyword arguments passed to ast_type_replace(). + * + * The 'dict', 'fields', 'attributes' and 'kwargs' arguments can be NULL. + * + * Note: this function can be removed in 3.15 since the verification + * will be done inside the constructor. + */ +static inline int +ast_type_replace_check(PyObject *self, + PyObject *dict, + PyObject *fields, + PyObject *attributes, + PyObject *kwargs) +{ + // While it is possible to make some fast paths that would avoid + // allocating objects on the stack, this would cost us readability. + // For instance, if 'fields' and 'attributes' are both empty, and + // 'kwargs' is not empty, we could raise a TypeError immediately. + PyObject *expecting = PySet_New(fields); + if (expecting == NULL) { + return -1; + } + if (attributes) { + if (_PySet_Update(expecting, attributes) < 0) { + Py_DECREF(expecting); + return -1; + } + } + // Any keyword argument that is neither a field nor attribute is rejected. + // We first need to check whether a keyword argument is accepted or not. + // If all keyword arguments are accepted, we compute the required fields + // and attributes. A field or attribute is not needed if: + // + // 1) it is given in 'kwargs', or + // 2) it already exists on 'self'. + if (kwargs) { + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(kwargs, &pos, &key, &value)) { + int rc = PySet_Discard(expecting, key); + if (rc < 0) { + Py_DECREF(expecting); + return -1; + } + if (rc == 0) { + PyErr_Format(PyExc_TypeError, + "%.400s.__replace__ got an unexpected keyword " + "argument '%U'.", Py_TYPE(self)->tp_name, key); + Py_DECREF(expecting); + return -1; + } + } + } + // check that the remaining fields or attributes would be filled + if (dict) { + Py_ssize_t pos = 0; + PyObject *key, *value; + while (PyDict_Next(dict, &pos, &key, &value)) { + // Mark fields or attributes that are found on the instance + // as non-mandatory. If they are not given in 'kwargs', they + // will be shallow-coied; otherwise, they would be replaced + // (not in this function). + if (PySet_Discard(expecting, key) < 0) { + Py_DECREF(expecting); + return -1; + } + } + if (attributes) { + // Some attributes may or may not be present at runtime. + // In particular, now that we checked whether 'kwargs' + // is correct or not, we allow any attribute to be missing. + // + // Note that fields must still be entirely determined when + // calling the constructor later. + PyObject *unused = PyObject_CallMethodOneArg(expecting, + &_Py_ID(difference_update), + attributes); + if (unused == NULL) { + Py_DECREF(expecting); + return -1; + } + Py_DECREF(unused); + } + } + // Now 'expecting' contains the fields or attributes + // that would not be filled inside ast_type_replace(). + Py_ssize_t m = PySet_GET_SIZE(expecting); + if (m > 0) { + PyObject *names = PyList_New(m); + if (names == NULL) { + Py_DECREF(expecting); + return -1; + } + Py_ssize_t i = 0, pos = 0; + PyObject *item; + Py_hash_t hash; + while (_PySet_NextEntry(expecting, &pos, &item, &hash)) { + PyObject *name = PyObject_Repr(item); + if (name == NULL) { + Py_DECREF(expecting); + Py_DECREF(names); + return -1; + } + // steal the reference 'name' + PyList_SET_ITEM(names, i++, name); + } + Py_DECREF(expecting); + if (PyList_Sort(names) < 0) { + Py_DECREF(names); + return -1; + } + PyObject *sep = PyUnicode_FromString(", "); + if (sep == NULL) { + Py_DECREF(names); + return -1; + } + PyObject *str_names = PyUnicode_Join(sep, names); + Py_DECREF(sep); + Py_DECREF(names); + if (str_names == NULL) { + return -1; + } + PyErr_Format(PyExc_TypeError, + "%.400s.__replace__ missing %ld keyword argument%s: %U.", + Py_TYPE(self)->tp_name, m, m == 1 ? "" : "s", str_names); + Py_DECREF(str_names); + return -1; + } + else { + Py_DECREF(expecting); + return 1; + } +} + +/* + * Python equivalent: + * + * for key in keys: + * if hasattr(self, key): + * payload[key] = getattr(self, key) + * + * The 'keys' argument is a sequence corresponding to + * the '_fields' or the '_attributes' of an AST node. + * + * This returns -1 if an error occurs and 0 otherwise. + * + * Parameters + * + * payload A dictionary to fill. + * keys A sequence of keys or NULL for an empty sequence. + * dict The AST node instance dictionary (must not be NULL). + */ +static inline int +ast_type_replace_update_payload(PyObject *payload, + PyObject *keys, + PyObject *dict) +{ + assert(dict != NULL); + if (keys == NULL) { + return 0; + } + Py_ssize_t n = PySequence_Size(keys); + if (n == -1) { + return -1; + } + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *key = PySequence_GetItem(keys, i); + if (key == NULL) { + return -1; + } + PyObject *value; + if (PyDict_GetItemRef(dict, key, &value) < 0) { + Py_DECREF(key); + return -1; + } + if (value == NULL) { + Py_DECREF(key); + // If a field or attribute is not present at runtime, it should + // be explicitly given in 'kwargs'. If not, the constructor will + // issue a warning (which becomes an error in 3.15). + continue; + } + int rc = PyDict_SetItem(payload, key, value); + Py_DECREF(key); + Py_DECREF(value); + if (rc < 0) { + return -1; + } + } + return 0; +} + +/* copy.replace() support (shallow copy) */ +static PyObject * +ast_type_replace(PyObject *self, PyObject *args, PyObject *kwargs) +{ + if (!_PyArg_NoPositional("__replace__", args)) { + return NULL; + } + + struct ast_state *state = get_ast_state(); + if (state == NULL) { + return NULL; + } + + PyObject *result = NULL; + // known AST class fields and attributes + PyObject *fields = NULL, *attributes = NULL; + // current instance dictionary + PyObject *dict = NULL; + // constructor positional and keyword arguments + PyObject *empty_tuple = NULL, *payload = NULL; + + PyObject *type = (PyObject *)Py_TYPE(self); + if (PyObject_GetOptionalAttr(type, state->_fields, &fields) < 0) { + goto cleanup; + } + if (PyObject_GetOptionalAttr(type, state->_attributes, &attributes) < 0) { + goto cleanup; + } + if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { + goto cleanup; + } + if (ast_type_replace_check(self, dict, fields, attributes, kwargs) < 0) { + goto cleanup; + } + empty_tuple = PyTuple_New(0); + if (empty_tuple == NULL) { + goto cleanup; + } + payload = PyDict_New(); + if (payload == NULL) { + goto cleanup; + } + if (dict) { // in case __dict__ is missing (for some obscure reason) + // copy the instance's fields (possibly NULL) + if (ast_type_replace_update_payload(payload, fields, dict) < 0) { + goto cleanup; + } + // copy the instance's attributes (possibly NULL) + if (ast_type_replace_update_payload(payload, attributes, dict) < 0) { + goto cleanup; + } + } + if (kwargs && PyDict_Update(payload, kwargs) < 0) { + goto cleanup; + } + result = PyObject_Call(type, empty_tuple, payload); +cleanup: + Py_XDECREF(payload); + Py_XDECREF(empty_tuple); + Py_XDECREF(dict); + Py_XDECREF(attributes); + Py_XDECREF(fields); + return result; +} + static PyMemberDef ast_type_members[] = { {"__dictoffset__", Py_T_PYSSIZET, offsetof(AST_object, dict), Py_READONLY}, {NULL} /* Sentinel */ @@ -5338,6 +5613,10 @@ static PyMemberDef ast_type_members[] = { static PyMethodDef ast_type_methods[] = { {"__reduce__", ast_type_reduce, METH_NOARGS, NULL}, + {"__replace__", _PyCFunction_CAST(ast_type_replace), METH_VARARGS | METH_KEYWORDS, + PyDoc_STR("__replace__($self, /, **fields)\n--\n\n" + "Return a copy of the AST node with new values " + "for the specified fields.")}, {NULL} }; From 2f5f19e783385ec5312f7054827ccf1cdb6e14ef Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 4 Jul 2024 00:17:00 -0700 Subject: [PATCH 40/41] gh-120754: Reduce system calls in full-file FileIO.readall() case (#120755) This reduces the system call count of a simple program[0] that reads all the `.rst` files in Doc by over 10% (5706 -> 4734 system calls on my linux system, 5813 -> 4875 on my macOS) This reduces the number of `fstat()` calls always and seek calls most the time. Stat was always called twice, once at open (to error early on directories), and a second time to get the size of the file to be able to read the whole file in one read. Now the size is cached with the first call. The code keeps an optimization that if the user had previously read a lot of data, the current position is subtracted from the number of bytes to read. That is somewhat expensive so only do it on larger files, otherwise just try and read the extra bytes and resize the PyBytes as needeed. I built a little test program to validate the behavior + assumptions around relative costs and then ran it under `strace` to get a log of the system calls. Full samples below[1]. After the changes, this is everything in one `filename.read_text()`: ```python3 openat(AT_FDCWD, "cpython/Doc/howto/clinic.rst", O_RDONLY|O_CLOEXEC) = 3` fstat(3, {st_mode=S_IFREG|0644, st_size=343, ...}) = 0` ioctl(3, TCGETS, 0x7ffdfac04b40) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 read(3, ":orphan:\n\n.. This page is retain"..., 344) = 343 read(3, "", 1) = 0 close(3) = 0 ``` This does make some tradeoffs 1. If the file size changes between open() and readall(), this will still get all the data but might have more read calls. 2. I experimented with avoiding the stat + cached result for small files in general, but on my dev workstation at least that tended to reduce performance compared to using the fstat(). [0] ```python3 from pathlib import Path nlines = [] for filename in Path("cpython/Doc").glob("**/*.rst"): nlines.append(len(filename.read_text())) ``` [1] Before small file: ``` openat(AT_FDCWD, "cpython/Doc/howto/clinic.rst", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=343, ...}) = 0 ioctl(3, TCGETS, 0x7ffe52525930) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 lseek(3, 0, SEEK_CUR) = 0 fstat(3, {st_mode=S_IFREG|0644, st_size=343, ...}) = 0 read(3, ":orphan:\n\n.. This page is retain"..., 344) = 343 read(3, "", 1) = 0 close(3) = 0 ``` After small file: ``` openat(AT_FDCWD, "cpython/Doc/howto/clinic.rst", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=343, ...}) = 0 ioctl(3, TCGETS, 0x7ffdfac04b40) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 read(3, ":orphan:\n\n.. This page is retain"..., 344) = 343 read(3, "", 1) = 0 close(3) = 0 ``` Before large file: ``` openat(AT_FDCWD, "cpython/Doc/c-api/typeobj.rst", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=133104, ...}) = 0 ioctl(3, TCGETS, 0x7ffe52525930) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 lseek(3, 0, SEEK_CUR) = 0 fstat(3, {st_mode=S_IFREG|0644, st_size=133104, ...}) = 0 read(3, ".. highlight:: c\n\n.. _type-struc"..., 133105) = 133104 read(3, "", 1) = 0 close(3) = 0 ``` After large file: ``` openat(AT_FDCWD, "cpython/Doc/c-api/typeobj.rst", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=133104, ...}) = 0 ioctl(3, TCGETS, 0x7ffdfac04b40) = -1 ENOTTY (Inappropriate ioctl for device) lseek(3, 0, SEEK_CUR) = 0 lseek(3, 0, SEEK_CUR) = 0 read(3, ".. highlight:: c\n\n.. _type-struc"..., 133105) = 133104 read(3, "", 1) = 0 close(3) = 0 ``` Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Co-authored-by: Erlend E. Aasland Co-authored-by: Victor Stinner --- Lib/_pyio.py | 22 +++--- ...-06-19-19-54-35.gh-issue-120754.uF29sj.rst | 1 + Modules/_io/fileio.c | 70 ++++++++++++------- 3 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-06-19-19-54-35.gh-issue-120754.uF29sj.rst diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 7d298e1674b49a..75b5ad1b1a47d2 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -1577,6 +1577,7 @@ def __init__(self, file, mode='r', closefd=True, opener=None): self._blksize = getattr(fdfstat, 'st_blksize', 0) if self._blksize <= 1: self._blksize = DEFAULT_BUFFER_SIZE + self._estimated_size = fdfstat.st_size if _setmode: # don't translate newlines (\r\n <=> \n) @@ -1654,14 +1655,18 @@ def readall(self): """ self._checkClosed() self._checkReadable() - bufsize = DEFAULT_BUFFER_SIZE - try: - pos = os.lseek(self._fd, 0, SEEK_CUR) - end = os.fstat(self._fd).st_size - if end >= pos: - bufsize = end - pos + 1 - except OSError: - pass + if self._estimated_size <= 0: + bufsize = DEFAULT_BUFFER_SIZE + else: + bufsize = self._estimated_size + 1 + + if self._estimated_size > 65536: + try: + pos = os.lseek(self._fd, 0, SEEK_CUR) + if self._estimated_size >= pos: + bufsize = self._estimated_size - pos + 1 + except OSError: + pass result = bytearray() while True: @@ -1737,6 +1742,7 @@ def truncate(self, size=None): if size is None: size = self.tell() os.ftruncate(self._fd, size) + self._estimated_size = size return size def close(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-06-19-19-54-35.gh-issue-120754.uF29sj.rst b/Misc/NEWS.d/next/Core and Builtins/2024-06-19-19-54-35.gh-issue-120754.uF29sj.rst new file mode 100644 index 00000000000000..46481d8f31aaba --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-06-19-19-54-35.gh-issue-120754.uF29sj.rst @@ -0,0 +1 @@ +Reduce the number of system calls invoked when reading a whole file (ex. ``open('a.txt').read()``). For a sample program that reads the contents of the 400+ ``.rst`` files in the cpython repository ``Doc`` folder, there is an over 10% reduction in system call count. diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index b5129ffcbffdcf..d5bf328eee9c10 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -54,6 +54,9 @@ # define SMALLCHUNK BUFSIZ #endif +/* Size at which a buffer is considered "large" and behavior should change to + avoid excessive memory allocation */ +#define LARGE_BUFFER_CUTOFF_SIZE 65536 /*[clinic input] module _io @@ -72,6 +75,7 @@ typedef struct { unsigned int closefd : 1; char finalizing; unsigned int blksize; + Py_off_t estimated_size; PyObject *weakreflist; PyObject *dict; } fileio; @@ -196,6 +200,7 @@ fileio_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->appending = 0; self->seekable = -1; self->blksize = 0; + self->estimated_size = -1; self->closefd = 1; self->weakreflist = NULL; } @@ -482,6 +487,9 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode, if (fdfstat.st_blksize > 1) self->blksize = fdfstat.st_blksize; #endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */ + if (fdfstat.st_size < PY_SSIZE_T_MAX) { + self->estimated_size = (Py_off_t)fdfstat.st_size; + } } #if defined(MS_WINDOWS) || defined(__CYGWIN__) @@ -684,7 +692,7 @@ new_buffersize(fileio *self, size_t currentsize) giving us amortized linear-time behavior. For bigger sizes, use a less-than-double growth factor to avoid excessive allocation. */ assert(currentsize <= PY_SSIZE_T_MAX); - if (currentsize > 65536) + if (currentsize > LARGE_BUFFER_CUTOFF_SIZE) addend = currentsize >> 3; else addend = 256 + currentsize; @@ -707,43 +715,56 @@ static PyObject * _io_FileIO_readall_impl(fileio *self) /*[clinic end generated code: output=faa0292b213b4022 input=dbdc137f55602834]*/ { - struct _Py_stat_struct status; Py_off_t pos, end; PyObject *result; Py_ssize_t bytes_read = 0; Py_ssize_t n; size_t bufsize; - int fstat_result; - if (self->fd < 0) + if (self->fd < 0) { return err_closed(); + } - Py_BEGIN_ALLOW_THREADS - _Py_BEGIN_SUPPRESS_IPH -#ifdef MS_WINDOWS - pos = _lseeki64(self->fd, 0L, SEEK_CUR); -#else - pos = lseek(self->fd, 0L, SEEK_CUR); -#endif - _Py_END_SUPPRESS_IPH - fstat_result = _Py_fstat_noraise(self->fd, &status); - Py_END_ALLOW_THREADS - - if (fstat_result == 0) - end = status.st_size; - else - end = (Py_off_t)-1; - - if (end > 0 && end >= pos && pos >= 0 && end - pos < PY_SSIZE_T_MAX) { + end = self->estimated_size; + if (end <= 0) { + /* Use a default size and resize as needed. */ + bufsize = SMALLCHUNK; + } + else { /* This is probably a real file, so we try to allocate a buffer one byte larger than the rest of the file. If the calculation is right then we should get EOF without having to enlarge the buffer. */ - bufsize = (size_t)(end - pos + 1); - } else { - bufsize = SMALLCHUNK; + if (end > _PY_READ_MAX - 1) { + bufsize = _PY_READ_MAX; + } + else { + bufsize = (size_t)end + 1; + } + + /* While a lot of code does open().read() to get the whole contents + of a file it is possible a caller seeks/reads a ways into the file + then calls readall() to get the rest, which would result in allocating + more than required. Guard against that for larger files where we expect + the I/O time to dominate anyways while keeping small files fast. */ + if (bufsize > LARGE_BUFFER_CUTOFF_SIZE) { + Py_BEGIN_ALLOW_THREADS + _Py_BEGIN_SUPPRESS_IPH +#ifdef MS_WINDOWS + pos = _lseeki64(self->fd, 0L, SEEK_CUR); +#else + pos = lseek(self->fd, 0L, SEEK_CUR); +#endif + _Py_END_SUPPRESS_IPH + Py_END_ALLOW_THREADS + + if (end >= pos && pos >= 0 && (end - pos) < (_PY_READ_MAX - 1)) { + bufsize = (size_t)(end - pos) + 1; + } + } } + result = PyBytes_FromStringAndSize(NULL, bufsize); if (result == NULL) return NULL; @@ -783,7 +804,6 @@ _io_FileIO_readall_impl(fileio *self) return NULL; } bytes_read += n; - pos += n; } if (PyBytes_GET_SIZE(result) > bytes_read) { From 19d1e43e43df97d14c5ab415520b6ccd941e1c88 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:28:44 +0100 Subject: [PATCH 41/41] gh-121352: use _Py_SourceLocation in symtable (#121353) --- Include/internal/pycore_symtable.h | 7 +- Python/symtable.c | 195 ++++++++++------------------- 2 files changed, 69 insertions(+), 133 deletions(-) diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 4cfdf92459c70a..d9ed16a3d2321f 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -127,12 +127,7 @@ typedef struct _symtable_entry { unsigned ste_can_see_class_scope : 1; /* true if this block can see names bound in an enclosing class scope */ int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */ - int ste_lineno; /* first line of block */ - int ste_col_offset; /* offset of first line of block */ - int ste_end_lineno; /* end line of block */ - int ste_end_col_offset; /* end offset of first line of block */ - int ste_opt_lineno; /* lineno of last exec or import * */ - int ste_opt_col_offset; /* offset of last exec or import * */ + _Py_SourceLocation ste_loc; /* source location of block */ struct _symtable_entry *ste_annotation_block; /* symbol table entry for this entry's annotations */ struct symtable *ste_table; } PySTEntryObject; diff --git a/Python/symtable.c b/Python/symtable.c index 65677f86092b0b..6ff07077d4d0ed 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -71,16 +71,15 @@ "duplicate type parameter '%U'" -#define LOCATION(x) \ - (x)->lineno, (x)->col_offset, (x)->end_lineno, (x)->end_col_offset +#define LOCATION(x) SRC_LOCATION_FROM_AST(x) -#define ST_LOCATION(x) \ - (x)->ste_lineno, (x)->ste_col_offset, (x)->ste_end_lineno, (x)->ste_end_col_offset +#define SET_ERROR_LOCATION(FNAME, L) \ + PyErr_RangedSyntaxLocationObject((FNAME), \ + (L).lineno, (L).col_offset + 1, (L).end_lineno, (L).end_col_offset + 1) static PySTEntryObject * ste_new(struct symtable *st, identifier name, _Py_block_ty block, - void *key, int lineno, int col_offset, - int end_lineno, int end_col_offset) + void *key, _Py_SourceLocation loc) { PySTEntryObject *ste = NULL; PyObject *k = NULL; @@ -112,13 +111,8 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block, ste->ste_free = 0; ste->ste_varargs = 0; ste->ste_varkeywords = 0; - ste->ste_opt_lineno = 0; - ste->ste_opt_col_offset = 0; ste->ste_annotations_used = 0; - ste->ste_lineno = lineno; - ste->ste_col_offset = col_offset; - ste->ste_end_lineno = end_lineno; - ste->ste_end_col_offset = end_col_offset; + ste->ste_loc = loc; if (st->st_cur != NULL && (st->st_cur->ste_nested || @@ -158,7 +152,7 @@ static PyObject * ste_repr(PySTEntryObject *ste) { return PyUnicode_FromFormat("", - ste->ste_name, ste->ste_id, ste->ste_lineno); + ste->ste_name, ste->ste_id, ste->ste_loc.lineno); } static void @@ -186,7 +180,7 @@ static PyMemberDef ste_memberlist[] = { {"children", _Py_T_OBJECT, OFF(ste_children), Py_READONLY}, {"nested", Py_T_INT, OFF(ste_nested), Py_READONLY}, {"type", Py_T_INT, OFF(ste_type), Py_READONLY}, - {"lineno", Py_T_INT, OFF(ste_lineno), Py_READONLY}, + {"lineno", Py_T_INT, OFF(ste_loc.lineno), Py_READONLY}, {NULL} }; @@ -233,9 +227,7 @@ PyTypeObject PySTEntry_Type = { static int symtable_analyze(struct symtable *st); static int symtable_enter_block(struct symtable *st, identifier name, - _Py_block_ty block, void *ast, - int lineno, int col_offset, - int end_lineno, int end_col_offset); + _Py_block_ty block, void *ast, _Py_SourceLocation loc); static int symtable_exit_block(struct symtable *st); static int symtable_visit_stmt(struct symtable *st, stmt_ty s); static int symtable_visit_expr(struct symtable *st, expr_ty s); @@ -311,8 +303,8 @@ static void _dump_symtable(PySTEntryObject* ste, PyObject* prefix) ste->ste_comp_iter_target ? " comp_iter_target" : "", ste->ste_can_see_class_scope ? " can_see_class_scope" : "", prefix, - ste->ste_lineno, - ste->ste_col_offset, + ste->ste_loc.lineno, + ste->ste_loc.col_offset, prefix ); assert(msg != NULL); @@ -424,7 +416,9 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, _PyFutureFeatures *future) st->recursion_limit = Py_C_RECURSION_LIMIT; /* Make the initial symbol information gathering pass */ - if (!symtable_enter_block(st, &_Py_ID(top), ModuleBlock, (void *)mod, 0, 0, 0, 0)) { + + _Py_SourceLocation loc0 = {0, 0, 0, 0}; + if (!symtable_enter_block(st, &_Py_ID(top), ModuleBlock, (void *)mod, loc0)) { _PySymtable_Free(st); return NULL; } @@ -1379,11 +1373,9 @@ symtable_enter_existing_block(struct symtable *st, PySTEntryObject* ste) static int symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block, - void *ast, int lineno, int col_offset, - int end_lineno, int end_col_offset) + void *ast, _Py_SourceLocation loc) { - PySTEntryObject *ste = ste_new(st, name, block, ast, - lineno, col_offset, end_lineno, end_col_offset); + PySTEntryObject *ste = ste_new(st, name, block, ast, loc); if (ste == NULL) return 0; int result = symtable_enter_existing_block(st, ste); @@ -1410,7 +1402,7 @@ symtable_lookup(struct symtable *st, PyObject *name) static int symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _symtable_entry *ste, - int lineno, int col_offset, int end_lineno, int end_col_offset) + _Py_SourceLocation loc) { PyObject *o; PyObject *dict; @@ -1425,16 +1417,12 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s if ((flag & DEF_PARAM) && (val & DEF_PARAM)) { /* Is it better to use 'mangled' or 'name' here? */ PyErr_Format(PyExc_SyntaxError, DUPLICATE_ARGUMENT, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - lineno, col_offset + 1, - end_lineno, end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, loc); goto error; } if ((flag & DEF_TYPE_PARAM) && (val & DEF_TYPE_PARAM)) { PyErr_Format(PyExc_SyntaxError, DUPLICATE_TYPE_PARAM, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - lineno, col_offset + 1, - end_lineno, end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, loc); goto error; } val |= flag; @@ -1454,9 +1442,7 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s if (val & (DEF_GLOBAL | DEF_NONLOCAL)) { PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_INNER_LOOP_CONFLICT, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - lineno, col_offset + 1, - end_lineno, end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, loc); goto error; } val |= DEF_COMP_ITER; @@ -1501,33 +1487,28 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s } static int -symtable_add_def(struct symtable *st, PyObject *name, int flag, - int lineno, int col_offset, int end_lineno, int end_col_offset) +symtable_add_def(struct symtable *st, PyObject *name, int flag, _Py_SourceLocation loc) { if ((flag & DEF_TYPE_PARAM) && st->st_cur->ste_mangled_names != NULL) { if(PySet_Add(st->st_cur->ste_mangled_names, name) < 0) { return 0; } } - return symtable_add_def_helper(st, name, flag, st->st_cur, - lineno, col_offset, end_lineno, end_col_offset); + return symtable_add_def_helper(st, name, flag, st->st_cur, loc); } static int symtable_enter_type_param_block(struct symtable *st, identifier name, void *ast, int has_defaults, int has_kwdefaults, - enum _stmt_kind kind, - int lineno, int col_offset, - int end_lineno, int end_col_offset) + enum _stmt_kind kind, _Py_SourceLocation loc) { _Py_block_ty current_type = st->st_cur->ste_type; - if(!symtable_enter_block(st, name, TypeParametersBlock, ast, lineno, - col_offset, end_lineno, end_col_offset)) { + if(!symtable_enter_block(st, name, TypeParametersBlock, ast, loc)) { return 0; } if (current_type == ClassBlock) { st->st_cur->ste_can_see_class_scope = 1; - if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_ID(__classdict__), USE, loc)) { return 0; } } @@ -1535,36 +1516,30 @@ symtable_enter_type_param_block(struct symtable *st, identifier name, _Py_DECLARE_STR(type_params, ".type_params"); // It gets "set" when we create the type params tuple and // "used" when we build up the bases. - if (!symtable_add_def(st, &_Py_STR(type_params), DEF_LOCAL, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(type_params), DEF_LOCAL, loc)) { return 0; } - if (!symtable_add_def(st, &_Py_STR(type_params), USE, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(type_params), USE, loc)) { return 0; } // This is used for setting the generic base _Py_DECLARE_STR(generic_base, ".generic_base"); - if (!symtable_add_def(st, &_Py_STR(generic_base), DEF_LOCAL, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(generic_base), DEF_LOCAL, loc)) { return 0; } - if (!symtable_add_def(st, &_Py_STR(generic_base), USE, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(generic_base), USE, loc)) { return 0; } } if (has_defaults) { _Py_DECLARE_STR(defaults, ".defaults"); - if (!symtable_add_def(st, &_Py_STR(defaults), DEF_PARAM, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(defaults), DEF_PARAM, loc)) { return 0; } } if (has_kwdefaults) { _Py_DECLARE_STR(kwdefaults, ".kwdefaults"); - if (!symtable_add_def(st, &_Py_STR(kwdefaults), DEF_PARAM, - lineno, col_offset, end_lineno, end_col_offset)) { + if (!symtable_add_def(st, &_Py_STR(kwdefaults), DEF_PARAM, loc)) { return 0; } } @@ -1627,8 +1602,7 @@ symtable_enter_type_param_block(struct symtable *st, identifier name, } while(0) static int -symtable_record_directive(struct symtable *st, identifier name, int lineno, - int col_offset, int end_lineno, int end_col_offset) +symtable_record_directive(struct symtable *st, identifier name, _Py_SourceLocation loc) { PyObject *data, *mangled; int res; @@ -1640,7 +1614,8 @@ symtable_record_directive(struct symtable *st, identifier name, int lineno, mangled = _Py_MaybeMangle(st->st_private, st->st_cur, name); if (!mangled) return 0; - data = Py_BuildValue("(Niiii)", mangled, lineno, col_offset, end_lineno, end_col_offset); + data = Py_BuildValue("(Niiii)", mangled, loc.lineno, loc.col_offset, + loc.end_lineno, loc.end_col_offset); if (!data) return 0; res = PyList_Append(st->st_cur->ste_directives, data); @@ -1673,9 +1648,7 @@ check_import_from(struct symtable *st, stmt_ty s) PyErr_SetString(PyExc_SyntaxError, "from __future__ imports must occur " "at the beginning of the file"); - PyErr_RangedSyntaxLocationObject(st->st_filename, - s->lineno, s->col_offset + 1, - s->end_lineno, s->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(s)); return 0; } return 1; @@ -1772,9 +1745,9 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) VISIT_SEQ(st, expr, s->v.ClassDef.bases); VISIT_SEQ(st, keyword, s->v.ClassDef.keywords); if (!symtable_enter_block(st, s->v.ClassDef.name, ClassBlock, - (void *)s, s->lineno, s->col_offset, - s->end_lineno, s->end_col_offset)) + (void *)s, LOCATION(s))) { VISIT_QUIT(st, 0); + } st->st_private = s->v.ClassDef.name; if (asdl_seq_LEN(s->v.ClassDef.type_params) > 0) { if (!symtable_add_def(st, &_Py_ID(__type_params__), @@ -1814,8 +1787,9 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) VISIT_SEQ(st, type_param, s->v.TypeAlias.type_params); } if (!symtable_enter_block(st, name, TypeAliasBlock, - (void *)s, LOCATION(s))) + (void *)s, LOCATION(s))) { VISIT_QUIT(st, 0); + } st->st_cur->ste_can_see_class_scope = is_in_class; if (is_in_class && !symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(s->v.TypeAlias.value))) { VISIT_QUIT(st, 0); @@ -1856,11 +1830,7 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) PyErr_Format(PyExc_SyntaxError, cur & DEF_GLOBAL ? GLOBAL_ANNOT : NONLOCAL_ANNOT, e_name->v.Name.id); - PyErr_RangedSyntaxLocationObject(st->st_filename, - s->lineno, - s->col_offset + 1, - s->end_lineno, - s->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(s)); VISIT_QUIT(st, 0); } if (s->v.AnnAssign.simple && @@ -1970,18 +1940,15 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) } PyErr_Format(PyExc_SyntaxError, msg, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - s->lineno, - s->col_offset + 1, - s->end_lineno, - s->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(s)); VISIT_QUIT(st, 0); } - if (!symtable_add_def(st, name, DEF_GLOBAL, LOCATION(s))) + if (!symtable_add_def(st, name, DEF_GLOBAL, LOCATION(s))) { VISIT_QUIT(st, 0); - if (!symtable_record_directive(st, name, s->lineno, s->col_offset, - s->end_lineno, s->end_col_offset)) + } + if (!symtable_record_directive(st, name, LOCATION(s))) { VISIT_QUIT(st, 0); + } } break; } @@ -2005,18 +1972,14 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) msg = NONLOCAL_AFTER_ASSIGN; } PyErr_Format(PyExc_SyntaxError, msg, name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - s->lineno, - s->col_offset + 1, - s->end_lineno, - s->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(s)); VISIT_QUIT(st, 0); } if (!symtable_add_def(st, name, DEF_NONLOCAL, LOCATION(s))) VISIT_QUIT(st, 0); - if (!symtable_record_directive(st, name, s->lineno, s->col_offset, - s->end_lineno, s->end_col_offset)) + if (!symtable_record_directive(st, name, LOCATION(s))) { VISIT_QUIT(st, 0); + } } break; } @@ -2124,11 +2087,7 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e) if ((target_in_scope & DEF_COMP_ITER) && (target_in_scope & DEF_LOCAL)) { PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_CONFLICT, target_name); - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, - e->col_offset + 1, - e->end_lineno, - e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); VISIT_QUIT(st, 0); } continue; @@ -2141,20 +2100,24 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e) if (!symtable_add_def(st, target_name, DEF_GLOBAL, LOCATION(e))) VISIT_QUIT(st, 0); } else { - if (!symtable_add_def(st, target_name, DEF_NONLOCAL, LOCATION(e))) + if (!symtable_add_def(st, target_name, DEF_NONLOCAL, LOCATION(e))) { VISIT_QUIT(st, 0); + } } - if (!symtable_record_directive(st, target_name, LOCATION(e))) + if (!symtable_record_directive(st, target_name, LOCATION(e))) { VISIT_QUIT(st, 0); + } return symtable_add_def_helper(st, target_name, DEF_LOCAL, ste, LOCATION(e)); } /* If we find a ModuleBlock entry, add as GLOBAL */ if (ste->ste_type == ModuleBlock) { - if (!symtable_add_def(st, target_name, DEF_GLOBAL, LOCATION(e))) + if (!symtable_add_def(st, target_name, DEF_GLOBAL, LOCATION(e))) { VISIT_QUIT(st, 0); - if (!symtable_record_directive(st, target_name, LOCATION(e))) + } + if (!symtable_record_directive(st, target_name, LOCATION(e))) { VISIT_QUIT(st, 0); + } return symtable_add_def_helper(st, target_name, DEF_GLOBAL, ste, LOCATION(e)); } @@ -2179,11 +2142,7 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e) default: Py_UNREACHABLE(); } - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, - e->col_offset + 1, - e->end_lineno, - e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); VISIT_QUIT(st, 0); } } @@ -2201,11 +2160,7 @@ symtable_handle_namedexpr(struct symtable *st, expr_ty e) if (st->st_cur->ste_comp_iter_expr > 0) { /* Assignment isn't allowed in a comprehension iterable expression */ PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_ITER_EXPR); - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, - e->col_offset + 1, - e->end_lineno, - e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); return 0; } if (st->st_cur->ste_comprehension) { @@ -2250,10 +2205,9 @@ symtable_visit_expr(struct symtable *st, expr_ty e) if (e->v.Lambda.args->kw_defaults) VISIT_SEQ_WITH_NULL(st, expr, e->v.Lambda.args->kw_defaults); if (!symtable_enter_block(st, &_Py_ID(lambda), - FunctionBlock, (void *)e, - e->lineno, e->col_offset, - e->end_lineno, e->end_col_offset)) + FunctionBlock, (void *)e, LOCATION(e))) { VISIT_QUIT(st, 0); + } VISIT(st, arguments, e->v.Lambda.args); VISIT(st, expr, e->v.Lambda.body); if (!symtable_exit_block(st)) @@ -2385,8 +2339,9 @@ symtable_visit_type_param_bound_or_default( { if (e) { int is_in_class = st->st_cur->ste_can_see_class_scope; - if (!symtable_enter_block(st, name, TypeVariableBlock, key, LOCATION(e))) + if (!symtable_enter_block(st, name, TypeVariableBlock, key, LOCATION(e))) { return 0; + } st->st_cur->ste_can_see_class_scope = is_in_class; if (is_in_class && !symtable_add_def(st, &_Py_ID(__classdict__), USE, LOCATION(e))) { @@ -2519,7 +2474,7 @@ symtable_implicit_arg(struct symtable *st, int pos) PyObject *id = PyUnicode_FromFormat(".%d", pos); if (id == NULL) return 0; - if (!symtable_add_def(st, id, DEF_PARAM, ST_LOCATION(st->st_cur))) { + if (!symtable_add_def(st, id, DEF_PARAM, st->st_cur->ste_loc)) { Py_DECREF(id); return 0; } @@ -2740,14 +2695,8 @@ symtable_visit_alias(struct symtable *st, alias_ty a) } else { if (st->st_cur->ste_type != ModuleBlock) { - int lineno = a->lineno; - int col_offset = a->col_offset; - int end_lineno = a->end_lineno; - int end_col_offset = a->end_col_offset; PyErr_SetString(PyExc_SyntaxError, IMPORT_STAR_WARNING); - PyErr_RangedSyntaxLocationObject(st->st_filename, - lineno, col_offset + 1, - end_lineno, end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(a)); Py_DECREF(store_name); return 0; } @@ -2796,9 +2745,7 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e, st->st_cur->ste_comp_iter_expr--; /* Create comprehension scope for the rest */ if (!scope_name || - !symtable_enter_block(st, scope_name, FunctionBlock, (void *)e, - e->lineno, e->col_offset, - e->end_lineno, e->end_col_offset)) { + !symtable_enter_block(st, scope_name, FunctionBlock, (void *)e, LOCATION(e))) { return 0; } switch(e->kind) { @@ -2902,11 +2849,7 @@ symtable_raise_if_annotation_block(struct symtable *st, const char *name, expr_t else return 1; - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, - e->col_offset + 1, - e->end_lineno, - e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); return 0; } @@ -2918,9 +2861,7 @@ symtable_raise_if_comprehension_block(struct symtable *st, expr_ty e) { (type == SetComprehension) ? "'yield' inside set comprehension" : (type == DictComprehension) ? "'yield' inside dict comprehension" : "'yield' inside generator expression"); - PyErr_RangedSyntaxLocationObject(st->st_filename, - e->lineno, e->col_offset + 1, - e->end_lineno, e->end_col_offset + 1); + SET_ERROR_LOCATION(st->st_filename, LOCATION(e)); VISIT_QUIT(st, 0); }