From 909bde3eaf4ebcd63c315104defed27a8722eb08 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Sun, 17 May 2026 18:39:20 -0400 Subject: [PATCH 01/33] Update _elementtree.c --- Modules/_elementtree.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index cee564c2c1c1f0f..43bd6699f04a96b 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -1946,6 +1946,34 @@ element_subscr(PyObject *op, PyObject *item) } } +// Pointer-by-pointer memmove for PyObject** arrays that is safe +// for shared ElementObjects in Py_GIL_DISABLED builds. +static void +ptr_wise_atomic_memmove(ElementObject *a, PyObject **dest, PyObject **src, Py_ssize_t n) +{ +#ifndef Py_GIL_DISABLED + memmove(dest, src, n * sizeof(PyObject *)); +#else + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); + if (_Py_IsOwnedByCurrentThread((PyObject *)a) && !_PyObject_GC_IS_SHARED(a)) { + // No other threads can read this list concurrently + memmove(dest, src, n * sizeof(PyObject *)); + return; + } + if (dest < src) { + for (Py_ssize_t i = 0; i != n; i++) { + _Py_atomic_store_ptr_release(&dest[i], src[i]); + } + } + else { + // copy backwards to avoid overwriting src before it's read + for (Py_ssize_t i = n; i != 0; i--) { + _Py_atomic_store_ptr_release(&dest[i - 1], src[i - 1]); + } + } +#endif +} + static int element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) { @@ -2021,19 +2049,21 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) PyList_SET_ITEM(recycle, i, self->extra->children[cur]); - memmove( + ptr_wise_atomic_memmove( + self, self->extra->children + cur - i, self->extra->children + cur + 1, - num_moved * sizeof(PyObject *)); + num_moved); } /* Leftover "tail" after the last removed child */ cur = start + (size_t)slicelen * step; if (cur < (size_t)self->extra->length) { - memmove( + ptr_wise_atomic_memmove( + self, self->extra->children + cur - slicelen, self->extra->children + cur, - (self->extra->length - cur) * sizeof(PyObject *)); + self->extra->length - cur); } self->extra->length -= slicelen; From f916acddd35950f7b29d507e1e3c283f96f4b112 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 22:42:57 +0000 Subject: [PATCH 02/33] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst diff --git a/Misc/NEWS.d/next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst b/Misc/NEWS.d/next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst new file mode 100644 index 000000000000000..ea8eac20e4c8ec3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst @@ -0,0 +1 @@ +WIthin ``Modules/_elementtree.c``, improve thread-safety in ElementObjects within ``element_ass_subscr`` From 8ec9c38e4f0381834e892aff6b6dfe3c7b82d45f Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Sun, 17 May 2026 18:59:10 -0400 Subject: [PATCH 03/33] Modify critical section assertion in _elementtree.c Commented out critical section assertion for ElementObject. --- Modules/_elementtree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 43bd6699f04a96b..c4cca0e2fd6ae5f 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -1954,7 +1954,8 @@ ptr_wise_atomic_memmove(ElementObject *a, PyObject **dest, PyObject **src, Py_ss #ifndef Py_GIL_DISABLED memmove(dest, src, n * sizeof(PyObject *)); #else - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); + // XXX: maybe a critical section isn't needed for ElementObject? + // _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); if (_Py_IsOwnedByCurrentThread((PyObject *)a) && !_PyObject_GC_IS_SHARED(a)) { // No other threads can read this list concurrently memmove(dest, src, n * sizeof(PyObject *)); From 8ac6aca94b56176d204ffd69f4162e96d19e33a5 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Wed, 10 Jun 2026 12:55:17 -0400 Subject: [PATCH 04/33] =?UTF-8?q?Revert=20"=F0=9F=93=9C=F0=9F=A4=96=20Adde?= =?UTF-8?q?d=20by=20blurb=5Fit."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a3a7eee2343990a5368a427320c852e5de8bd70a. --- ...-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst | 1 - .../_testinternalcapi/test_ptr_wise_memmove.c | 212 ++++++++++++++++++ 2 files changed, 212 insertions(+), 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst create mode 100644 Modules/_testinternalcapi/test_ptr_wise_memmove.c diff --git a/Misc/NEWS.d/next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst b/Misc/NEWS.d/next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst deleted file mode 100644 index ea8eac20e4c8ec3..000000000000000 --- a/Misc/NEWS.d/next/Library/2026-05-17-22-42-54.gh-issue-149965.OFW3ZD.rst +++ /dev/null @@ -1 +0,0 @@ -WIthin ``Modules/_elementtree.c``, improve thread-safety in ElementObjects within ``element_ass_subscr`` diff --git a/Modules/_testinternalcapi/test_ptr_wise_memmove.c b/Modules/_testinternalcapi/test_ptr_wise_memmove.c new file mode 100644 index 000000000000000..732642992b568e8 --- /dev/null +++ b/Modules/_testinternalcapi/test_ptr_wise_memmove.c @@ -0,0 +1,212 @@ +// Tests for _PyObject_ptr_wise_atomic_memmove() in pycore_object.h + +#include "parts.h" +#include "pycore_object.h" // _PyObject_ptr_wise_atomic_memmove() +#include "pycore_gc.h" // _PyObject_GC_SET_SHARED() + +// Five distinguishable immortal singletons used as placeholder pointers. +// These require no reference-count management when stored in a raw array. +#define NPTRS 5 +static PyObject *test_objs[NPTRS]; + +static void +setup_test_objs(void) +{ + test_objs[0] = Py_None; + test_objs[1] = Py_True; + test_objs[2] = Py_False; + test_objs[3] = Py_Ellipsis; + test_objs[4] = Py_NotImplemented; +} + +// Fill buf[0..NPTRS-1] with test_objs in order. +static void +fill_buf(PyObject **buf) +{ + for (int i = 0; i < NPTRS; i++) { + buf[i] = test_objs[i]; + } +} + +// Return a fresh empty PyListObject whose GC SHARED bit is set in +// Py_GIL_DISABLED builds. This forces _PyObject_ptr_wise_atomic_memmove() +// to take the atomic (non-fast) path so we can exercise all branches. +// In non-GIL builds the function always uses memmove, so no flag is needed. +static PyObject * +make_shared_container(void) +{ + PyObject *a = PyList_New(0); + if (a == NULL) { + return NULL; + } +#ifdef Py_GIL_DISABLED + _PyObject_GC_SET_SHARED(a); +#endif + return a; +} + +// Helper: create container, call the function, return it for cleanup. +// Returns NULL (with exception set) on allocation failure. +static PyObject * +call_memmove(PyObject **dest, PyObject **src, Py_ssize_t n) +{ + PyObject *a = make_shared_container(); + if (a != NULL) { + _PyObject_ptr_wise_atomic_memmove(a, dest, src, n); + } + return a; +} + + +// dest < src: forward pointer-by-pointer copy. +// buf = [0,1,2,3,4], copy src=&buf[2] n=3 to dest=&buf[0] +// Expected result: buf = [2,3,4,3,4] +static PyObject * +test_memmove_dest_lt_src(PyObject *self, PyObject *Py_UNUSED(arg)) +{ + setup_test_objs(); + + PyObject *buf[NPTRS]; + fill_buf(buf); + + PyObject *a = call_memmove(&buf[0], &buf[2], 3); + if (a == NULL) { + return NULL; + } + + assert(buf[0] == test_objs[2]); + assert(buf[1] == test_objs[3]); + assert(buf[2] == test_objs[4]); + assert(buf[3] == test_objs[3]); // unchanged + assert(buf[4] == test_objs[4]); // unchanged + + Py_DECREF(a); + Py_RETURN_NONE; +} + +// dest > src: backward pointer-by-pointer copy. +// buf = [0,1,2,3,4], copy src=&buf[0] n=3 to dest=&buf[2] +// Expected result: buf = [0,1,0,1,2] +static PyObject * +test_memmove_dest_gt_src(PyObject *self, PyObject *Py_UNUSED(arg)) +{ + setup_test_objs(); + + PyObject *buf[NPTRS]; + fill_buf(buf); + + PyObject *a = call_memmove(&buf[2], &buf[0], 3); + if (a == NULL) { + return NULL; + } + + assert(buf[0] == test_objs[0]); // unchanged + assert(buf[1] == test_objs[1]); // unchanged + assert(buf[2] == test_objs[0]); + assert(buf[3] == test_objs[1]); + assert(buf[4] == test_objs[2]); + + Py_DECREF(a); + Py_RETURN_NONE; +} + +// dest == src: backward copy where every write is a no-op. +// buf = [0,1,2,3,4], copy src=&buf[1] n=3 to dest=&buf[1] +// Expected result: buf unchanged. +static PyObject * +test_memmove_dest_eq_src(PyObject *self, PyObject *Py_UNUSED(arg)) +{ + setup_test_objs(); + + PyObject *buf[NPTRS]; + fill_buf(buf); + + PyObject *a = call_memmove(&buf[1], &buf[1], 3); + if (a == NULL) { + return NULL; + } + + for (int i = 0; i < NPTRS; i++) { + assert(buf[i] == test_objs[i]); + } + + Py_DECREF(a); + Py_RETURN_NONE; +} + +// Overlapping ranges, dest < src: forward copy is safe. +// buf = [0,1,2,3,4], copy src=&buf[2] n=3 to dest=&buf[1] +// Forward: buf[1]=buf[2]=2, buf[2]=buf[3]=3, buf[3]=buf[4]=4 +// Expected result: buf = [0,2,3,4,4] +static PyObject * +test_memmove_overlapping(PyObject *self, PyObject *Py_UNUSED(arg)) +{ + setup_test_objs(); + + PyObject *buf[NPTRS]; + fill_buf(buf); + + PyObject *a = call_memmove(&buf[1], &buf[2], 3); + if (a == NULL) { + return NULL; + } + + assert(buf[0] == test_objs[0]); // unchanged + assert(buf[1] == test_objs[2]); + assert(buf[2] == test_objs[3]); + assert(buf[3] == test_objs[4]); + assert(buf[4] == test_objs[4]); // unchanged + + Py_DECREF(a); + Py_RETURN_NONE; +} + +// Single owner fast path (GIL_DISABLED only): object owned by this thread +// and not GC-shared => memmove is used instead of atomic stores. +// In non-GIL builds this always takes the memmove path, so the test +// degenerates to a basic correctness check. +static PyObject * +test_memmove_single_owner(PyObject *self, PyObject *Py_UNUSED(arg)) +{ + setup_test_objs(); + + PyObject *a = PyList_New(0); + if (a == NULL) { + return NULL; + } + +#ifdef Py_GIL_DISABLED + // A freelist-reused list may carry the SHARED bit set by a sibling test. + // Clear it explicitly so this test exercises the single-owner fast path. + _PyObject_CLEAR_GC_BITS(a, _PyGC_BITS_SHARED); + assert(_Py_IsOwnedByCurrentThread(a) && !_PyObject_GC_IS_SHARED(a)); +#endif + + PyObject *buf[NPTRS]; + fill_buf(buf); + + _PyObject_ptr_wise_atomic_memmove(a, &buf[0], &buf[2], 3); + + assert(buf[0] == test_objs[2]); + assert(buf[1] == test_objs[3]); + assert(buf[2] == test_objs[4]); + + Py_DECREF(a); + Py_RETURN_NONE; +} + + +static PyMethodDef test_methods[] = { + {"test_memmove_dest_lt_src", test_memmove_dest_lt_src, METH_NOARGS}, + {"test_memmove_dest_gt_src", test_memmove_dest_gt_src, METH_NOARGS}, + {"test_memmove_dest_eq_src", test_memmove_dest_eq_src, METH_NOARGS}, + {"test_memmove_overlapping", test_memmove_overlapping, METH_NOARGS}, + {"test_memmove_single_owner", test_memmove_single_owner, METH_NOARGS}, + {NULL}, +}; + +int +_PyTestInternalCapi_Init_PtrWiseMemmove(PyObject *mod) +{ + return PyModule_AddFunctions(mod, test_methods); +} From 1443d4dcd5a6b59eb669794d82b871073667f5c0 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 9 Jun 2026 14:06:29 -0400 Subject: [PATCH 05/33] gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests Extract the duplicated static `ptr_wise_atomic_memmove` from `Modules/_elementtree.c` and `Objects/listobject.c` into a shared `static inline _Py_ptr_wise_atomic_memmove(PyObject *a, ...)` in `Include/internal/pycore_object.h`. The first parameter is generalised from a type-specific pointer to `PyObject *` since only `PyObject *`- level operations (`_Py_IsOwnedByCurrentThread`, `_PyObject_GC_IS_SHARED`) were ever performed on it. `listobject.c` previously embedded `_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED` inside the local helper. That assertion is preserved by moving it explicitly to each of the four call sites in `listobject.c` (which are all called under a critical section). `_elementtree.c`'s open question about whether a critical section is needed remains unanswered, so no assertion is added there. Unit tests are added in `Modules/_testinternalcapi/test_ptr_wise_memmove.c` covering: dest < src (forward copy), dest > src (backward copy), dest == src (no-op), overlapping ranges, and the single-owner fast path. The single-owner test explicitly clears the GC SHARED bit to guard against freelist reuse leaving the bit set from a sibling test. Co-Authored-By: Claude Sonnet 4.6 --- Include/internal/pycore_object.h | 29 +++++++++++++ Modules/Setup.stdlib.in | 6 +-- Modules/_elementtree.c | 37 +++------------- Modules/_testinternalcapi.c | 3 ++ Modules/_testinternalcapi/parts.h | 1 + .../_testinternalcapi/test_ptr_wise_memmove.c | 19 ++++++++ Objects/listobject.c | 43 +++++-------------- PCbuild/_testinternalcapi.vcxproj | 1 + 8 files changed, 71 insertions(+), 68 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index dbc0ebf994fd1e0..16ed31cb7a529d7 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -1017,6 +1017,35 @@ static inline Py_ALWAYS_INLINE void _Py_INCREF_MORTAL(PyObject *op) * references. */ PyAPI_FUNC(int) _PyObject_VisitType(PyObject *op, visitproc visit, void *arg); +// Pointer-by-pointer memmove for PyObject** arrays that is safe for shared +// objects in Py_GIL_DISABLED builds. Locking is the caller's responsibility. +static inline void +_Py_ptr_wise_atomic_memmove(PyObject *a, PyObject **dest, PyObject **src, + Py_ssize_t n) +{ +#ifndef Py_GIL_DISABLED + (void)a; + memmove(dest, src, n * sizeof(PyObject *)); +#else + if (_Py_IsOwnedByCurrentThread(a) && !_PyObject_GC_IS_SHARED(a)) { + // No other threads can read this object's array concurrently + memmove(dest, src, n * sizeof(PyObject *)); + return; + } + if (dest < src) { + for (Py_ssize_t i = 0; i != n; i++) { + _Py_atomic_store_ptr_release(&dest[i], src[i]); + } + } + else { + // copy backwards to avoid overwriting src before it's read + for (Py_ssize_t i = n; i != 0; i--) { + _Py_atomic_store_ptr_release(&dest[i - 1], src[i - 1]); + } + } +#endif +} + #ifdef __cplusplus } #endif diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index d8cd8de10dfe084..355a9499ed7130c 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -172,9 +172,9 @@ @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c -@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c _testcapi/fpcast.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c _testlimitedcapi/build.c +@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/test_ptr_wise_memmove.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index c4cca0e2fd6ae5f..680265764dc3d82 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -18,6 +18,7 @@ #include "Python.h" #include "pycore_ceval.h" // _Py_EnterRecursiveCall() #include "pycore_dict.h" // _PyDict_CopyAsDict() +#include "pycore_object.h" // _Py_ptr_wise_atomic_memmove() #include "pycore_pyhash.h" // _Py_HashSecret #include "pycore_tuple.h" // _PyTuple_FromPair #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() @@ -1946,34 +1947,6 @@ element_subscr(PyObject *op, PyObject *item) } } -// Pointer-by-pointer memmove for PyObject** arrays that is safe -// for shared ElementObjects in Py_GIL_DISABLED builds. -static void -ptr_wise_atomic_memmove(ElementObject *a, PyObject **dest, PyObject **src, Py_ssize_t n) -{ -#ifndef Py_GIL_DISABLED - memmove(dest, src, n * sizeof(PyObject *)); -#else - // XXX: maybe a critical section isn't needed for ElementObject? - // _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); - if (_Py_IsOwnedByCurrentThread((PyObject *)a) && !_PyObject_GC_IS_SHARED(a)) { - // No other threads can read this list concurrently - memmove(dest, src, n * sizeof(PyObject *)); - return; - } - if (dest < src) { - for (Py_ssize_t i = 0; i != n; i++) { - _Py_atomic_store_ptr_release(&dest[i], src[i]); - } - } - else { - // copy backwards to avoid overwriting src before it's read - for (Py_ssize_t i = n; i != 0; i--) { - _Py_atomic_store_ptr_release(&dest[i - 1], src[i - 1]); - } - } -#endif -} static int element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) @@ -2050,8 +2023,8 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) PyList_SET_ITEM(recycle, i, self->extra->children[cur]); - ptr_wise_atomic_memmove( - self, + _Py_ptr_wise_atomic_memmove( + (PyObject *)self, self->extra->children + cur - i, self->extra->children + cur + 1, num_moved); @@ -2060,8 +2033,8 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) /* Leftover "tail" after the last removed child */ cur = start + (size_t)slicelen * step; if (cur < (size_t)self->extra->length) { - ptr_wise_atomic_memmove( - self, + _Py_ptr_wise_atomic_memmove( + (PyObject *)self, self->extra->children + cur - slicelen, self->extra->children + cur, self->extra->length - cur); diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index f629b8e528faca7..7f90a6440a57350 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -3482,6 +3482,9 @@ module_exec(PyObject *module) if (_PyTestInternalCapi_Init_PyTime(module) < 0) { return 1; } + if (_PyTestInternalCapi_Init_PtrWiseMemmove(module) < 0) { + return 1; + } if (_PyTestInternalCapi_Init_Set(module) < 0) { return 1; } diff --git a/Modules/_testinternalcapi/parts.h b/Modules/_testinternalcapi/parts.h index 9b06eec3bce9e65..60a41e2c49b7108 100644 --- a/Modules/_testinternalcapi/parts.h +++ b/Modules/_testinternalcapi/parts.h @@ -12,6 +12,7 @@ int _PyTestInternalCapi_Init_Lock(PyObject *module); int _PyTestInternalCapi_Init_PyTime(PyObject *module); +int _PyTestInternalCapi_Init_PtrWiseMemmove(PyObject *module); int _PyTestInternalCapi_Init_Set(PyObject *module); int _PyTestInternalCapi_Init_Complex(PyObject *module); int _PyTestInternalCapi_Init_CriticalSection(PyObject *module); diff --git a/Modules/_testinternalcapi/test_ptr_wise_memmove.c b/Modules/_testinternalcapi/test_ptr_wise_memmove.c index 732642992b568e8..f5b5b97223d2b12 100644 --- a/Modules/_testinternalcapi/test_ptr_wise_memmove.c +++ b/Modules/_testinternalcapi/test_ptr_wise_memmove.c @@ -1,7 +1,14 @@ +<<<<<<< HEAD // Tests for _PyObject_ptr_wise_atomic_memmove() in pycore_object.h #include "parts.h" #include "pycore_object.h" // _PyObject_ptr_wise_atomic_memmove() +======= +// Tests for _Py_ptr_wise_atomic_memmove() in pycore_object.h + +#include "parts.h" +#include "pycore_object.h" // _Py_ptr_wise_atomic_memmove() +>>>>>>> a4c9efbacc (gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests) #include "pycore_gc.h" // _PyObject_GC_SET_SHARED() // Five distinguishable immortal singletons used as placeholder pointers. @@ -29,7 +36,11 @@ fill_buf(PyObject **buf) } // Return a fresh empty PyListObject whose GC SHARED bit is set in +<<<<<<< HEAD // Py_GIL_DISABLED builds. This forces _PyObject_ptr_wise_atomic_memmove() +======= +// Py_GIL_DISABLED builds. This forces _Py_ptr_wise_atomic_memmove() +>>>>>>> a4c9efbacc (gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests) // to take the atomic (non-fast) path so we can exercise all branches. // In non-GIL builds the function always uses memmove, so no flag is needed. static PyObject * @@ -52,7 +63,11 @@ call_memmove(PyObject **dest, PyObject **src, Py_ssize_t n) { PyObject *a = make_shared_container(); if (a != NULL) { +<<<<<<< HEAD _PyObject_ptr_wise_atomic_memmove(a, dest, src, n); +======= + _Py_ptr_wise_atomic_memmove(a, dest, src, n); +>>>>>>> a4c9efbacc (gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests) } return a; } @@ -185,7 +200,11 @@ test_memmove_single_owner(PyObject *self, PyObject *Py_UNUSED(arg)) PyObject *buf[NPTRS]; fill_buf(buf); +<<<<<<< HEAD _PyObject_ptr_wise_atomic_memmove(a, &buf[0], &buf[2], 3); +======= + _Py_ptr_wise_atomic_memmove(a, &buf[0], &buf[2], 3); +>>>>>>> a4c9efbacc (gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests) assert(buf[0] == test_objs[2]); assert(buf[1] == test_objs[3]); diff --git a/Objects/listobject.c b/Objects/listobject.c index f9488cc5c384bab..e86ae721f9acba3 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -10,7 +10,7 @@ #include "pycore_list.h" // struct _Py_list_freelist, _PyListIterObject #include "pycore_long.h" // _PyLong_DigitCount #include "pycore_modsupport.h" // _PyArg_NoKwnames() -#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() +#include "pycore_object.h" // _PyObject_GC_TRACK(), _Py_ptr_wise_atomic_memmove() #include "pycore_pyatomic_ft_wrappers.h" #include "pycore_setobject.h" // _PySet_NextEntry() #include "pycore_stackref.h" // _Py_TryIncrefCompareStackRef() @@ -967,33 +967,6 @@ list_clear_slot(PyObject *self) return 0; } -// Pointer-by-pointer memmove for PyObject** arrays that is safe -// for shared lists in Py_GIL_DISABLED builds. -static void -ptr_wise_atomic_memmove(PyListObject *a, PyObject **dest, PyObject **src, Py_ssize_t n) -{ -#ifndef Py_GIL_DISABLED - memmove(dest, src, n * sizeof(PyObject *)); -#else - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); - if (_Py_IsOwnedByCurrentThread((PyObject *)a) && !_PyObject_GC_IS_SHARED(a)) { - // No other threads can read this list concurrently - memmove(dest, src, n * sizeof(PyObject *)); - return; - } - if (dest < src) { - for (Py_ssize_t i = 0; i != n; i++) { - _Py_atomic_store_ptr_release(&dest[i], src[i]); - } - } - else { - // copy backwards to avoid overwriting src before it's read - for (Py_ssize_t i = n; i != 0; i--) { - _Py_atomic_store_ptr_release(&dest[i - 1], src[i - 1]); - } - } -#endif -} /* a[ilow:ihigh] = v if v != NULL. * del a[ilow:ihigh] if v == NULL. @@ -1066,7 +1039,8 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO if (d < 0) { /* Delete -d items */ Py_ssize_t tail = Py_SIZE(a) - ihigh; - ptr_wise_atomic_memmove(a, &item[ihigh+d], &item[ihigh], tail); + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); + _Py_ptr_wise_atomic_memmove((PyObject *)a, &item[ihigh+d], &item[ihigh], tail); (void)list_resize(a, Py_SIZE(a) + d); // NB: shrinking a list can't fail item = a->ob_item; } @@ -1075,7 +1049,8 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO if (list_resize(a, k+d) < 0) goto Error; item = a->ob_item; - ptr_wise_atomic_memmove(a, &item[ihigh+d], &item[ihigh], k - ihigh); + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); + _Py_ptr_wise_atomic_memmove((PyObject *)a, &item[ihigh+d], &item[ihigh], k - ihigh); } for (k = 0; k < n; k++, ilow++) { PyObject *w = vitem[k]; @@ -1163,10 +1138,11 @@ list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n) _Py_memory_repeat((char *)items, sizeof(PyObject *)*output_size, sizeof(PyObject *)*input_size); #else + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); Py_ssize_t copied = input_size; while (copied < output_size) { Py_ssize_t items_to_copy = Py_MIN(copied, output_size - copied); - ptr_wise_atomic_memmove(self, items + copied, items, items_to_copy); + _Py_ptr_wise_atomic_memmove((PyObject *)self, items + copied, items, items_to_copy); copied += items_to_copy; } #endif @@ -1664,8 +1640,9 @@ list_pop_impl(PyListObject *self, Py_ssize_t index) } Py_ssize_t size_after_pop = Py_SIZE(self) - 1; if (index < size_after_pop) { - ptr_wise_atomic_memmove(self, &items[index], &items[index+1], - size_after_pop - index); + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); + _Py_ptr_wise_atomic_memmove((PyObject *)self, &items[index], &items[index+1], + size_after_pop - index); } list_resize(self, size_after_pop); // NB: shrinking a list can't fail return v; diff --git a/PCbuild/_testinternalcapi.vcxproj b/PCbuild/_testinternalcapi.vcxproj index ae873f0e0267aca..2d7dde28d136796 100644 --- a/PCbuild/_testinternalcapi.vcxproj +++ b/PCbuild/_testinternalcapi.vcxproj @@ -97,6 +97,7 @@ + From a2f5d66ff7b31d522238bd9a2ca360dbea645b04 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 9 Jun 2026 14:49:48 -0400 Subject: [PATCH 06/33] Use _Py_ptr_wise_atomic_memmove everywhere in listobject.c --- Objects/listobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index e86ae721f9acba3..d7ba1ff240ec12d 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3828,12 +3828,12 @@ list_ass_subscript_lock_held(PyObject *_self, PyObject *item, PyObject *value) lim = Py_SIZE(self) - cur - 1; } - ptr_wise_atomic_memmove(self, self->ob_item + cur - i, + _Py_ptr_wise_atomic_memmove(self, self->ob_item + cur - i, self->ob_item + cur + 1, lim); } cur = start + (size_t)slicelength * step; if (cur < (size_t)Py_SIZE(self)) { - ptr_wise_atomic_memmove(self, self->ob_item + cur - slicelength, + _Py_ptr_wise_atomic_memmove(self, self->ob_item + cur - slicelength, self->ob_item + cur, Py_SIZE(self) - cur); } From 3305b86f5c50b2c176c85dba76e7dceb1f909819 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 9 Jun 2026 14:58:19 -0400 Subject: [PATCH 07/33] Keep _testlimitedcapi/weakref.c in Setup.stdlib.in --- Modules/Setup.stdlib.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 355a9499ed7130c..db81e482405b9d7 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -173,8 +173,8 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/test_ptr_wise_memmove.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c From 73a8c67cc33a9549275351f1f6ccf306439c4cb0 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 9 Jun 2026 15:07:54 -0400 Subject: [PATCH 08/33] Cast 1st arg of all calls to _Py_ptr_wise_atomic_memmove to PyObject* --- Objects/listobject.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index d7ba1ff240ec12d..c0a847ccfdf5004 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3828,12 +3828,14 @@ list_ass_subscript_lock_held(PyObject *_self, PyObject *item, PyObject *value) lim = Py_SIZE(self) - cur - 1; } - _Py_ptr_wise_atomic_memmove(self, self->ob_item + cur - i, + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); + _Py_ptr_wise_atomic_memmove((PyObject *)self, self->ob_item + cur - i, self->ob_item + cur + 1, lim); } cur = start + (size_t)slicelength * step; if (cur < (size_t)Py_SIZE(self)) { - _Py_ptr_wise_atomic_memmove(self, self->ob_item + cur - slicelength, + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); + _Py_ptr_wise_atomic_memmove((PyObject *)self, self->ob_item + cur - slicelength, self->ob_item + cur, Py_SIZE(self) - cur); } From 0b804bbae1fa4d563904c34e6f797cf5a8af36bd Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 9 Jun 2026 15:49:05 -0400 Subject: [PATCH 09/33] Make test shut up check-c-globals.py --- .../_testinternalcapi/test_ptr_wise_memmove.c | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/Modules/_testinternalcapi/test_ptr_wise_memmove.c b/Modules/_testinternalcapi/test_ptr_wise_memmove.c index f5b5b97223d2b12..9d548af1a9ee2b4 100644 --- a/Modules/_testinternalcapi/test_ptr_wise_memmove.c +++ b/Modules/_testinternalcapi/test_ptr_wise_memmove.c @@ -14,18 +14,9 @@ // Five distinguishable immortal singletons used as placeholder pointers. // These require no reference-count management when stored in a raw array. #define NPTRS 5 -static PyObject *test_objs[NPTRS]; - -static void -setup_test_objs(void) -{ - test_objs[0] = Py_None; - test_objs[1] = Py_True; - test_objs[2] = Py_False; - test_objs[3] = Py_Ellipsis; - test_objs[4] = Py_NotImplemented; -} - +const static PyObject *test_objs[NPTRS] = { + Py_None, Py_True, Py_False, Py_Ellipsis, Py_NotImplemented +}; // Fill buf[0..NPTRS-1] with test_objs in order. static void fill_buf(PyObject **buf) @@ -79,7 +70,6 @@ call_memmove(PyObject **dest, PyObject **src, Py_ssize_t n) static PyObject * test_memmove_dest_lt_src(PyObject *self, PyObject *Py_UNUSED(arg)) { - setup_test_objs(); PyObject *buf[NPTRS]; fill_buf(buf); @@ -105,7 +95,6 @@ test_memmove_dest_lt_src(PyObject *self, PyObject *Py_UNUSED(arg)) static PyObject * test_memmove_dest_gt_src(PyObject *self, PyObject *Py_UNUSED(arg)) { - setup_test_objs(); PyObject *buf[NPTRS]; fill_buf(buf); @@ -131,7 +120,6 @@ test_memmove_dest_gt_src(PyObject *self, PyObject *Py_UNUSED(arg)) static PyObject * test_memmove_dest_eq_src(PyObject *self, PyObject *Py_UNUSED(arg)) { - setup_test_objs(); PyObject *buf[NPTRS]; fill_buf(buf); @@ -156,7 +144,6 @@ test_memmove_dest_eq_src(PyObject *self, PyObject *Py_UNUSED(arg)) static PyObject * test_memmove_overlapping(PyObject *self, PyObject *Py_UNUSED(arg)) { - setup_test_objs(); PyObject *buf[NPTRS]; fill_buf(buf); @@ -183,7 +170,6 @@ test_memmove_overlapping(PyObject *self, PyObject *Py_UNUSED(arg)) static PyObject * test_memmove_single_owner(PyObject *self, PyObject *Py_UNUSED(arg)) { - setup_test_objs(); PyObject *a = PyList_New(0); if (a == NULL) { From d3fc80b86826cab73a237ad3f90ddfb801fbe4b2 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 9 Jun 2026 16:02:38 -0400 Subject: [PATCH 10/33] Drop const in test_objs --- Modules/_testinternalcapi/test_ptr_wise_memmove.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testinternalcapi/test_ptr_wise_memmove.c b/Modules/_testinternalcapi/test_ptr_wise_memmove.c index 9d548af1a9ee2b4..480a5d19363e9ef 100644 --- a/Modules/_testinternalcapi/test_ptr_wise_memmove.c +++ b/Modules/_testinternalcapi/test_ptr_wise_memmove.c @@ -14,7 +14,7 @@ // Five distinguishable immortal singletons used as placeholder pointers. // These require no reference-count management when stored in a raw array. #define NPTRS 5 -const static PyObject *test_objs[NPTRS] = { +static PyObject *test_objs[NPTRS] = { Py_None, Py_True, Py_False, Py_Ellipsis, Py_NotImplemented }; // Fill buf[0..NPTRS-1] with test_objs in order. From e2d17d263d0da1f94ba69ef98b1ec78dee505212 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 9 Jun 2026 16:13:45 -0400 Subject: [PATCH 11/33] Revert previous changes --- .../_testinternalcapi/test_ptr_wise_memmove.c | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Modules/_testinternalcapi/test_ptr_wise_memmove.c b/Modules/_testinternalcapi/test_ptr_wise_memmove.c index 480a5d19363e9ef..f5b5b97223d2b12 100644 --- a/Modules/_testinternalcapi/test_ptr_wise_memmove.c +++ b/Modules/_testinternalcapi/test_ptr_wise_memmove.c @@ -14,9 +14,18 @@ // Five distinguishable immortal singletons used as placeholder pointers. // These require no reference-count management when stored in a raw array. #define NPTRS 5 -static PyObject *test_objs[NPTRS] = { - Py_None, Py_True, Py_False, Py_Ellipsis, Py_NotImplemented -}; +static PyObject *test_objs[NPTRS]; + +static void +setup_test_objs(void) +{ + test_objs[0] = Py_None; + test_objs[1] = Py_True; + test_objs[2] = Py_False; + test_objs[3] = Py_Ellipsis; + test_objs[4] = Py_NotImplemented; +} + // Fill buf[0..NPTRS-1] with test_objs in order. static void fill_buf(PyObject **buf) @@ -70,6 +79,7 @@ call_memmove(PyObject **dest, PyObject **src, Py_ssize_t n) static PyObject * test_memmove_dest_lt_src(PyObject *self, PyObject *Py_UNUSED(arg)) { + setup_test_objs(); PyObject *buf[NPTRS]; fill_buf(buf); @@ -95,6 +105,7 @@ test_memmove_dest_lt_src(PyObject *self, PyObject *Py_UNUSED(arg)) static PyObject * test_memmove_dest_gt_src(PyObject *self, PyObject *Py_UNUSED(arg)) { + setup_test_objs(); PyObject *buf[NPTRS]; fill_buf(buf); @@ -120,6 +131,7 @@ test_memmove_dest_gt_src(PyObject *self, PyObject *Py_UNUSED(arg)) static PyObject * test_memmove_dest_eq_src(PyObject *self, PyObject *Py_UNUSED(arg)) { + setup_test_objs(); PyObject *buf[NPTRS]; fill_buf(buf); @@ -144,6 +156,7 @@ test_memmove_dest_eq_src(PyObject *self, PyObject *Py_UNUSED(arg)) static PyObject * test_memmove_overlapping(PyObject *self, PyObject *Py_UNUSED(arg)) { + setup_test_objs(); PyObject *buf[NPTRS]; fill_buf(buf); @@ -170,6 +183,7 @@ test_memmove_overlapping(PyObject *self, PyObject *Py_UNUSED(arg)) static PyObject * test_memmove_single_owner(PyObject *self, PyObject *Py_UNUSED(arg)) { + setup_test_objs(); PyObject *a = PyList_New(0); if (a == NULL) { From 50d71cfb7b523aa38fc6e111665056a8541e8131 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:01:16 +0000 Subject: [PATCH 12/33] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst new file mode 100644 index 000000000000000..17f62e95c43758d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst @@ -0,0 +1 @@ +Promote ``ptr_wise_atomic_memmove`` to internal header From ab0750e90b8c994cb37d50614a7f56772092cf7b Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Wed, 10 Jun 2026 12:39:50 -0400 Subject: [PATCH 13/33] Delete Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst --- .../2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst deleted file mode 100644 index 17f62e95c43758d..000000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-10-14-01-11.gh-issue-149965.edk2ut.rst +++ /dev/null @@ -1 +0,0 @@ -Promote ``ptr_wise_atomic_memmove`` to internal header From 44c5f6cc0c33a92eeaa9d7a5f9de6792fb88d45a Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Wed, 10 Jun 2026 16:21:49 -0400 Subject: [PATCH 14/33] Drop unit tests for now --- Modules/Setup.stdlib.in | 6 +- Modules/_testinternalcapi.c | 3 - Modules/_testinternalcapi/parts.h | 1 - .../_testinternalcapi/test_ptr_wise_memmove.c | 231 ------------------ PCbuild/_testinternalcapi.vcxproj | 1 - 5 files changed, 3 insertions(+), 239 deletions(-) delete mode 100644 Modules/_testinternalcapi/test_ptr_wise_memmove.c diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index db81e482405b9d7..c3dd47a5e40a675 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -172,9 +172,9 @@ @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c -@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/test_ptr_wise_memmove.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c +@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 7f90a6440a57350..f629b8e528faca7 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -3482,9 +3482,6 @@ module_exec(PyObject *module) if (_PyTestInternalCapi_Init_PyTime(module) < 0) { return 1; } - if (_PyTestInternalCapi_Init_PtrWiseMemmove(module) < 0) { - return 1; - } if (_PyTestInternalCapi_Init_Set(module) < 0) { return 1; } diff --git a/Modules/_testinternalcapi/parts.h b/Modules/_testinternalcapi/parts.h index 60a41e2c49b7108..9b06eec3bce9e65 100644 --- a/Modules/_testinternalcapi/parts.h +++ b/Modules/_testinternalcapi/parts.h @@ -12,7 +12,6 @@ int _PyTestInternalCapi_Init_Lock(PyObject *module); int _PyTestInternalCapi_Init_PyTime(PyObject *module); -int _PyTestInternalCapi_Init_PtrWiseMemmove(PyObject *module); int _PyTestInternalCapi_Init_Set(PyObject *module); int _PyTestInternalCapi_Init_Complex(PyObject *module); int _PyTestInternalCapi_Init_CriticalSection(PyObject *module); diff --git a/Modules/_testinternalcapi/test_ptr_wise_memmove.c b/Modules/_testinternalcapi/test_ptr_wise_memmove.c deleted file mode 100644 index f5b5b97223d2b12..000000000000000 --- a/Modules/_testinternalcapi/test_ptr_wise_memmove.c +++ /dev/null @@ -1,231 +0,0 @@ -<<<<<<< HEAD -// Tests for _PyObject_ptr_wise_atomic_memmove() in pycore_object.h - -#include "parts.h" -#include "pycore_object.h" // _PyObject_ptr_wise_atomic_memmove() -======= -// Tests for _Py_ptr_wise_atomic_memmove() in pycore_object.h - -#include "parts.h" -#include "pycore_object.h" // _Py_ptr_wise_atomic_memmove() ->>>>>>> a4c9efbacc (gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests) -#include "pycore_gc.h" // _PyObject_GC_SET_SHARED() - -// Five distinguishable immortal singletons used as placeholder pointers. -// These require no reference-count management when stored in a raw array. -#define NPTRS 5 -static PyObject *test_objs[NPTRS]; - -static void -setup_test_objs(void) -{ - test_objs[0] = Py_None; - test_objs[1] = Py_True; - test_objs[2] = Py_False; - test_objs[3] = Py_Ellipsis; - test_objs[4] = Py_NotImplemented; -} - -// Fill buf[0..NPTRS-1] with test_objs in order. -static void -fill_buf(PyObject **buf) -{ - for (int i = 0; i < NPTRS; i++) { - buf[i] = test_objs[i]; - } -} - -// Return a fresh empty PyListObject whose GC SHARED bit is set in -<<<<<<< HEAD -// Py_GIL_DISABLED builds. This forces _PyObject_ptr_wise_atomic_memmove() -======= -// Py_GIL_DISABLED builds. This forces _Py_ptr_wise_atomic_memmove() ->>>>>>> a4c9efbacc (gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests) -// to take the atomic (non-fast) path so we can exercise all branches. -// In non-GIL builds the function always uses memmove, so no flag is needed. -static PyObject * -make_shared_container(void) -{ - PyObject *a = PyList_New(0); - if (a == NULL) { - return NULL; - } -#ifdef Py_GIL_DISABLED - _PyObject_GC_SET_SHARED(a); -#endif - return a; -} - -// Helper: create container, call the function, return it for cleanup. -// Returns NULL (with exception set) on allocation failure. -static PyObject * -call_memmove(PyObject **dest, PyObject **src, Py_ssize_t n) -{ - PyObject *a = make_shared_container(); - if (a != NULL) { -<<<<<<< HEAD - _PyObject_ptr_wise_atomic_memmove(a, dest, src, n); -======= - _Py_ptr_wise_atomic_memmove(a, dest, src, n); ->>>>>>> a4c9efbacc (gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests) - } - return a; -} - - -// dest < src: forward pointer-by-pointer copy. -// buf = [0,1,2,3,4], copy src=&buf[2] n=3 to dest=&buf[0] -// Expected result: buf = [2,3,4,3,4] -static PyObject * -test_memmove_dest_lt_src(PyObject *self, PyObject *Py_UNUSED(arg)) -{ - setup_test_objs(); - - PyObject *buf[NPTRS]; - fill_buf(buf); - - PyObject *a = call_memmove(&buf[0], &buf[2], 3); - if (a == NULL) { - return NULL; - } - - assert(buf[0] == test_objs[2]); - assert(buf[1] == test_objs[3]); - assert(buf[2] == test_objs[4]); - assert(buf[3] == test_objs[3]); // unchanged - assert(buf[4] == test_objs[4]); // unchanged - - Py_DECREF(a); - Py_RETURN_NONE; -} - -// dest > src: backward pointer-by-pointer copy. -// buf = [0,1,2,3,4], copy src=&buf[0] n=3 to dest=&buf[2] -// Expected result: buf = [0,1,0,1,2] -static PyObject * -test_memmove_dest_gt_src(PyObject *self, PyObject *Py_UNUSED(arg)) -{ - setup_test_objs(); - - PyObject *buf[NPTRS]; - fill_buf(buf); - - PyObject *a = call_memmove(&buf[2], &buf[0], 3); - if (a == NULL) { - return NULL; - } - - assert(buf[0] == test_objs[0]); // unchanged - assert(buf[1] == test_objs[1]); // unchanged - assert(buf[2] == test_objs[0]); - assert(buf[3] == test_objs[1]); - assert(buf[4] == test_objs[2]); - - Py_DECREF(a); - Py_RETURN_NONE; -} - -// dest == src: backward copy where every write is a no-op. -// buf = [0,1,2,3,4], copy src=&buf[1] n=3 to dest=&buf[1] -// Expected result: buf unchanged. -static PyObject * -test_memmove_dest_eq_src(PyObject *self, PyObject *Py_UNUSED(arg)) -{ - setup_test_objs(); - - PyObject *buf[NPTRS]; - fill_buf(buf); - - PyObject *a = call_memmove(&buf[1], &buf[1], 3); - if (a == NULL) { - return NULL; - } - - for (int i = 0; i < NPTRS; i++) { - assert(buf[i] == test_objs[i]); - } - - Py_DECREF(a); - Py_RETURN_NONE; -} - -// Overlapping ranges, dest < src: forward copy is safe. -// buf = [0,1,2,3,4], copy src=&buf[2] n=3 to dest=&buf[1] -// Forward: buf[1]=buf[2]=2, buf[2]=buf[3]=3, buf[3]=buf[4]=4 -// Expected result: buf = [0,2,3,4,4] -static PyObject * -test_memmove_overlapping(PyObject *self, PyObject *Py_UNUSED(arg)) -{ - setup_test_objs(); - - PyObject *buf[NPTRS]; - fill_buf(buf); - - PyObject *a = call_memmove(&buf[1], &buf[2], 3); - if (a == NULL) { - return NULL; - } - - assert(buf[0] == test_objs[0]); // unchanged - assert(buf[1] == test_objs[2]); - assert(buf[2] == test_objs[3]); - assert(buf[3] == test_objs[4]); - assert(buf[4] == test_objs[4]); // unchanged - - Py_DECREF(a); - Py_RETURN_NONE; -} - -// Single owner fast path (GIL_DISABLED only): object owned by this thread -// and not GC-shared => memmove is used instead of atomic stores. -// In non-GIL builds this always takes the memmove path, so the test -// degenerates to a basic correctness check. -static PyObject * -test_memmove_single_owner(PyObject *self, PyObject *Py_UNUSED(arg)) -{ - setup_test_objs(); - - PyObject *a = PyList_New(0); - if (a == NULL) { - return NULL; - } - -#ifdef Py_GIL_DISABLED - // A freelist-reused list may carry the SHARED bit set by a sibling test. - // Clear it explicitly so this test exercises the single-owner fast path. - _PyObject_CLEAR_GC_BITS(a, _PyGC_BITS_SHARED); - assert(_Py_IsOwnedByCurrentThread(a) && !_PyObject_GC_IS_SHARED(a)); -#endif - - PyObject *buf[NPTRS]; - fill_buf(buf); - -<<<<<<< HEAD - _PyObject_ptr_wise_atomic_memmove(a, &buf[0], &buf[2], 3); -======= - _Py_ptr_wise_atomic_memmove(a, &buf[0], &buf[2], 3); ->>>>>>> a4c9efbacc (gh-149966: deduplicate ptr_wise_atomic_memmove into a shared internal helper and add unit tests) - - assert(buf[0] == test_objs[2]); - assert(buf[1] == test_objs[3]); - assert(buf[2] == test_objs[4]); - - Py_DECREF(a); - Py_RETURN_NONE; -} - - -static PyMethodDef test_methods[] = { - {"test_memmove_dest_lt_src", test_memmove_dest_lt_src, METH_NOARGS}, - {"test_memmove_dest_gt_src", test_memmove_dest_gt_src, METH_NOARGS}, - {"test_memmove_dest_eq_src", test_memmove_dest_eq_src, METH_NOARGS}, - {"test_memmove_overlapping", test_memmove_overlapping, METH_NOARGS}, - {"test_memmove_single_owner", test_memmove_single_owner, METH_NOARGS}, - {NULL}, -}; - -int -_PyTestInternalCapi_Init_PtrWiseMemmove(PyObject *mod) -{ - return PyModule_AddFunctions(mod, test_methods); -} diff --git a/PCbuild/_testinternalcapi.vcxproj b/PCbuild/_testinternalcapi.vcxproj index 2d7dde28d136796..ae873f0e0267aca 100644 --- a/PCbuild/_testinternalcapi.vcxproj +++ b/PCbuild/_testinternalcapi.vcxproj @@ -97,7 +97,6 @@ - From c4db78564b94ea8da8f03f2b9b0e0f9678f02696 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Wed, 10 Jun 2026 16:29:03 -0400 Subject: [PATCH 15/33] Don't exclude weakref for testing --- Modules/Setup.stdlib.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index c3dd47a5e40a675..db81e482405b9d7 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -172,9 +172,9 @@ @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c -@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c +@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/test_ptr_wise_memmove.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c From cf79ca0f1011e9fdc06fca28af1b8077cc2f639b Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Wed, 10 Jun 2026 16:38:57 -0400 Subject: [PATCH 16/33] Actually drop unit test --- Modules/Setup.stdlib.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index db81e482405b9d7..8efea27824f0e88 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -172,7 +172,7 @@ @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c -@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/test_ptr_wise_memmove.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c +@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c From 98a544bc2327fc07d75f46a6450f460859492930 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Thu, 11 Jun 2026 16:28:13 -0400 Subject: [PATCH 17/33] Rename to _PyObject_ptr_wise_atomic_memmove --- Include/internal/pycore_object.h | 4 +++- Modules/_elementtree.c | 10 +++++----- Objects/listobject.c | 14 +++++++------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 16ed31cb7a529d7..984b95c4bdd5983 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -1020,7 +1020,7 @@ PyAPI_FUNC(int) _PyObject_VisitType(PyObject *op, visitproc visit, void *arg); // Pointer-by-pointer memmove for PyObject** arrays that is safe for shared // objects in Py_GIL_DISABLED builds. Locking is the caller's responsibility. static inline void -_Py_ptr_wise_atomic_memmove(PyObject *a, PyObject **dest, PyObject **src, +_PyObject_ptr_wise_atomic_memmove(PyObject *a, PyObject **dest, PyObject **src, Py_ssize_t n) { #ifndef Py_GIL_DISABLED @@ -1046,6 +1046,8 @@ _Py_ptr_wise_atomic_memmove(PyObject *a, PyObject **dest, PyObject **src, #endif } +#define _PyObject_ptr_wise_atomic_memmove(a, dest, src, n) _PyObject_ptr_wise_atomic_memmove(_PyObject_CAST(a), dest, src, n) + #ifdef __cplusplus } #endif diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 680265764dc3d82..13299a1cf0751ec 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -18,7 +18,7 @@ #include "Python.h" #include "pycore_ceval.h" // _Py_EnterRecursiveCall() #include "pycore_dict.h" // _PyDict_CopyAsDict() -#include "pycore_object.h" // _Py_ptr_wise_atomic_memmove() +#include "pycore_object.h" // _PyObject_ptr_wise_atomic_memmove() #include "pycore_pyhash.h" // _Py_HashSecret #include "pycore_tuple.h" // _PyTuple_FromPair #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() @@ -2023,8 +2023,8 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) PyList_SET_ITEM(recycle, i, self->extra->children[cur]); - _Py_ptr_wise_atomic_memmove( - (PyObject *)self, + _PyObject_ptr_wise_atomic_memmove( + self, self->extra->children + cur - i, self->extra->children + cur + 1, num_moved); @@ -2033,8 +2033,8 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) /* Leftover "tail" after the last removed child */ cur = start + (size_t)slicelen * step; if (cur < (size_t)self->extra->length) { - _Py_ptr_wise_atomic_memmove( - (PyObject *)self, + _PyObject_ptr_wise_atomic_memmove( + self, self->extra->children + cur - slicelen, self->extra->children + cur, self->extra->length - cur); diff --git a/Objects/listobject.c b/Objects/listobject.c index c0a847ccfdf5004..03fedc491f33a19 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -10,7 +10,7 @@ #include "pycore_list.h" // struct _Py_list_freelist, _PyListIterObject #include "pycore_long.h" // _PyLong_DigitCount #include "pycore_modsupport.h" // _PyArg_NoKwnames() -#include "pycore_object.h" // _PyObject_GC_TRACK(), _Py_ptr_wise_atomic_memmove() +#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyObject_ptr_wise_atomic_memmove() #include "pycore_pyatomic_ft_wrappers.h" #include "pycore_setobject.h" // _PySet_NextEntry() #include "pycore_stackref.h" // _Py_TryIncrefCompareStackRef() @@ -1040,7 +1040,7 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO if (d < 0) { /* Delete -d items */ Py_ssize_t tail = Py_SIZE(a) - ihigh; _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); - _Py_ptr_wise_atomic_memmove((PyObject *)a, &item[ihigh+d], &item[ihigh], tail); + _PyObject_ptr_wise_atomic_memmove(a, &item[ihigh+d], &item[ihigh], tail); (void)list_resize(a, Py_SIZE(a) + d); // NB: shrinking a list can't fail item = a->ob_item; } @@ -1050,7 +1050,7 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO goto Error; item = a->ob_item; _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); - _Py_ptr_wise_atomic_memmove((PyObject *)a, &item[ihigh+d], &item[ihigh], k - ihigh); + _PyObject_ptr_wise_atomic_memmove(a, &item[ihigh+d], &item[ihigh], k - ihigh); } for (k = 0; k < n; k++, ilow++) { PyObject *w = vitem[k]; @@ -1142,7 +1142,7 @@ list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n) Py_ssize_t copied = input_size; while (copied < output_size) { Py_ssize_t items_to_copy = Py_MIN(copied, output_size - copied); - _Py_ptr_wise_atomic_memmove((PyObject *)self, items + copied, items, items_to_copy); + _PyObject_ptr_wise_atomic_memmove(self, items + copied, items, items_to_copy); copied += items_to_copy; } #endif @@ -1641,7 +1641,7 @@ list_pop_impl(PyListObject *self, Py_ssize_t index) Py_ssize_t size_after_pop = Py_SIZE(self) - 1; if (index < size_after_pop) { _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); - _Py_ptr_wise_atomic_memmove((PyObject *)self, &items[index], &items[index+1], + _PyObject_ptr_wise_atomic_memmove(self, &items[index], &items[index+1], size_after_pop - index); } list_resize(self, size_after_pop); // NB: shrinking a list can't fail @@ -3829,13 +3829,13 @@ list_ass_subscript_lock_held(PyObject *_self, PyObject *item, PyObject *value) } _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); - _Py_ptr_wise_atomic_memmove((PyObject *)self, self->ob_item + cur - i, + _PyObject_ptr_wise_atomic_memmove(self, self->ob_item + cur - i, self->ob_item + cur + 1, lim); } cur = start + (size_t)slicelength * step; if (cur < (size_t)Py_SIZE(self)) { _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); - _Py_ptr_wise_atomic_memmove((PyObject *)self, self->ob_item + cur - slicelength, + _PyObject_ptr_wise_atomic_memmove(self, self->ob_item + cur - slicelength, self->ob_item + cur, Py_SIZE(self) - cur); } From d079b0df3eb5ca2bb964783683d11997ec8a99d9 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 19:38:32 +0000 Subject: [PATCH 18/33] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst new file mode 100644 index 000000000000000..2e0745c63688397 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst @@ -0,0 +1 @@ +Use :c:func:``_PyObject_ptr_wise_atomic_memmove`` in ``_elementtree`` From 5944e34d5a346f884c685c3704d1081fc57f7880 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Sun, 11 Oct 2026 08:05:33 -0400 Subject: [PATCH 19/33] Backtick --- .../2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst index 2e0745c63688397..45a1456984372da 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst @@ -1 +1 @@ -Use :c:func:``_PyObject_ptr_wise_atomic_memmove`` in ``_elementtree`` +Use :c:func:`_PyObject_ptr_wise_atomic_memmove` in ``_elementtree`` From f526c6033f13da447fd3d9aa939a3988e78e86cd Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Sun, 11 Oct 2026 08:10:28 -0400 Subject: [PATCH 20/33] Rebase mishaps --- Modules/Setup.stdlib.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 8efea27824f0e88..50db569eba2e5dd 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -172,9 +172,9 @@ @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c -@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c +@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c _testcapi/fpcast.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c _testlimitedcapi/build.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c From 0daec9c0564264c59ac7d737244548a57dbb8b32 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Sun, 11 Oct 2026 08:23:38 -0400 Subject: [PATCH 21/33] Backtick --- .../2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst index 45a1456984372da..fe6a6a01705f84a 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst @@ -1 +1 @@ -Use :c:func:`_PyObject_ptr_wise_atomic_memmove` in ``_elementtree`` +Use ``_PyObject_ptr_wise_atomic_memmove`` in ``_elementtree`` From 1535db6c545d9dd7355161bd901ee435faf585be Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Sun, 11 Oct 2026 08:30:20 -0400 Subject: [PATCH 22/33] Finally use func declaration in internal header --- Objects/listobject.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 03fedc491f33a19..9dc1d98e940d56c 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -499,15 +499,13 @@ end:; return ret; } -static void ptr_wise_atomic_memmove(PyListObject *a, PyObject **dest, - PyObject **src, Py_ssize_t n); - static inline void list_shift_items_right_lock_held(PyListObject *self, Py_ssize_t first, Py_ssize_t last) { #ifdef Py_GIL_DISABLED - ptr_wise_atomic_memmove(self, &self->ob_item[first + 1], + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); + _PyObject_ptr_wise_atomic_memmove(self, &self->ob_item[first + 1], &self->ob_item[first], last - first); #else PyObject **items = self->ob_item; @@ -522,7 +520,8 @@ list_shift_items_left_lock_held(PyListObject *self, Py_ssize_t first, Py_ssize_t last) { #ifdef Py_GIL_DISABLED - ptr_wise_atomic_memmove(self, &self->ob_item[first], + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); + _PyObject_ptr_wise_atomic_memmove(self, &self->ob_item[first], &self->ob_item[first + 1], last - first); #else PyObject **items = self->ob_item; From 31298e01305dd5b3a6860c78992dad1656a5e3a6 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 08:39:24 -0400 Subject: [PATCH 23/33] Update 2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst --- .../2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst index fe6a6a01705f84a..416c7451b19810a 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-12-19-38-28.gh-issue-149965.w5eiS4.rst @@ -1 +1 @@ -Use ``_PyObject_ptr_wise_atomic_memmove`` in ``_elementtree`` +Fix thread-safety issues in ``xml.etree`` From 4d1ca96b78e3d4684d5050d173a71f1e3a395454 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 08:40:16 -0400 Subject: [PATCH 24/33] Update comments and formatting in Setup.stdlib.in --- Modules/Setup.stdlib.in | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 50db569eba2e5dd..7730aa85159dc37 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -5,20 +5,20 @@ # The file is not used by default yet. For testing do: # -# ln -sfr Modules/Setup.stdlib Modules/Setup.local +# ln -sfr Modules/Setup.stdlib Modules/Setup.local # # * @MODULE_{NAME}_TRUE@ is removed when configure detects all build -# dependencies for a module. Otherwise the template variable is replaced -# by a comment "#" and the module is skipped. +# dependencies for a module. Otherwise the template variable is replaced +# by a comment "#" and the module is skipped. # * Module lines without any compiler and linker flags automatically use -# $(MODULE_{NAME}_CFLAGS) and $(MODULE_{NAME}_LDFLAGS). These flags are -# generated by PY_STDLIB_MOD macro. +# $(MODULE_{NAME}_CFLAGS) and $(MODULE_{NAME}_LDFLAGS). These flags are +# generated by PY_STDLIB_MOD macro. # * All source files automatically depend on $(PYTHON_HEADERS) and -# $(MODULE_{NAME}_DEPS). +# $(MODULE_{NAME}_DEPS). # * ${NAME}$(EXT_SUFFIX) rules automatically depend on $(MODULE_{NAME}_LDEPS), -# thus $(MODULE_{NAME}_LDEPS) must only contain Makefile rule names. To pass -# specific values to the linker, use $(MODULE_{NAME}_LDFLAGS) instead. +# thus $(MODULE_{NAME}_LDEPS) must only contain Makefile rule names. To pass +# specific values to the linker, use $(MODULE_{NAME}_LDFLAGS) instead. # # See Modules/Setup and Modules/makesetup # @@ -174,7 +174,7 @@ @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c _testcapi/fpcast.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c _testlimitedcapi/build.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c _testlimitedcapi/build.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c From 85454966b57c3063fc0c9a142e6ae4c2e692ea7f Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 08:41:10 -0400 Subject: [PATCH 25/33] Comment out lines in Setup.stdlib.in for clarity --- Modules/Setup.stdlib.in | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 7730aa85159dc37..0a7691abeb97b8b 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -5,20 +5,20 @@ # The file is not used by default yet. For testing do: # -# ln -sfr Modules/Setup.stdlib Modules/Setup.local +# ln -sfr Modules/Setup.stdlib Modules/Setup.local # # * @MODULE_{NAME}_TRUE@ is removed when configure detects all build -# dependencies for a module. Otherwise the template variable is replaced -# by a comment "#" and the module is skipped. +# dependencies for a module. Otherwise the template variable is replaced +# by a comment "#" and the module is skipped. # * Module lines without any compiler and linker flags automatically use -# $(MODULE_{NAME}_CFLAGS) and $(MODULE_{NAME}_LDFLAGS). These flags are -# generated by PY_STDLIB_MOD macro. +# $(MODULE_{NAME}_CFLAGS) and $(MODULE_{NAME}_LDFLAGS). These flags are +# generated by PY_STDLIB_MOD macro. # * All source files automatically depend on $(PYTHON_HEADERS) and -# $(MODULE_{NAME}_DEPS). +# $(MODULE_{NAME}_DEPS). # * ${NAME}$(EXT_SUFFIX) rules automatically depend on $(MODULE_{NAME}_LDEPS), -# thus $(MODULE_{NAME}_LDEPS) must only contain Makefile rule names. To pass -# specific values to the linker, use $(MODULE_{NAME}_LDFLAGS) instead. +# thus $(MODULE_{NAME}_LDEPS) must only contain Makefile rule names. To pass +# specific values to the linker, use $(MODULE_{NAME}_LDFLAGS) instead. # # See Modules/Setup and Modules/makesetup # From a2f13f188603e76077c8e27fb91e1d956d57364f Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 08:41:32 -0400 Subject: [PATCH 26/33] Fix comment formatting in Setup.stdlib.in --- Modules/Setup.stdlib.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 0a7691abeb97b8b..d8cd8de10dfe084 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -5,7 +5,7 @@ # The file is not used by default yet. For testing do: # -# ln -sfr Modules/Setup.stdlib Modules/Setup.local +# ln -sfr Modules/Setup.stdlib Modules/Setup.local # # * @MODULE_{NAME}_TRUE@ is removed when configure detects all build From 1e8267c7fb0b1427e5e30a4d2640a5213d41e8ed Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 08:43:47 -0400 Subject: [PATCH 27/33] Update _elementtree.c --- Modules/_elementtree.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 13299a1cf0751ec..d2cd040ce1e30ba 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -1947,7 +1947,6 @@ element_subscr(PyObject *op, PyObject *item) } } - static int element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) { From bdb231c576a1b97b63491429a3320faa1dbeb3ad Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 08:47:13 -0400 Subject: [PATCH 28/33] Remove lock assertions in listobject functions Removed assertions for object lock in list operations. --- Objects/listobject.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 9dc1d98e940d56c..d7f06a76383d7a8 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -504,7 +504,6 @@ list_shift_items_right_lock_held(PyListObject *self, Py_ssize_t first, Py_ssize_t last) { #ifdef Py_GIL_DISABLED - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); _PyObject_ptr_wise_atomic_memmove(self, &self->ob_item[first + 1], &self->ob_item[first], last - first); #else @@ -520,7 +519,6 @@ list_shift_items_left_lock_held(PyListObject *self, Py_ssize_t first, Py_ssize_t last) { #ifdef Py_GIL_DISABLED - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); _PyObject_ptr_wise_atomic_memmove(self, &self->ob_item[first], &self->ob_item[first + 1], last - first); #else @@ -1038,7 +1036,6 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO if (d < 0) { /* Delete -d items */ Py_ssize_t tail = Py_SIZE(a) - ihigh; - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); _PyObject_ptr_wise_atomic_memmove(a, &item[ihigh+d], &item[ihigh], tail); (void)list_resize(a, Py_SIZE(a) + d); // NB: shrinking a list can't fail item = a->ob_item; @@ -1048,7 +1045,6 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO if (list_resize(a, k+d) < 0) goto Error; item = a->ob_item; - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); _PyObject_ptr_wise_atomic_memmove(a, &item[ihigh+d], &item[ihigh], k - ihigh); } for (k = 0; k < n; k++, ilow++) { @@ -1137,7 +1133,6 @@ list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n) _Py_memory_repeat((char *)items, sizeof(PyObject *)*output_size, sizeof(PyObject *)*input_size); #else - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); Py_ssize_t copied = input_size; while (copied < output_size) { Py_ssize_t items_to_copy = Py_MIN(copied, output_size - copied); @@ -1639,7 +1634,6 @@ list_pop_impl(PyListObject *self, Py_ssize_t index) } Py_ssize_t size_after_pop = Py_SIZE(self) - 1; if (index < size_after_pop) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); _PyObject_ptr_wise_atomic_memmove(self, &items[index], &items[index+1], size_after_pop - index); } @@ -3827,13 +3821,11 @@ list_ass_subscript_lock_held(PyObject *_self, PyObject *item, PyObject *value) lim = Py_SIZE(self) - cur - 1; } - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); _PyObject_ptr_wise_atomic_memmove(self, self->ob_item + cur - i, self->ob_item + cur + 1, lim); } cur = start + (size_t)slicelength * step; if (cur < (size_t)Py_SIZE(self)) { - _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); _PyObject_ptr_wise_atomic_memmove(self, self->ob_item + cur - slicelength, self->ob_item + cur, Py_SIZE(self) - cur); } From 4c75be205eb4ebc1474596d114113ab51eb053c2 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 08:48:00 -0400 Subject: [PATCH 29/33] Add assertion for object lock in memory move --- Include/internal/pycore_object.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 984b95c4bdd5983..f92f0c409c52748 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -1027,6 +1027,7 @@ _PyObject_ptr_wise_atomic_memmove(PyObject *a, PyObject **dest, PyObject **src, (void)a; memmove(dest, src, n * sizeof(PyObject *)); #else + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a); if (_Py_IsOwnedByCurrentThread(a) && !_PyObject_GC_IS_SHARED(a)) { // No other threads can read this object's array concurrently memmove(dest, src, n * sizeof(PyObject *)); From 50fa059c54b57d2dd2332f13a912ec665a4977cf Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 09:37:13 -0400 Subject: [PATCH 30/33] Update pycore_object.h --- Include/internal/pycore_object.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index f92f0c409c52748..a979dba51681873 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -8,6 +8,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_critical_section.h" // _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED #include "pycore_wasm_trampoline.h" // _PyCFunction_TrampolineCall() #include "pycore_gc.h" // _PyObject_GC_TRACK() #include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_ACQUIRE() From 4f8427fc2e1e0ca9bab7f89acd3c4a78e4a3bca0 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 16:08:02 +0200 Subject: [PATCH 31/33] Unit tests for etree --- Lib/test/test_xml_etree_c.py | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Lib/test/test_xml_etree_c.py b/Lib/test/test_xml_etree_c.py index deb2bd8d572581a..58dd9cab45b1a65 100644 --- a/Lib/test/test_xml_etree_c.py +++ b/Lib/test/test_xml_etree_c.py @@ -2,6 +2,7 @@ import io import struct from test import support +from test.support import threading_helper from test.support.import_helper import import_fresh_module import types import unittest @@ -193,6 +194,48 @@ def test_disallow_instantiation(self): support.check_disallow_instantiation(self, iter_type) +@unittest.skipUnless(cET, 'requires _elementtree') +@threading_helper.requires_working_threading() +class ThreadingTests(unittest.TestCase): + def test_concurrent_slice_deletion(self): + self.check_concurrent_slice_deletion(slice(1, 65)) + + def test_concurrent_extended_slice_deletion(self): + for index in (slice(1, 65, 3), slice(64, 0, -3), slice(1, None, 3)): + with self.subTest(index=index): + self.check_concurrent_slice_deletion(index) + + def check_concurrent_slice_deletion(self, index): + # gh-149965: Read children while slice deletion shifts the child + # pointers. Create the elements here so the writer does not own them. + roots = [] + expected = [] + for _ in range(100): + root = cET.Element('root') + children = [cET.SubElement(root, str(i)) for i in range(100)] + del children[index] + roots.append(root) + expected.append(children) + + def delete(): + for root in roots: + del root[index] + + def read(): + for _ in range(10): + for root in roots: + # Each slice leaves at least two children. Their identity + # can change during deletion, but they must remain valid. + child = root[1] + self.assertIsInstance(child, cET.Element) + self.assertIn(child.tag, tags) + + tags = {str(i) for i in range(100)} + threading_helper.run_concurrently([delete, read, read]) + for root, children in zip(roots, expected): + self.assertEqual(root[:], children) + + @unittest.skipUnless(cET, 'requires _elementtree') class TestAliasWorking(unittest.TestCase): # Test that the cET alias module is alive From 1f3422fc1c0fc7cf6f6593a5381b4756497f7bf1 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 10:25:22 -0400 Subject: [PATCH 32/33] Update _elementtree.c --- Modules/_elementtree.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index d2cd040ce1e30ba..b3683d7aac9b6e0 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -1948,7 +1948,7 @@ element_subscr(PyObject *op, PyObject *item) } static int -element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) +element_ass_subscr_lock_held(PyObject *op, PyObject *item, PyObject *value) { ElementObject *self = _Element_CAST(op); @@ -2138,6 +2138,15 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) } } +static int +element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) +{ + Py_BEGIN_CRITICAL_SECTION(op); + int res = element_ass_subscr_lock_held(op, item, value); + Py_END_CRITICAL_SECTION(); + return res; +} + static PyObject* element_tag_getter(PyObject *op, void *closure) { From 3efd47c1cfc1c791f9043ff693fb7947ccb22be0 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Tue, 13 Oct 2026 10:31:55 -0400 Subject: [PATCH 33/33] Update _elementtree.c --- Modules/_elementtree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index b3683d7aac9b6e0..0f79adb46472d23 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -2141,8 +2141,9 @@ element_ass_subscr_lock_held(PyObject *op, PyObject *item, PyObject *value) static int element_ass_subscr(PyObject *op, PyObject *item, PyObject *value) { + int res = 0; Py_BEGIN_CRITICAL_SECTION(op); - int res = element_ass_subscr_lock_held(op, item, value); + res = element_ass_subscr_lock_held(op, item, value); Py_END_CRITICAL_SECTION(); return res; }