diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index eca3e76d84a1cfd..5d9a7b6314b1668 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -515,7 +515,7 @@ since it is impossible to detect the termination of alien threads. This constructor should always be called with keyword arguments. Arguments are: - *group* should be ``None``; reserved for future extension when a + *group* must be ``None`` as it is reserved for future extension when a :class:`!ThreadGroup` class is implemented. *target* is the callable object to be invoked by the :meth:`run` method. diff --git a/Include/internal/pycore_sliceobject.h b/Include/internal/pycore_sliceobject.h index ba8b1f1cb27dee3..b6c821764886c36 100644 --- a/Include/internal/pycore_sliceobject.h +++ b/Include/internal/pycore_sliceobject.h @@ -12,7 +12,7 @@ extern "C" { /* runtime lifecycle */ PyAPI_FUNC(PyObject *) -_PyBuildSlice_ConsumeRefs(PyObject *start, PyObject *stop); +_PyBuildSlice_ConsumeRefs(PyObject *start, PyObject *stop, PyObject *step); #ifdef __cplusplus } diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index fa3a94764b507ae..0d5e362188ab21d 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -145,10 +145,7 @@ def _wake_up_first(self): """Ensure that the first waiter will wake up.""" if not self._waiters: return - try: - fut = next(iter(self._waiters)) - except StopIteration: - return + fut = next(iter(self._waiters)) # .done() means that the waiter is already set to wake up. if not fut.done(): diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js index 1611bf754424c13..840acf2c27d1201 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js @@ -7,6 +7,7 @@ let invertedData = null; let currentThreadFilter = 'all'; let isInverted = false; let useModuleNames = true; +let zoomedNodeValue = null; // Heat colors are now defined in CSS variables (--heat-1 through --heat-8) // and automatically switch with theme changes - no JS color arrays needed! @@ -316,6 +317,7 @@ function createPythonTooltip(data) { const selfSamples = d.data.self || 0; const selfMs = (selfSamples / 1000).toFixed(2); const percentage = ((d.data.value / data.value) * 100).toFixed(2); + const relativePercentage = Math.min(100, ((d.data.value / (zoomedNodeValue ?? data.value)) * 100)).toFixed(2); const calls = d.data.calls || 0; const childCount = d.children ? d.children.length : 0; const source = d.data.source; @@ -439,6 +441,11 @@ function createPythonTooltip(data) { Percentage: ${percentage}% + ${relativePercentage != percentage && relativePercentage != "100.00" ? ` + Relative Percentage: + ${relativePercentage}% + ` : ''} + ${calls > 0 ? ` Function Calls: ${calls.toLocaleString()} @@ -620,6 +627,9 @@ function createFlamegraph(tooltip, rootValue, data) { const percentage = d.data.value / rootValue; const level = getHeatLevel(percentage); return heatColors[level]; + }) + .onClick(function (d) { + zoomedNodeValue = d.data.value; }); return chart; @@ -629,6 +639,7 @@ function renderFlamegraph(chart, data) { d3.select("#chart").datum(data).call(chart); window.flamegraphChart = chart; window.flamegraphData = data; + zoomedNodeValue = null; populateStats(data); } @@ -1269,6 +1280,7 @@ function filterDataByThread(data, threadId) { function resetZoom() { if (window.flamegraphChart) { + zoomedNodeValue = null; window.flamegraphChart.resetZoom(); } } diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 22ad9f6243eda91..849a2afd8ed7986 100644 --- a/Lib/test/test_type_cache.py +++ b/Lib/test/test_type_cache.py @@ -1,4 +1,5 @@ """ Tests for the internal type cache in CPython. """ +import collections.abc import dis import unittest import warnings @@ -114,6 +115,25 @@ class HolderSub(Holder): Holder.set_value() HolderSub.value + def test_abc_register_invalidates_subclass_versions(self): + class Parent: + pass + + class Child(Parent): + pass + + type_assign_version(Parent) + type_assign_version(Child) + parent_version = type_get_version(Parent) + child_version = type_get_version(Child) + if parent_version == 0 or child_version == 0: + self.skipTest("Could not assign valid type versions") + + collections.abc.Mapping.register(Parent) + + self.assertEqual(type_get_version(Parent), 0) + self.assertEqual(type_get_version(Child), 0) + @support.cpython_only class TypeCacheWithSpecializationTests(unittest.TestCase): def tearDown(self): diff --git a/Makefile.pre.in b/Makefile.pre.in index 9435bf534fb5121..765c72290e46ee4 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -3111,9 +3111,18 @@ config.status: $(srcdir)/configure .PRECIOUS: config.status $(BUILDPYTHON) Makefile Makefile.pre -Python/asm_trampoline.o: $(srcdir)/Python/asm_trampoline.S +Python/asm_trampoline_x86_64.o: $(srcdir)/Python/asm_trampoline_x86_64.S $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< +Python/asm_trampoline_aarch64.o: $(srcdir)/Python/asm_trampoline_aarch64.S + $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< + +Python/asm_trampoline_riscv64.o: $(srcdir)/Python/asm_trampoline_riscv64.S + $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< + +Python/asm_trampoline_universal2.o: Python/asm_trampoline_aarch64.o Python/asm_trampoline_x86_64.o + lipo -create -output $@ Python/asm_trampoline_aarch64.o Python/asm_trampoline_x86_64.o + Python/emscripten_trampoline_inner.wasm: $(srcdir)/Python/emscripten_trampoline_inner.c # emcc has a path that ends with emsdk/upstream/emscripten/emcc, we're looking for emsdk/upstream/bin/clang. $$(dirname $$(dirname $(CC)))/bin/clang -o $@ $< -mgc -O2 -Wl,--no-entry -Wl,--import-table -Wl,--import-memory -target wasm32-unknown-unknown -nostdlib diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst new file mode 100644 index 000000000000000..2a7d0d9bb3a7f7e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-15-15-48-04.gh-issue-148450.2MEVqH.rst @@ -0,0 +1 @@ +Fix ``abc.register()`` so it invalidates type version tags for registered classes. diff --git a/Misc/NEWS.d/next/Tools-Demos/2026-05-22-18-51-09.gh-issue-150258.dh8GVK.rst b/Misc/NEWS.d/next/Tools-Demos/2026-05-22-18-51-09.gh-issue-150258.dh8GVK.rst new file mode 100644 index 000000000000000..02cad6c4f53d928 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2026-05-22-18-51-09.gh-issue-150258.dh8GVK.rst @@ -0,0 +1 @@ +Update the tooltip on the Tachyon flame graph to show both absolute and relative percentages. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 0fdae7b2d210040..5537947f6a51c11 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1509,7 +1509,9 @@ buffered_iternext(PyObject *op) tp == state->PyBufferedRandom_Type) { /* Skip method call overhead for speed */ + Py_BEGIN_CRITICAL_SECTION(self); line = _buffered_readline(self, -1); + Py_END_CRITICAL_SECTION(); } else { line = PyObject_CallMethodNoArgs((PyObject *)self, diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index a2506524f0bb6dc..b463bb18b160564 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -12007,7 +12007,8 @@ v = stack_pointer[-4]; _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *slice = _PyBuildSlice_ConsumeRefs(PyStackRef_AsPyObjectSteal(start), - PyStackRef_AsPyObjectSteal(stop)); + PyStackRef_AsPyObjectSteal(stop), + Py_None); stack_pointer = _PyFrame_GetStackPointer(frame); int err; if (slice == NULL) { diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 95f10815687757e..96ff3118dc4405d 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -117,8 +117,8 @@ PyObject _Py_EllipsisObject = _PyObject_HEAD_INIT(&PyEllipsis_Type); index is present. */ -static PySliceObject * -_PyBuildSlice_Consume2(PyObject *start, PyObject *stop, PyObject *step) +PyObject * +_PyBuildSlice_ConsumeRefs(PyObject *start, PyObject *stop, PyObject *step) { assert(start != NULL && stop != NULL && step != NULL); PySliceObject *obj = _Py_FREELIST_POP(PySliceObject, slices); @@ -131,13 +131,14 @@ _PyBuildSlice_Consume2(PyObject *start, PyObject *stop, PyObject *step) obj->start = start; obj->stop = stop; - obj->step = Py_NewRef(step); + obj->step = step; _PyObject_GC_TRACK(obj); - return obj; + return (PyObject *)obj; error: Py_DECREF(start); Py_DECREF(stop); + Py_DECREF(step); return NULL; } @@ -153,15 +154,8 @@ PySlice_New(PyObject *start, PyObject *stop, PyObject *step) if (stop == NULL) { stop = Py_None; } - return (PyObject *)_PyBuildSlice_Consume2(Py_NewRef(start), - Py_NewRef(stop), step); -} - -PyObject * -_PyBuildSlice_ConsumeRefs(PyObject *start, PyObject *stop) -{ - assert(start != NULL && stop != NULL); - return (PyObject *)_PyBuildSlice_Consume2(start, stop, Py_None); + return _PyBuildSlice_ConsumeRefs(Py_NewRef(start), + Py_NewRef(stop), Py_NewRef(step)); } PyObject * @@ -177,9 +171,7 @@ _PySlice_FromIndices(Py_ssize_t istart, Py_ssize_t istop) return NULL; } - slice = PySlice_New(start, end, NULL); - Py_DECREF(start); - Py_DECREF(end); + slice = _PyBuildSlice_ConsumeRefs(start, end, Py_None); return slice; } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 7cca137f74be58f..fc679ef747e8567 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6490,9 +6490,25 @@ set_flags_recursive(PyTypeObject *self, unsigned long mask, unsigned long flags) void _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long flags) { + BEGIN_TYPE_LOCK(); + /* Ideally, changing flags and invalidating the old version tag would + happen in one step. But type_modified_unlocked() is re-entrant and + cannot run with the world stopped, so we must invalidate first. + Immutable/static-builtin types are skipped because + set_flags_recursive() does not modify them. */ + if (!PyType_HasFeature(self, Py_TPFLAGS_IMMUTABLETYPE) && + (self->tp_flags & mask) != flags) + { + type_modified_unlocked(self); + } + /* Keep TYPE_LOCK held while waiting for stop-the-world so no thread + can reassign a version tag before the flag update. */ + type_lock_prevent_release(); types_stop_world(); set_flags_recursive(self, mask, flags); types_start_world(); + type_lock_allow_release(); + END_TYPE_LOCK(); } /* This is similar to PyObject_GenericGetAttr(), diff --git a/Python/asm_trampoline_aarch64.h b/Python/asm_trampoline_aarch64.S similarity index 76% rename from Python/asm_trampoline_aarch64.h rename to Python/asm_trampoline_aarch64.S index bc83aa460b6860d..b3aeb728de200c6 100644 --- a/Python/asm_trampoline_aarch64.h +++ b/Python/asm_trampoline_aarch64.S @@ -1,6 +1,3 @@ -#ifndef ASM_TRAMPOLINE_AARCH_64_H_ -#define ASM_TRAMPOLINE_AARCH_64_H_ - /* * References: * - https://developer.arm.com/documentation/101028/0012/5--Feature-test-macros @@ -38,6 +35,31 @@ #define GNU_PROPERTY_AARCH64_GCS 0 #endif + .text +#if defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) +#if defined(__APPLE__) + .globl __Py_trampoline_func_start +__Py_trampoline_func_start: +#else + .globl _Py_trampoline_func_start +_Py_trampoline_func_start: +#endif + SIGN_LR + stp x29, x30, [sp, -16]! + mov x29, sp + blr x3 + ldp x29, x30, [sp], 16 + VERIFY_LR + ret +#if defined(__APPLE__) + .globl __Py_trampoline_func_end +__Py_trampoline_func_end: +#else + .globl _Py_trampoline_func_end +_Py_trampoline_func_end: + .section .note.GNU-stack,"",@progbits +#endif + /* Add the BTI, PAC and GCS support to GNU Notes section */ #if GNU_PROPERTY_AARCH64_BTI != 0 || GNU_PROPERTY_AARCH64_POINTER_AUTH != 0 || GNU_PROPERTY_AARCH64_GCS != 0 .pushsection .note.gnu.property, "a"; /* Start a new allocatable section */ @@ -52,5 +74,4 @@ .long 0; /* padding for 8 byte alignment */ .popsection; /* end the section */ #endif - -#endif +#endif // __aarch64__ && __AARCH64EL__ && !__ILP32__ diff --git a/Python/asm_trampoline_riscv64.S b/Python/asm_trampoline_riscv64.S new file mode 100644 index 000000000000000..6125ba95373f954 --- /dev/null +++ b/Python/asm_trampoline_riscv64.S @@ -0,0 +1,12 @@ + .text + .globl _Py_trampoline_func_start +_Py_trampoline_func_start: + addi sp,sp,-16 + sd ra,8(sp) + jalr a3 + ld ra,8(sp) + addi sp,sp,16 + jr ra + .globl _Py_trampoline_func_end +_Py_trampoline_func_end: + .section .note.GNU-stack,"",@progbits diff --git a/Python/asm_trampoline.S b/Python/asm_trampoline_x86_64.S similarity index 51% rename from Python/asm_trampoline.S rename to Python/asm_trampoline_x86_64.S index 9f3ca909ab7d852..0e6b11589eafc82 100644 --- a/Python/asm_trampoline.S +++ b/Python/asm_trampoline_x86_64.S @@ -1,24 +1,12 @@ -#include "asm_trampoline_aarch64.h" - .text +#ifdef __x86_64__ #if defined(__APPLE__) .globl __Py_trampoline_func_start -#else - .globl _Py_trampoline_func_start -#endif -# The following assembly is equivalent to: -# PyObject * -# trampoline(PyThreadState *ts, _PyInterpreterFrame *f, -# int throwflag, py_evaluator evaluator) -# { -# return evaluator(ts, f, throwflag); -# } -#if defined(__APPLE__) __Py_trampoline_func_start: #else + .globl _Py_trampoline_func_start _Py_trampoline_func_start: #endif -#ifdef __x86_64__ #if defined(__CET__) && (__CET__ & 1) endbr64 #endif @@ -27,26 +15,6 @@ _Py_trampoline_func_start: call *%rcx pop %rbp ret -#endif // __x86_64__ -#if defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) - // ARM64 little endian, 64bit ABI - // generate with aarch64-linux-gnu-gcc 12.1 - SIGN_LR - stp x29, x30, [sp, -16]! - mov x29, sp - blr x3 - ldp x29, x30, [sp], 16 - VERIFY_LR - ret -#endif -#ifdef __riscv - addi sp,sp,-16 - sd ra,8(sp) - jalr a3 - ld ra,8(sp) - addi sp,sp,16 - jr ra -#endif #if defined(__APPLE__) .globl __Py_trampoline_func_end __Py_trampoline_func_end: @@ -56,7 +24,7 @@ _Py_trampoline_func_end: .section .note.GNU-stack,"",@progbits #endif # Note for indicating the assembly code supports CET -#if defined(__x86_64__) && defined(__CET__) && (__CET__ & 1) +#if defined(__CET__) && (__CET__ & 1) .section .note.gnu.property,"a" .align 8 .long 1f - 0f @@ -73,4 +41,5 @@ _Py_trampoline_func_end: 3: .align 8 4: +#endif #endif // __x86_64__ diff --git a/Python/bytecodes.c b/Python/bytecodes.c index f7487c7136962f1..300b7da753c2baf 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1116,7 +1116,8 @@ dummy_func( op(_STORE_SLICE, (v, container, start, stop -- )) { PyObject *slice = _PyBuildSlice_ConsumeRefs(PyStackRef_AsPyObjectSteal(start), - PyStackRef_AsPyObjectSteal(stop)); + PyStackRef_AsPyObjectSteal(stop), + Py_None); int err; if (slice == NULL) { err = 1; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index efa61d7de74e88c..952860f01b8a682 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -6811,7 +6811,8 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *slice = _PyBuildSlice_ConsumeRefs(PyStackRef_AsPyObjectSteal(start), - PyStackRef_AsPyObjectSteal(stop)); + PyStackRef_AsPyObjectSteal(stop), + Py_None); stack_pointer = _PyFrame_GetStackPointer(frame); int err; if (slice == NULL) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 53e09a8f4523c7c..83051cf41cc043b 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -12004,7 +12004,8 @@ v = stack_pointer[-4]; _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *slice = _PyBuildSlice_ConsumeRefs(PyStackRef_AsPyObjectSteal(start), - PyStackRef_AsPyObjectSteal(stop)); + PyStackRef_AsPyObjectSteal(stop), + Py_None); stack_pointer = _PyFrame_GetStackPointer(frame); int err; if (slice == NULL) { diff --git a/Python/hamt.c b/Python/hamt.c index e4719e71a5259a5..95998ae5062ac7e 100644 --- a/Python/hamt.c +++ b/Python/hamt.c @@ -702,6 +702,7 @@ hamt_node_bitmap_assoc(PyHamtNode_Bitmap *self, PyHamtNode_Bitmap *ret = hamt_node_bitmap_clone(self); if (ret == NULL) { + Py_DECREF(sub_node); return NULL; } Py_SETREF(ret->b_array[val_idx], (PyObject*)sub_node); @@ -994,6 +995,7 @@ hamt_node_bitmap_without(PyHamtNode_Bitmap *self, PyHamtNode_Bitmap *clone = hamt_node_bitmap_clone(self); if (clone == NULL) { + Py_DECREF(sub_node); return W_ERROR; } diff --git a/configure b/configure index a1b635ffd15c4a3..00e3ca25c86e8f9 100755 --- a/configure +++ b/configure @@ -14411,17 +14411,35 @@ printf "%s\n" "$SHLIBS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking perf trampoline" >&5 printf %s "checking perf trampoline... " >&6; } +PERF_TRAMPOLINE_OBJ="" case $PLATFORM_TRIPLET in #( x86_64-linux-gnu) : - perf_trampoline=yes ;; #( + perf_trampoline=yes + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_x86_64.o ;; #( aarch64-linux-gnu) : - perf_trampoline=yes ;; #( + perf_trampoline=yes + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_aarch64.o ;; #( darwin) : case $MACOSX_DEPLOYMENT_TARGET in #( 10.[0-9]|10.1[0-1]) : perf_trampoline=no ;; #( *) : perf_trampoline=yes + if test "${enable_universalsdk}" && test "$UNIVERSAL_ARCHS" = "universal2"; then + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_universal2.o + else + case "$host_cpu" in + x86_64) + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_x86_64.o + ;; + aarch64|arm64) + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_aarch64.o + ;; + *) + perf_trampoline=no + ;; + esac + fi ;; esac ;; #( *) : @@ -14437,7 +14455,6 @@ then : printf "%s\n" "#define PY_HAVE_PERF_TRAMPOLINE 1" >>confdefs.h - PERF_TRAMPOLINE_OBJ=Python/asm_trampoline.o fi diff --git a/configure.ac b/configure.ac index 082c6c2d756cdca..bb2a7eee9a116e3 100644 --- a/configure.ac +++ b/configure.ac @@ -3866,12 +3866,30 @@ AC_MSG_RESULT([$SHLIBS]) dnl perf trampoline is Linux and macOS specific and requires an arch-specific dnl trampoline in assembly. AC_MSG_CHECKING([perf trampoline]) +PERF_TRAMPOLINE_OBJ="" AS_CASE([$PLATFORM_TRIPLET], - [x86_64-linux-gnu], [perf_trampoline=yes], - [aarch64-linux-gnu], [perf_trampoline=yes], + [x86_64-linux-gnu], [perf_trampoline=yes + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_x86_64.o], + [aarch64-linux-gnu], [perf_trampoline=yes + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_aarch64.o], [darwin], [AS_CASE([$MACOSX_DEPLOYMENT_TARGET], [[10.[0-9]|10.1[0-1]]], [perf_trampoline=no], - [perf_trampoline=yes] + [perf_trampoline=yes + if test "${enable_universalsdk}" && test "$UNIVERSAL_ARCHS" = "universal2"; then + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_universal2.o + else + case "$host_cpu" in + x86_64) + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_x86_64.o + ;; + aarch64|arm64) + PERF_TRAMPOLINE_OBJ=Python/asm_trampoline_aarch64.o + ;; + *) + perf_trampoline=no + ;; + esac + fi] )], [perf_trampoline=no] ) @@ -3879,7 +3897,6 @@ AC_MSG_RESULT([$perf_trampoline]) AS_VAR_IF([perf_trampoline], [yes], [ AC_DEFINE([PY_HAVE_PERF_TRAMPOLINE], [1], [Define to 1 if you have the perf trampoline.]) - PERF_TRAMPOLINE_OBJ=Python/asm_trampoline.o ]) AC_SUBST([PERF_TRAMPOLINE_OBJ])