diff --git a/.gitignore b/.gitignore index 4ea2fd9655471d..e234d86e8d5532 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ gmon.out .pytest_cache/ .ruff_cache/ .DS_Store +.pixi/ *.exe diff --git a/Doc/deprecations/c-api-pending-removal-in-3.20.rst b/Doc/deprecations/c-api-pending-removal-in-3.20.rst index a813cb21dd4dbf..8de55bbe7e695c 100644 --- a/Doc/deprecations/c-api-pending-removal-in-3.20.rst +++ b/Doc/deprecations/c-api-pending-removal-in-3.20.rst @@ -3,7 +3,7 @@ Pending removal in Python 3.20 * :c:func:`!_PyObject_CallMethodId`, :c:func:`!_PyObject_GetAttrId` and :c:func:`!_PyUnicode_FromId` are deprecated since 3.15 and will be removed in - 3.20. Instead, use :c:func:`PyUnicode_FromString()` and cache the result in + 3.20. Instead, use :c:func:`PyUnicode_InternFromString()` and cache the result in the module state, then call :c:func:`PyObject_CallMethod` or :c:func:`PyObject_GetAttr`. (Contributed by Victor Stinner in :gh:`141049`.) diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 1e517531c953e9..4a6a6c3c43a722 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -7,6 +7,7 @@ Pending removal in Python 3.20 - :mod:`argparse` - :mod:`csv` + - :mod:`ctypes` - :mod:`!ctypes.macholib` - :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead) - :mod:`http.server` diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 9c0b246c095483..6038af99009d02 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -1388,6 +1388,9 @@ On Linux, :func:`~ctypes.util.find_library` tries to run external programs (``/sbin/ldconfig``, ``gcc``, ``objdump`` and ``ld``) to find the library file. It returns the filename of the library file. +Note that if the output of these programs does not correspond to the dynamic +linker used by Python, the result of this function may be misleading. + .. versionchanged:: 3.6 On Linux, the value of the environment variable ``LD_LIBRARY_PATH`` is used when searching for libraries, if a library cannot be found by any other means. @@ -2132,6 +2135,8 @@ Utility functions The exact functionality is system dependent. + See :ref:`ctypes-finding-shared-libraries` for complete documentation. + .. function:: find_msvcrt() :module: ctypes.util diff --git a/Doc/library/profiling.sampling.rst b/Doc/library/profiling.sampling.rst index e0e583d00500f7..1f60e2cb578c4d 100644 --- a/Doc/library/profiling.sampling.rst +++ b/Doc/library/profiling.sampling.rst @@ -295,21 +295,23 @@ The default configuration works well for most use cases: :widths: 25 75 * - Option - - Default behavior - * - ``--interval`` / ``-i`` + - Default + * - Default for ``--interval`` / ``-i`` - 100 µs between samples (~10,000 samples/sec) - * - ``--duration`` / ``-d`` - - Profile for 10 seconds - * - ``--all-threads`` / ``-a`` - - Sample main thread only - * - ``--native`` + * - Default for ``--duration`` / ``-d`` + - 10 seconds + * - Default for ``--all-threads`` / ``-a`` + - Main thread only + * - Default for ``--native`` - No ```` frames (C code time attributed to caller) - * - ``--no-gc`` - - Include ```` frames when garbage collection is active - * - ``--mode`` + * - Default for ``--no-gc`` + - ```` frames included when garbage collection is active + * - Default for ``--mode`` - Wall-clock mode (all samples recorded) - * - ``--realtime-stats`` - - No live statistics display during profiling + * - Default for ``--realtime-stats`` + - Disabled + * - Default for ``--subprocesses`` + - Disabled Sampling interval and duration @@ -442,6 +444,78 @@ working correctly and that sufficient samples are being collected. See :ref:`sampling-efficiency` for details on interpreting these metrics. +Subprocess profiling +-------------------- + +The :option:`--subprocesses` option enables automatic profiling of subprocesses +spawned by the target:: + + python -m profiling.sampling run --subprocesses script.py + python -m profiling.sampling attach --subprocesses 12345 + +When enabled, the profiler monitors the target process for child process +creation. When a new Python child process is detected, a separate profiler +instance is automatically spawned to profile it. This is useful for +applications that use :mod:`multiprocessing`, :mod:`subprocess`, +:mod:`concurrent.futures` with :class:`~concurrent.futures.ProcessPoolExecutor`, +or other process spawning mechanisms. + +.. code-block:: python + :caption: worker_pool.py + + from concurrent.futures import ProcessPoolExecutor + import math + + def compute_factorial(n): + total = 0 + for i in range(50): + total += math.factorial(n) + return total + + if __name__ == "__main__": + numbers = [5000 + i * 100 for i in range(50)] + with ProcessPoolExecutor(max_workers=4) as executor: + results = list(executor.map(compute_factorial, numbers)) + print(f"Computed {len(results)} factorials") + +:: + + python -m profiling.sampling run --subprocesses --flamegraph worker_pool.py + +This produces separate flame graphs for the main process and each worker +process: ``flamegraph_.html``, ``flamegraph_.html``, +and so on. + +Each subprocess receives its own output file. The filename is derived from +the specified output path (or the default) with the subprocess's process ID +appended: + +- If you specify ``-o profile.html``, subprocesses produce ``profile_12345.html``, + ``profile_12346.html``, and so on +- With default output, subprocesses produce files like ``flamegraph_12345.html`` + or directories like ``heatmap_12345`` +- For pstats format (which defaults to stdout), subprocesses produce files like + ``profile_12345.pstats`` + +The subprocess profilers inherit most sampling options from the parent (interval, +duration, thread selection, native frames, GC frames, async-aware mode, and +output format). All Python descendant processes are profiled recursively, +including grandchildren and further descendants. + +Subprocess detection works by periodically scanning for new descendants of +the target process and checking whether each new process is a Python process +by probing the process memory for Python runtime structures. Non-Python +subprocesses (such as shell commands or external tools) are ignored. + +There is a limit of 100 concurrent subprocess profilers to prevent resource +exhaustion in programs that spawn many processes. If this limit is reached, +additional subprocesses are not profiled and a warning is printed. + +The :option:`--subprocesses` option is incompatible with :option:`--live` mode +because live mode uses an interactive terminal interface that cannot +accommodate multiple concurrent profiler displays. + + .. _sampling-efficiency: Sampling efficiency @@ -1217,6 +1291,11 @@ Sampling options Compatible with ``--live``, ``--flamegraph``, ``--heatmap``, and ``--gecko`` formats only. +.. option:: --subprocesses + + Also profile subprocesses. Each subprocess gets its own profiler + instance and output file. Incompatible with ``--live``. + Mode options ------------ diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d9a34fe920d10d..19762584ef798c 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1032,6 +1032,7 @@ New deprecations - :mod:`argparse` - :mod:`csv` + - :mod:`ctypes` - :mod:`!ctypes.macholib` - :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead) - :mod:`http.server` @@ -1225,7 +1226,7 @@ Deprecated C APIs * :c:func:`!_PyObject_CallMethodId`, :c:func:`!_PyObject_GetAttrId` and :c:func:`!_PyUnicode_FromId` are deprecated since 3.15 and will be removed in - 3.20. Instead, use :c:func:`PyUnicode_FromString()` and cache the result in + 3.20. Instead, use :c:func:`PyUnicode_InternFromString()` and cache the result in the module state, then call :c:func:`PyObject_CallMethod` or :c:func:`PyObject_GetAttr`. (Contributed by Victor Stinner in :gh:`141049`.) diff --git a/Include/internal/pycore_context.h b/Include/internal/pycore_context.h index c77ef7910c09aa..a833f790a621b1 100644 --- a/Include/internal/pycore_context.h +++ b/Include/internal/pycore_context.h @@ -55,5 +55,8 @@ struct _pycontexttokenobject { // Export for '_testcapi' shared extension PyAPI_FUNC(PyObject*) _PyContext_NewHamtForTests(void); +PyAPI_FUNC(int) _PyContext_Enter(PyThreadState *ts, PyObject *octx); +PyAPI_FUNC(int) _PyContext_Exit(PyThreadState *ts, PyObject *octx); + #endif /* !Py_INTERNAL_CONTEXT_H */ diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 6473a3c64a6c23..56bc003ac3e246 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1994,6 +1994,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(readline)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(readonly)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(real)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(recursive)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(reducer_override)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(registry)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(rel_tol)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index ec720de2524e6e..8be948b92ec8f9 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -717,6 +717,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(readline) STRUCT_FOR_ID(readonly) STRUCT_FOR_ID(real) + STRUCT_FOR_ID(recursive) STRUCT_FOR_ID(reducer_override) STRUCT_FOR_ID(registry) STRUCT_FOR_ID(rel_tol) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index fb50acd62da5eb..6b91e4334b169e 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -496,6 +496,9 @@ static inline void Py_DECREF_MORTAL_SPECIALIZED(PyObject *op, destructor destruc #define Py_DECREF_MORTAL_SPECIALIZED(op, destruct) Py_DECREF_MORTAL_SPECIALIZED(_PyObject_CAST(op), destruct) #endif +#else // Py_GIL_DISABLED +# define Py_DECREF_MORTAL(op) Py_DECREF(op) +# define Py_DECREF_MORTAL_SPECIALIZED(op, destruct) Py_DECREF(op) #endif /* Inline functions trading binary compatibility for speed: @@ -1045,6 +1048,8 @@ static inline Py_ALWAYS_INLINE void _Py_INCREF_MORTAL(PyObject *op) } #endif } +#else +# define _Py_INCREF_MORTAL(op) Py_INCREF(op) #endif /* Utility for the tp_traverse slot of mutable heap types that have no other diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 0a29fabe7676dc..fda52806561430 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1121,7 +1121,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [CALL_KW_NON_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_KW_PY] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_SYNC_SP_FLAG | HAS_NEEDS_GUARD_IP_FLAG }, [CALL_LEN] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, - [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_METHOD_DESCRIPTOR_NOARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1367,7 +1367,7 @@ _PyOpcode_macro_expansion[256] = { [CALL_KW_NON_PY] = { .nuops = 3, .uops = { { _CHECK_IS_NOT_PY_CALLABLE_KW, OPARG_SIMPLE, 3 }, { _CALL_KW_NON_PY, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, [CALL_KW_PY] = { .nuops = 6, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_FUNCTION_VERSION_KW, 2, 1 }, { _CHECK_RECURSION_REMAINING, OPARG_SIMPLE, 3 }, { _PY_FRAME_KW, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } }, [CALL_LEN] = { .nuops = 5, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_LEN, OPARG_SIMPLE, 3 }, { _CALL_LEN, OPARG_SIMPLE, 3 }, { _POP_TOP, OPARG_SIMPLE, 3 }, { _POP_TOP, OPARG_SIMPLE, 3 } } }, - [CALL_LIST_APPEND] = { .nuops = 4, .uops = { { _GUARD_CALLABLE_LIST_APPEND, OPARG_SIMPLE, 3 }, { _GUARD_NOS_NOT_NULL, OPARG_SIMPLE, 3 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 3 }, { _CALL_LIST_APPEND, OPARG_SIMPLE, 3 } } }, + [CALL_LIST_APPEND] = { .nuops = 6, .uops = { { _GUARD_CALLABLE_LIST_APPEND, OPARG_SIMPLE, 3 }, { _GUARD_NOS_NOT_NULL, OPARG_SIMPLE, 3 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 3 }, { _CALL_LIST_APPEND, OPARG_SIMPLE, 3 }, { _POP_TOP, OPARG_SIMPLE, 3 }, { _POP_TOP, OPARG_SIMPLE, 3 } } }, [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_NOARGS, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } }, @@ -1493,7 +1493,7 @@ _PyOpcode_macro_expansion[256] = { [STORE_NAME] = { .nuops = 1, .uops = { { _STORE_NAME, OPARG_SIMPLE, 0 } } }, [STORE_SLICE] = { .nuops = 1, .uops = { { _STORE_SLICE, OPARG_SIMPLE, 0 } } }, [STORE_SUBSCR] = { .nuops = 1, .uops = { { _STORE_SUBSCR, OPARG_SIMPLE, 0 } } }, - [STORE_SUBSCR_DICT] = { .nuops = 2, .uops = { { _GUARD_NOS_DICT, OPARG_SIMPLE, 0 }, { _STORE_SUBSCR_DICT, OPARG_SIMPLE, 1 } } }, + [STORE_SUBSCR_DICT] = { .nuops = 3, .uops = { { _GUARD_NOS_DICT, OPARG_SIMPLE, 0 }, { _STORE_SUBSCR_DICT, OPARG_SIMPLE, 1 }, { _POP_TOP, OPARG_SIMPLE, 1 } } }, [STORE_SUBSCR_LIST_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _STORE_SUBSCR_LIST_INT, OPARG_SIMPLE, 1 }, { _POP_TOP_INT, OPARG_SIMPLE, 1 }, { _POP_TOP, OPARG_SIMPLE, 1 } } }, [SWAP] = { .nuops = 1, .uops = { { _SWAP, OPARG_SIMPLE, 0 } } }, [TO_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL, OPARG_SIMPLE, 2 } } }, diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index b32083db98e29e..d381fb9d2d42a3 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1992,6 +1992,7 @@ extern "C" { INIT_ID(readline), \ INIT_ID(readonly), \ INIT_ID(real), \ + INIT_ID(recursive), \ INIT_ID(reducer_override), \ INIT_ID(registry), \ INIT_ID(rel_tol), \ diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 996f87874ee65a..69d667b4be47d2 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -452,184 +452,6 @@ PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref) return (_PyStackRef){ .bits = ref.bits + (1 << Py_TAGGED_SHIFT) }; } -#define PyStackRef_IsDeferredOrTaggedInt(ref) (((ref).bits & Py_TAG_REFCNT) != 0) - -#ifdef Py_GIL_DISABLED - -#define Py_TAG_DEFERRED Py_TAG_REFCNT - -#define Py_TAG_PTR ((uintptr_t)0) - - -static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED}; - -#define PyStackRef_IsNull(stackref) ((stackref).bits == PyStackRef_NULL.bits) -#define PyStackRef_True ((_PyStackRef){.bits = ((uintptr_t)&_Py_TrueStruct) | Py_TAG_DEFERRED }) -#define PyStackRef_False ((_PyStackRef){.bits = ((uintptr_t)&_Py_FalseStruct) | Py_TAG_DEFERRED }) -#define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) | Py_TAG_DEFERRED }) - -// Checks that mask out the deferred bit in the free threading build. -#define PyStackRef_IsNone(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_None) -#define PyStackRef_IsTrue(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_True) -#define PyStackRef_IsFalse(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_False) - -#define PyStackRef_IsNullOrInt(stackref) (PyStackRef_IsNull(stackref) || PyStackRef_IsTaggedInt(stackref)) - -static inline PyObject * -PyStackRef_AsPyObjectBorrow(_PyStackRef stackref) -{ - assert(!PyStackRef_IsTaggedInt(stackref)); - PyObject *cleared = ((PyObject *)((stackref).bits & (~Py_TAG_BITS))); - return cleared; -} - -#define PyStackRef_IsDeferred(ref) (((ref).bits & Py_TAG_BITS) == Py_TAG_DEFERRED) - -static inline PyObject * -PyStackRef_AsPyObjectSteal(_PyStackRef stackref) -{ - assert(!PyStackRef_IsNull(stackref)); - if (PyStackRef_IsDeferred(stackref)) { - return Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)); - } - return PyStackRef_AsPyObjectBorrow(stackref); -} - -static inline _PyStackRef -_PyStackRef_FromPyObjectSteal(PyObject *obj) -{ - assert(obj != NULL); - // Make sure we don't take an already tagged value. - assert(((uintptr_t)obj & Py_TAG_BITS) == 0); - return (_PyStackRef){ .bits = (uintptr_t)obj }; -} -# define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj)) - -static inline bool -PyStackRef_IsHeapSafe(_PyStackRef stackref) -{ - if (PyStackRef_IsDeferred(stackref)) { - PyObject *obj = PyStackRef_AsPyObjectBorrow(stackref); - return obj == NULL || _Py_IsImmortal(obj) || _PyObject_HasDeferredRefcount(obj); - } - return true; -} - -static inline _PyStackRef -PyStackRef_MakeHeapSafe(_PyStackRef stackref) -{ - if (PyStackRef_IsHeapSafe(stackref)) { - return stackref; - } - PyObject *obj = PyStackRef_AsPyObjectBorrow(stackref); - return (_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) | Py_TAG_PTR }; -} - -static inline _PyStackRef -PyStackRef_FromPyObjectStealMortal(PyObject *obj) -{ - assert(obj != NULL); - assert(!_Py_IsImmortal(obj)); - // Make sure we don't take an already tagged value. - assert(((uintptr_t)obj & Py_TAG_BITS) == 0); - return (_PyStackRef){ .bits = (uintptr_t)obj }; -} - -static inline _PyStackRef -PyStackRef_FromPyObjectNew(PyObject *obj) -{ - // Make sure we don't take an already tagged value. - assert(((uintptr_t)obj & Py_TAG_BITS) == 0); - assert(obj != NULL); - if (_PyObject_HasDeferredRefcount(obj)) { - return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED }; - } - else { - return (_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) | Py_TAG_PTR }; - } -} -#define PyStackRef_FromPyObjectNew(obj) PyStackRef_FromPyObjectNew(_PyObject_CAST(obj)) - -static inline _PyStackRef -PyStackRef_FromPyObjectBorrow(PyObject *obj) -{ - // Make sure we don't take an already tagged value. - assert(((uintptr_t)obj & Py_TAG_BITS) == 0); - assert(obj != NULL); - return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED }; -} -#define PyStackRef_FromPyObjectBorrow(obj) PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj)) - -#define PyStackRef_CLOSE(REF) \ - do { \ - _PyStackRef _close_tmp = (REF); \ - assert(!PyStackRef_IsNull(_close_tmp)); \ - if (!PyStackRef_IsDeferredOrTaggedInt(_close_tmp)) { \ - Py_DECREF(PyStackRef_AsPyObjectBorrow(_close_tmp)); \ - } \ - } while (0) - -static inline void -PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct) -{ - (void)destruct; - PyStackRef_CLOSE(ref); -} - -static inline int -PyStackRef_RefcountOnObject(_PyStackRef ref) -{ - return (ref.bits & Py_TAG_REFCNT) == 0; -} - -static inline _PyStackRef -PyStackRef_DUP(_PyStackRef stackref) -{ - assert(!PyStackRef_IsNull(stackref)); - if (PyStackRef_IsDeferredOrTaggedInt(stackref)) { - return stackref; - } - Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref)); - return stackref; -} - -static inline _PyStackRef -PyStackRef_Borrow(_PyStackRef stackref) -{ - return (_PyStackRef){ .bits = stackref.bits | Py_TAG_DEFERRED }; -} - -// Convert a possibly deferred reference to a strong reference. -static inline _PyStackRef -PyStackRef_AsStrongReference(_PyStackRef stackref) -{ - return PyStackRef_FromPyObjectSteal(PyStackRef_AsPyObjectSteal(stackref)); -} - -#define PyStackRef_XCLOSE(stackref) \ - do { \ - _PyStackRef _tmp = (stackref); \ - if (!PyStackRef_IsNull(_tmp)) { \ - PyStackRef_CLOSE(_tmp); \ - } \ - } while (0); - -#define PyStackRef_CLEAR(op) \ - do { \ - _PyStackRef *_tmp_op_ptr = &(op); \ - _PyStackRef _tmp_old_op = (*_tmp_op_ptr); \ - if (!PyStackRef_IsNull(_tmp_old_op)) { \ - *_tmp_op_ptr = PyStackRef_NULL; \ - PyStackRef_CLOSE(_tmp_old_op); \ - } \ - } while (0) - -#define PyStackRef_FromPyObjectNewMortal PyStackRef_FromPyObjectNew - -#else // Py_GIL_DISABLED - -// With GIL - /* References to immortal objects always have their tag bit set to Py_TAG_REFCNT * as they can (must) have their reclamation deferred */ @@ -648,13 +470,24 @@ static const _PyStackRef PyStackRef_NULL = { .bits = PyStackRef_NULL_BITS }; #define PyStackRef_False ((_PyStackRef){.bits = ((uintptr_t)&_Py_FalseStruct) | Py_TAG_REFCNT }) #define PyStackRef_None ((_PyStackRef){.bits = ((uintptr_t)&_Py_NoneStruct) | Py_TAG_REFCNT }) +#ifdef Py_GIL_DISABLED +// Checks that mask out the deferred bit in the free threading build. +#define PyStackRef_IsNone(REF) (((REF).bits & ~Py_TAG_REFCNT) == (uintptr_t)&_Py_NoneStruct) +#define PyStackRef_IsTrue(REF) (((REF).bits & ~Py_TAG_REFCNT) == (uintptr_t)&_Py_TrueStruct) +#define PyStackRef_IsFalse(REF) (((REF).bits & ~Py_TAG_REFCNT) == (uintptr_t)&_Py_FalseStruct) +#else #define PyStackRef_IsTrue(REF) ((REF).bits == (((uintptr_t)&_Py_TrueStruct) | Py_TAG_REFCNT)) #define PyStackRef_IsFalse(REF) ((REF).bits == (((uintptr_t)&_Py_FalseStruct) | Py_TAG_REFCNT)) #define PyStackRef_IsNone(REF) ((REF).bits == (((uintptr_t)&_Py_NoneStruct) | Py_TAG_REFCNT)) +#endif -#ifdef Py_DEBUG +#define PyStackRef_IsNullOrInt(stackref) (PyStackRef_IsNull(stackref) || PyStackRef_IsTaggedInt(stackref)) + +#if defined(Py_DEBUG) && !defined(Py_GIL_DISABLED) -static inline void PyStackRef_CheckValid(_PyStackRef ref) { +static inline void +PyStackRef_CheckValid(_PyStackRef ref) +{ assert(ref.bits != 0); int tag = ref.bits & Py_TAG_BITS; PyObject *obj = BITS_TO_PTR_MASKED(ref); @@ -705,6 +538,8 @@ PyStackRef_Borrow(_PyStackRef ref) static inline PyObject * PyStackRef_AsPyObjectSteal(_PyStackRef ref) { + assert(!PyStackRef_IsNull(ref)); + assert(!PyStackRef_IsTaggedInt(ref)); if (PyStackRef_RefcountOnObject(ref)) { return BITS_TO_PTR(ref); } @@ -717,14 +552,18 @@ static inline _PyStackRef PyStackRef_FromPyObjectSteal(PyObject *obj) { assert(obj != NULL); -#if SIZEOF_VOID_P > 4 - unsigned int tag = obj->ob_flags & Py_TAG_REFCNT; +#ifdef Py_GIL_DISABLED + return (_PyStackRef){ .bits = (uintptr_t)obj }; #else +# if SIZEOF_VOID_P > 4 + unsigned int tag = obj->ob_flags & Py_TAG_REFCNT; +# else unsigned int tag = _Py_IsImmortal(obj) ? Py_TAG_REFCNT : 0; -#endif +# endif _PyStackRef ref = ((_PyStackRef){.bits = ((uintptr_t)(obj)) | tag}); PyStackRef_CheckValid(ref); return ref; +#endif } static inline _PyStackRef @@ -732,7 +571,7 @@ PyStackRef_FromPyObjectStealMortal(PyObject *obj) { assert(obj != NULL); assert(!_Py_IsImmortal(obj)); - _PyStackRef ref = ((_PyStackRef){.bits = ((uintptr_t)(obj)) }); + _PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj }; PyStackRef_CheckValid(ref); return ref; } @@ -741,9 +580,15 @@ static inline _PyStackRef _PyStackRef_FromPyObjectNew(PyObject *obj) { assert(obj != NULL); +#ifdef Py_GIL_DISABLED + if (_PyObject_HasDeferredRefcount(obj)) { + return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT }; + } +#else if (_Py_IsImmortal(obj)) { - return (_PyStackRef){ .bits = ((uintptr_t)obj) | Py_TAG_REFCNT}; + return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT }; } +#endif _Py_INCREF_MORTAL(obj); _PyStackRef ref = (_PyStackRef){ .bits = (uintptr_t)obj }; PyStackRef_CheckValid(ref); @@ -766,6 +611,7 @@ _PyStackRef_FromPyObjectNewMortal(PyObject *obj) static inline _PyStackRef PyStackRef_FromPyObjectBorrow(PyObject *obj) { + assert(obj != NULL); return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT}; } @@ -788,7 +634,15 @@ PyStackRef_DUP(_PyStackRef ref) static inline bool PyStackRef_IsHeapSafe(_PyStackRef ref) { - return (ref.bits & Py_TAG_BITS) != Py_TAG_REFCNT || ref.bits == PyStackRef_NULL_BITS || _Py_IsImmortal(BITS_TO_PTR_MASKED(ref)); +#ifdef Py_GIL_DISABLED + if ((ref.bits & Py_TAG_BITS) != Py_TAG_REFCNT) { + return true; + } + PyObject *obj = BITS_TO_PTR_MASKED(ref); + return obj == NULL || _PyObject_HasDeferredRefcount(obj); +#else + return (ref.bits & Py_TAG_BITS) != Py_TAG_REFCNT || ref.bits == PyStackRef_NULL_BITS || _Py_IsImmortal(BITS_TO_PTR_MASKED(ref)); +#endif } static inline _PyStackRef @@ -804,6 +658,13 @@ PyStackRef_MakeHeapSafe(_PyStackRef ref) return ref; } +// Convert a possibly deferred reference to a strong reference. +static inline _PyStackRef +PyStackRef_AsStrongReference(_PyStackRef stackref) +{ + return PyStackRef_FromPyObjectSteal(PyStackRef_AsPyObjectSteal(stackref)); +} + #ifdef _WIN32 #define PyStackRef_CLOSE(REF) \ do { \ @@ -821,12 +682,6 @@ PyStackRef_CLOSE(_PyStackRef ref) } #endif -static inline bool -PyStackRef_IsNullOrInt(_PyStackRef ref) -{ - return PyStackRef_IsNull(ref) || PyStackRef_IsTaggedInt(ref); -} - static inline void PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct) { @@ -859,8 +714,6 @@ PyStackRef_XCLOSE(_PyStackRef ref) } while (0) -#endif // Py_GIL_DISABLED - // Note: this is a macro because MSVC (Windows) has trouble inlining it. #define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_REFCNT)) == ((b).bits & (~Py_TAG_REFCNT))) @@ -945,7 +798,7 @@ static inline int _Py_TryIncrefCompareStackRef(PyObject **src, PyObject *op, _PyStackRef *out) { if (_PyObject_HasDeferredRefcount(op)) { - *out = (_PyStackRef){ .bits = (uintptr_t)op | Py_TAG_DEFERRED }; + *out = (_PyStackRef){ .bits = (uintptr_t)op | Py_TAG_REFCNT }; return 1; } if (_Py_TryIncrefCompare(src, op)) { diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index f3756fde2c4073..24e50828935106 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -2648,6 +2648,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(recursive); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(reducer_override); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 64e51bd2b8bb58..42793d940b51b7 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -432,683 +432,686 @@ extern "C" { #define _CALL_ISINSTANCE_r31 625 #define _CALL_KW_NON_PY_r11 626 #define _CALL_LEN_r33 627 -#define _CALL_LIST_APPEND_r30 628 -#define _CALL_METHOD_DESCRIPTOR_FAST_r01 629 -#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r01 630 -#define _CALL_METHOD_DESCRIPTOR_NOARGS_r01 631 -#define _CALL_METHOD_DESCRIPTOR_O_r01 632 -#define _CALL_NON_PY_GENERAL_r01 633 -#define _CALL_STR_1_r32 634 -#define _CALL_TUPLE_1_r32 635 -#define _CALL_TYPE_1_r31 636 -#define _CHECK_AND_ALLOCATE_OBJECT_r00 637 -#define _CHECK_ATTR_CLASS_r01 638 -#define _CHECK_ATTR_CLASS_r11 639 -#define _CHECK_ATTR_CLASS_r22 640 -#define _CHECK_ATTR_CLASS_r33 641 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r01 642 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r11 643 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r22 644 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r33 645 -#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS_r00 646 -#define _CHECK_EG_MATCH_r22 647 -#define _CHECK_EXC_MATCH_r22 648 -#define _CHECK_FUNCTION_EXACT_ARGS_r00 649 -#define _CHECK_FUNCTION_VERSION_r00 650 -#define _CHECK_FUNCTION_VERSION_INLINE_r00 651 -#define _CHECK_FUNCTION_VERSION_INLINE_r11 652 -#define _CHECK_FUNCTION_VERSION_INLINE_r22 653 -#define _CHECK_FUNCTION_VERSION_INLINE_r33 654 -#define _CHECK_FUNCTION_VERSION_KW_r11 655 -#define _CHECK_IS_NOT_PY_CALLABLE_r00 656 -#define _CHECK_IS_NOT_PY_CALLABLE_KW_r11 657 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r01 658 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r11 659 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r22 660 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r33 661 -#define _CHECK_METHOD_VERSION_r00 662 -#define _CHECK_METHOD_VERSION_KW_r11 663 -#define _CHECK_PEP_523_r00 664 -#define _CHECK_PEP_523_r11 665 -#define _CHECK_PEP_523_r22 666 -#define _CHECK_PEP_523_r33 667 -#define _CHECK_PERIODIC_r00 668 -#define _CHECK_PERIODIC_AT_END_r00 669 -#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM_r00 670 -#define _CHECK_RECURSION_REMAINING_r00 671 -#define _CHECK_RECURSION_REMAINING_r11 672 -#define _CHECK_RECURSION_REMAINING_r22 673 -#define _CHECK_RECURSION_REMAINING_r33 674 -#define _CHECK_STACK_SPACE_r00 675 -#define _CHECK_STACK_SPACE_OPERAND_r00 676 -#define _CHECK_STACK_SPACE_OPERAND_r11 677 -#define _CHECK_STACK_SPACE_OPERAND_r22 678 -#define _CHECK_STACK_SPACE_OPERAND_r33 679 -#define _CHECK_VALIDITY_r00 680 -#define _CHECK_VALIDITY_r11 681 -#define _CHECK_VALIDITY_r22 682 -#define _CHECK_VALIDITY_r33 683 -#define _COLD_DYNAMIC_EXIT_r00 684 -#define _COLD_EXIT_r00 685 -#define _COMPARE_OP_r21 686 -#define _COMPARE_OP_FLOAT_r01 687 -#define _COMPARE_OP_FLOAT_r11 688 -#define _COMPARE_OP_FLOAT_r21 689 -#define _COMPARE_OP_FLOAT_r32 690 -#define _COMPARE_OP_INT_r21 691 -#define _COMPARE_OP_STR_r21 692 -#define _CONTAINS_OP_r21 693 -#define _CONTAINS_OP_DICT_r21 694 -#define _CONTAINS_OP_SET_r21 695 -#define _CONVERT_VALUE_r11 696 -#define _COPY_r01 697 -#define _COPY_1_r02 698 -#define _COPY_1_r12 699 -#define _COPY_1_r23 700 -#define _COPY_2_r03 701 -#define _COPY_2_r13 702 -#define _COPY_2_r23 703 -#define _COPY_3_r03 704 -#define _COPY_3_r13 705 -#define _COPY_3_r23 706 -#define _COPY_3_r33 707 -#define _COPY_FREE_VARS_r00 708 -#define _COPY_FREE_VARS_r11 709 -#define _COPY_FREE_VARS_r22 710 -#define _COPY_FREE_VARS_r33 711 -#define _CREATE_INIT_FRAME_r01 712 -#define _DELETE_ATTR_r10 713 -#define _DELETE_DEREF_r00 714 -#define _DELETE_FAST_r00 715 -#define _DELETE_GLOBAL_r00 716 -#define _DELETE_NAME_r00 717 -#define _DELETE_SUBSCR_r20 718 -#define _DEOPT_r00 719 -#define _DEOPT_r10 720 -#define _DEOPT_r20 721 -#define _DEOPT_r30 722 -#define _DICT_MERGE_r10 723 -#define _DICT_UPDATE_r10 724 -#define _DO_CALL_r01 725 -#define _DO_CALL_FUNCTION_EX_r31 726 -#define _DO_CALL_KW_r11 727 -#define _DYNAMIC_EXIT_r00 728 -#define _DYNAMIC_EXIT_r10 729 -#define _DYNAMIC_EXIT_r20 730 -#define _DYNAMIC_EXIT_r30 731 -#define _END_FOR_r10 732 -#define _END_SEND_r21 733 -#define _ERROR_POP_N_r00 734 -#define _EXIT_INIT_CHECK_r10 735 -#define _EXIT_TRACE_r00 736 -#define _EXIT_TRACE_r10 737 -#define _EXIT_TRACE_r20 738 -#define _EXIT_TRACE_r30 739 -#define _EXPAND_METHOD_r00 740 -#define _EXPAND_METHOD_KW_r11 741 -#define _FATAL_ERROR_r00 742 -#define _FATAL_ERROR_r11 743 -#define _FATAL_ERROR_r22 744 -#define _FATAL_ERROR_r33 745 -#define _FORMAT_SIMPLE_r11 746 -#define _FORMAT_WITH_SPEC_r21 747 -#define _FOR_ITER_r23 748 -#define _FOR_ITER_GEN_FRAME_r23 749 -#define _FOR_ITER_TIER_TWO_r23 750 -#define _GET_AITER_r11 751 -#define _GET_ANEXT_r12 752 -#define _GET_AWAITABLE_r11 753 -#define _GET_ITER_r12 754 -#define _GET_LEN_r12 755 -#define _GET_YIELD_FROM_ITER_r11 756 -#define _GUARD_BINARY_OP_EXTEND_r22 757 -#define _GUARD_CALLABLE_ISINSTANCE_r03 758 -#define _GUARD_CALLABLE_ISINSTANCE_r13 759 -#define _GUARD_CALLABLE_ISINSTANCE_r23 760 -#define _GUARD_CALLABLE_ISINSTANCE_r33 761 -#define _GUARD_CALLABLE_LEN_r03 762 -#define _GUARD_CALLABLE_LEN_r13 763 -#define _GUARD_CALLABLE_LEN_r23 764 -#define _GUARD_CALLABLE_LEN_r33 765 -#define _GUARD_CALLABLE_LIST_APPEND_r03 766 -#define _GUARD_CALLABLE_LIST_APPEND_r13 767 -#define _GUARD_CALLABLE_LIST_APPEND_r23 768 -#define _GUARD_CALLABLE_LIST_APPEND_r33 769 -#define _GUARD_CALLABLE_STR_1_r03 770 -#define _GUARD_CALLABLE_STR_1_r13 771 -#define _GUARD_CALLABLE_STR_1_r23 772 -#define _GUARD_CALLABLE_STR_1_r33 773 -#define _GUARD_CALLABLE_TUPLE_1_r03 774 -#define _GUARD_CALLABLE_TUPLE_1_r13 775 -#define _GUARD_CALLABLE_TUPLE_1_r23 776 -#define _GUARD_CALLABLE_TUPLE_1_r33 777 -#define _GUARD_CALLABLE_TYPE_1_r03 778 -#define _GUARD_CALLABLE_TYPE_1_r13 779 -#define _GUARD_CALLABLE_TYPE_1_r23 780 -#define _GUARD_CALLABLE_TYPE_1_r33 781 -#define _GUARD_DORV_NO_DICT_r01 782 -#define _GUARD_DORV_NO_DICT_r11 783 -#define _GUARD_DORV_NO_DICT_r22 784 -#define _GUARD_DORV_NO_DICT_r33 785 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 786 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 787 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 788 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 789 -#define _GUARD_GLOBALS_VERSION_r00 790 -#define _GUARD_GLOBALS_VERSION_r11 791 -#define _GUARD_GLOBALS_VERSION_r22 792 -#define _GUARD_GLOBALS_VERSION_r33 793 -#define _GUARD_IP_RETURN_GENERATOR_r00 794 -#define _GUARD_IP_RETURN_GENERATOR_r11 795 -#define _GUARD_IP_RETURN_GENERATOR_r22 796 -#define _GUARD_IP_RETURN_GENERATOR_r33 797 -#define _GUARD_IP_RETURN_VALUE_r00 798 -#define _GUARD_IP_RETURN_VALUE_r11 799 -#define _GUARD_IP_RETURN_VALUE_r22 800 -#define _GUARD_IP_RETURN_VALUE_r33 801 -#define _GUARD_IP_YIELD_VALUE_r00 802 -#define _GUARD_IP_YIELD_VALUE_r11 803 -#define _GUARD_IP_YIELD_VALUE_r22 804 -#define _GUARD_IP_YIELD_VALUE_r33 805 -#define _GUARD_IP__PUSH_FRAME_r00 806 -#define _GUARD_IP__PUSH_FRAME_r11 807 -#define _GUARD_IP__PUSH_FRAME_r22 808 -#define _GUARD_IP__PUSH_FRAME_r33 809 -#define _GUARD_IS_FALSE_POP_r00 810 -#define _GUARD_IS_FALSE_POP_r10 811 -#define _GUARD_IS_FALSE_POP_r21 812 -#define _GUARD_IS_FALSE_POP_r32 813 -#define _GUARD_IS_NONE_POP_r00 814 -#define _GUARD_IS_NONE_POP_r10 815 -#define _GUARD_IS_NONE_POP_r21 816 -#define _GUARD_IS_NONE_POP_r32 817 -#define _GUARD_IS_NOT_NONE_POP_r10 818 -#define _GUARD_IS_TRUE_POP_r00 819 -#define _GUARD_IS_TRUE_POP_r10 820 -#define _GUARD_IS_TRUE_POP_r21 821 -#define _GUARD_IS_TRUE_POP_r32 822 -#define _GUARD_KEYS_VERSION_r01 823 -#define _GUARD_KEYS_VERSION_r11 824 -#define _GUARD_KEYS_VERSION_r22 825 -#define _GUARD_KEYS_VERSION_r33 826 -#define _GUARD_NOS_DICT_r02 827 -#define _GUARD_NOS_DICT_r12 828 -#define _GUARD_NOS_DICT_r22 829 -#define _GUARD_NOS_DICT_r33 830 -#define _GUARD_NOS_FLOAT_r02 831 -#define _GUARD_NOS_FLOAT_r12 832 -#define _GUARD_NOS_FLOAT_r22 833 -#define _GUARD_NOS_FLOAT_r33 834 -#define _GUARD_NOS_INT_r02 835 -#define _GUARD_NOS_INT_r12 836 -#define _GUARD_NOS_INT_r22 837 -#define _GUARD_NOS_INT_r33 838 -#define _GUARD_NOS_LIST_r02 839 -#define _GUARD_NOS_LIST_r12 840 -#define _GUARD_NOS_LIST_r22 841 -#define _GUARD_NOS_LIST_r33 842 -#define _GUARD_NOS_NOT_NULL_r02 843 -#define _GUARD_NOS_NOT_NULL_r12 844 -#define _GUARD_NOS_NOT_NULL_r22 845 -#define _GUARD_NOS_NOT_NULL_r33 846 -#define _GUARD_NOS_NULL_r02 847 -#define _GUARD_NOS_NULL_r12 848 -#define _GUARD_NOS_NULL_r22 849 -#define _GUARD_NOS_NULL_r33 850 -#define _GUARD_NOS_OVERFLOWED_r02 851 -#define _GUARD_NOS_OVERFLOWED_r12 852 -#define _GUARD_NOS_OVERFLOWED_r22 853 -#define _GUARD_NOS_OVERFLOWED_r33 854 -#define _GUARD_NOS_TUPLE_r02 855 -#define _GUARD_NOS_TUPLE_r12 856 -#define _GUARD_NOS_TUPLE_r22 857 -#define _GUARD_NOS_TUPLE_r33 858 -#define _GUARD_NOS_UNICODE_r02 859 -#define _GUARD_NOS_UNICODE_r12 860 -#define _GUARD_NOS_UNICODE_r22 861 -#define _GUARD_NOS_UNICODE_r33 862 -#define _GUARD_NOT_EXHAUSTED_LIST_r02 863 -#define _GUARD_NOT_EXHAUSTED_LIST_r12 864 -#define _GUARD_NOT_EXHAUSTED_LIST_r22 865 -#define _GUARD_NOT_EXHAUSTED_LIST_r33 866 -#define _GUARD_NOT_EXHAUSTED_RANGE_r02 867 -#define _GUARD_NOT_EXHAUSTED_RANGE_r12 868 -#define _GUARD_NOT_EXHAUSTED_RANGE_r22 869 -#define _GUARD_NOT_EXHAUSTED_RANGE_r33 870 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 871 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 872 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 873 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 874 -#define _GUARD_THIRD_NULL_r03 875 -#define _GUARD_THIRD_NULL_r13 876 -#define _GUARD_THIRD_NULL_r23 877 -#define _GUARD_THIRD_NULL_r33 878 -#define _GUARD_TOS_ANY_SET_r01 879 -#define _GUARD_TOS_ANY_SET_r11 880 -#define _GUARD_TOS_ANY_SET_r22 881 -#define _GUARD_TOS_ANY_SET_r33 882 -#define _GUARD_TOS_DICT_r01 883 -#define _GUARD_TOS_DICT_r11 884 -#define _GUARD_TOS_DICT_r22 885 -#define _GUARD_TOS_DICT_r33 886 -#define _GUARD_TOS_FLOAT_r01 887 -#define _GUARD_TOS_FLOAT_r11 888 -#define _GUARD_TOS_FLOAT_r22 889 -#define _GUARD_TOS_FLOAT_r33 890 -#define _GUARD_TOS_INT_r01 891 -#define _GUARD_TOS_INT_r11 892 -#define _GUARD_TOS_INT_r22 893 -#define _GUARD_TOS_INT_r33 894 -#define _GUARD_TOS_LIST_r01 895 -#define _GUARD_TOS_LIST_r11 896 -#define _GUARD_TOS_LIST_r22 897 -#define _GUARD_TOS_LIST_r33 898 -#define _GUARD_TOS_OVERFLOWED_r01 899 -#define _GUARD_TOS_OVERFLOWED_r11 900 -#define _GUARD_TOS_OVERFLOWED_r22 901 -#define _GUARD_TOS_OVERFLOWED_r33 902 -#define _GUARD_TOS_SLICE_r01 903 -#define _GUARD_TOS_SLICE_r11 904 -#define _GUARD_TOS_SLICE_r22 905 -#define _GUARD_TOS_SLICE_r33 906 -#define _GUARD_TOS_TUPLE_r01 907 -#define _GUARD_TOS_TUPLE_r11 908 -#define _GUARD_TOS_TUPLE_r22 909 -#define _GUARD_TOS_TUPLE_r33 910 -#define _GUARD_TOS_UNICODE_r01 911 -#define _GUARD_TOS_UNICODE_r11 912 -#define _GUARD_TOS_UNICODE_r22 913 -#define _GUARD_TOS_UNICODE_r33 914 -#define _GUARD_TYPE_VERSION_r01 915 -#define _GUARD_TYPE_VERSION_r11 916 -#define _GUARD_TYPE_VERSION_r22 917 -#define _GUARD_TYPE_VERSION_r33 918 -#define _GUARD_TYPE_VERSION_AND_LOCK_r01 919 -#define _GUARD_TYPE_VERSION_AND_LOCK_r11 920 -#define _GUARD_TYPE_VERSION_AND_LOCK_r22 921 -#define _GUARD_TYPE_VERSION_AND_LOCK_r33 922 -#define _HANDLE_PENDING_AND_DEOPT_r00 923 -#define _HANDLE_PENDING_AND_DEOPT_r10 924 -#define _HANDLE_PENDING_AND_DEOPT_r20 925 -#define _HANDLE_PENDING_AND_DEOPT_r30 926 -#define _IMPORT_FROM_r12 927 -#define _IMPORT_NAME_r21 928 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 929 -#define _INIT_CALL_PY_EXACT_ARGS_r01 930 -#define _INIT_CALL_PY_EXACT_ARGS_0_r01 931 -#define _INIT_CALL_PY_EXACT_ARGS_1_r01 932 -#define _INIT_CALL_PY_EXACT_ARGS_2_r01 933 -#define _INIT_CALL_PY_EXACT_ARGS_3_r01 934 -#define _INIT_CALL_PY_EXACT_ARGS_4_r01 935 -#define _INSERT_NULL_r10 936 -#define _INSTRUMENTED_FOR_ITER_r23 937 -#define _INSTRUMENTED_INSTRUCTION_r00 938 -#define _INSTRUMENTED_JUMP_FORWARD_r00 939 -#define _INSTRUMENTED_JUMP_FORWARD_r11 940 -#define _INSTRUMENTED_JUMP_FORWARD_r22 941 -#define _INSTRUMENTED_JUMP_FORWARD_r33 942 -#define _INSTRUMENTED_LINE_r00 943 -#define _INSTRUMENTED_NOT_TAKEN_r00 944 -#define _INSTRUMENTED_NOT_TAKEN_r11 945 -#define _INSTRUMENTED_NOT_TAKEN_r22 946 -#define _INSTRUMENTED_NOT_TAKEN_r33 947 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 948 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 949 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 950 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 951 -#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 952 -#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 953 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 954 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 955 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 956 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 957 -#define _IS_NONE_r11 958 -#define _IS_OP_r21 959 -#define _ITER_CHECK_LIST_r02 960 -#define _ITER_CHECK_LIST_r12 961 -#define _ITER_CHECK_LIST_r22 962 -#define _ITER_CHECK_LIST_r33 963 -#define _ITER_CHECK_RANGE_r02 964 -#define _ITER_CHECK_RANGE_r12 965 -#define _ITER_CHECK_RANGE_r22 966 -#define _ITER_CHECK_RANGE_r33 967 -#define _ITER_CHECK_TUPLE_r02 968 -#define _ITER_CHECK_TUPLE_r12 969 -#define _ITER_CHECK_TUPLE_r22 970 -#define _ITER_CHECK_TUPLE_r33 971 -#define _ITER_JUMP_LIST_r02 972 -#define _ITER_JUMP_LIST_r12 973 -#define _ITER_JUMP_LIST_r22 974 -#define _ITER_JUMP_LIST_r33 975 -#define _ITER_JUMP_RANGE_r02 976 -#define _ITER_JUMP_RANGE_r12 977 -#define _ITER_JUMP_RANGE_r22 978 -#define _ITER_JUMP_RANGE_r33 979 -#define _ITER_JUMP_TUPLE_r02 980 -#define _ITER_JUMP_TUPLE_r12 981 -#define _ITER_JUMP_TUPLE_r22 982 -#define _ITER_JUMP_TUPLE_r33 983 -#define _ITER_NEXT_LIST_r23 984 -#define _ITER_NEXT_LIST_TIER_TWO_r23 985 -#define _ITER_NEXT_RANGE_r03 986 -#define _ITER_NEXT_RANGE_r13 987 -#define _ITER_NEXT_RANGE_r23 988 -#define _ITER_NEXT_TUPLE_r03 989 -#define _ITER_NEXT_TUPLE_r13 990 -#define _ITER_NEXT_TUPLE_r23 991 -#define _JUMP_BACKWARD_NO_INTERRUPT_r00 992 -#define _JUMP_BACKWARD_NO_INTERRUPT_r11 993 -#define _JUMP_BACKWARD_NO_INTERRUPT_r22 994 -#define _JUMP_BACKWARD_NO_INTERRUPT_r33 995 -#define _JUMP_TO_TOP_r00 996 -#define _LIST_APPEND_r10 997 -#define _LIST_EXTEND_r10 998 -#define _LOAD_ATTR_r10 999 -#define _LOAD_ATTR_CLASS_r11 1000 -#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_r11 1001 -#define _LOAD_ATTR_INSTANCE_VALUE_r11 1002 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 1003 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 1004 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 1005 -#define _LOAD_ATTR_METHOD_NO_DICT_r02 1006 -#define _LOAD_ATTR_METHOD_NO_DICT_r12 1007 -#define _LOAD_ATTR_METHOD_NO_DICT_r23 1008 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 1009 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 1010 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 1011 -#define _LOAD_ATTR_MODULE_r11 1012 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 1013 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1014 -#define _LOAD_ATTR_PROPERTY_FRAME_r11 1015 -#define _LOAD_ATTR_SLOT_r11 1016 -#define _LOAD_ATTR_WITH_HINT_r11 1017 -#define _LOAD_BUILD_CLASS_r01 1018 -#define _LOAD_BYTECODE_r00 1019 -#define _LOAD_COMMON_CONSTANT_r01 1020 -#define _LOAD_COMMON_CONSTANT_r12 1021 -#define _LOAD_COMMON_CONSTANT_r23 1022 -#define _LOAD_CONST_r01 1023 -#define _LOAD_CONST_r12 1024 -#define _LOAD_CONST_r23 1025 -#define _LOAD_CONST_INLINE_r01 1026 -#define _LOAD_CONST_INLINE_r12 1027 -#define _LOAD_CONST_INLINE_r23 1028 -#define _LOAD_CONST_INLINE_BORROW_r01 1029 -#define _LOAD_CONST_INLINE_BORROW_r12 1030 -#define _LOAD_CONST_INLINE_BORROW_r23 1031 -#define _LOAD_CONST_UNDER_INLINE_r02 1032 -#define _LOAD_CONST_UNDER_INLINE_r12 1033 -#define _LOAD_CONST_UNDER_INLINE_r23 1034 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r02 1035 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r12 1036 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r23 1037 -#define _LOAD_DEREF_r01 1038 -#define _LOAD_FAST_r01 1039 -#define _LOAD_FAST_r12 1040 -#define _LOAD_FAST_r23 1041 -#define _LOAD_FAST_0_r01 1042 -#define _LOAD_FAST_0_r12 1043 -#define _LOAD_FAST_0_r23 1044 -#define _LOAD_FAST_1_r01 1045 -#define _LOAD_FAST_1_r12 1046 -#define _LOAD_FAST_1_r23 1047 -#define _LOAD_FAST_2_r01 1048 -#define _LOAD_FAST_2_r12 1049 -#define _LOAD_FAST_2_r23 1050 -#define _LOAD_FAST_3_r01 1051 -#define _LOAD_FAST_3_r12 1052 -#define _LOAD_FAST_3_r23 1053 -#define _LOAD_FAST_4_r01 1054 -#define _LOAD_FAST_4_r12 1055 -#define _LOAD_FAST_4_r23 1056 -#define _LOAD_FAST_5_r01 1057 -#define _LOAD_FAST_5_r12 1058 -#define _LOAD_FAST_5_r23 1059 -#define _LOAD_FAST_6_r01 1060 -#define _LOAD_FAST_6_r12 1061 -#define _LOAD_FAST_6_r23 1062 -#define _LOAD_FAST_7_r01 1063 -#define _LOAD_FAST_7_r12 1064 -#define _LOAD_FAST_7_r23 1065 -#define _LOAD_FAST_AND_CLEAR_r01 1066 -#define _LOAD_FAST_AND_CLEAR_r12 1067 -#define _LOAD_FAST_AND_CLEAR_r23 1068 -#define _LOAD_FAST_BORROW_r01 1069 -#define _LOAD_FAST_BORROW_r12 1070 -#define _LOAD_FAST_BORROW_r23 1071 -#define _LOAD_FAST_BORROW_0_r01 1072 -#define _LOAD_FAST_BORROW_0_r12 1073 -#define _LOAD_FAST_BORROW_0_r23 1074 -#define _LOAD_FAST_BORROW_1_r01 1075 -#define _LOAD_FAST_BORROW_1_r12 1076 -#define _LOAD_FAST_BORROW_1_r23 1077 -#define _LOAD_FAST_BORROW_2_r01 1078 -#define _LOAD_FAST_BORROW_2_r12 1079 -#define _LOAD_FAST_BORROW_2_r23 1080 -#define _LOAD_FAST_BORROW_3_r01 1081 -#define _LOAD_FAST_BORROW_3_r12 1082 -#define _LOAD_FAST_BORROW_3_r23 1083 -#define _LOAD_FAST_BORROW_4_r01 1084 -#define _LOAD_FAST_BORROW_4_r12 1085 -#define _LOAD_FAST_BORROW_4_r23 1086 -#define _LOAD_FAST_BORROW_5_r01 1087 -#define _LOAD_FAST_BORROW_5_r12 1088 -#define _LOAD_FAST_BORROW_5_r23 1089 -#define _LOAD_FAST_BORROW_6_r01 1090 -#define _LOAD_FAST_BORROW_6_r12 1091 -#define _LOAD_FAST_BORROW_6_r23 1092 -#define _LOAD_FAST_BORROW_7_r01 1093 -#define _LOAD_FAST_BORROW_7_r12 1094 -#define _LOAD_FAST_BORROW_7_r23 1095 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1096 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1097 -#define _LOAD_FAST_CHECK_r01 1098 -#define _LOAD_FAST_LOAD_FAST_r02 1099 -#define _LOAD_FAST_LOAD_FAST_r13 1100 -#define _LOAD_FROM_DICT_OR_DEREF_r11 1101 -#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1102 -#define _LOAD_GLOBAL_r00 1103 -#define _LOAD_GLOBAL_BUILTINS_r01 1104 -#define _LOAD_GLOBAL_MODULE_r01 1105 -#define _LOAD_LOCALS_r01 1106 -#define _LOAD_NAME_r01 1107 -#define _LOAD_SMALL_INT_r01 1108 -#define _LOAD_SMALL_INT_r12 1109 -#define _LOAD_SMALL_INT_r23 1110 -#define _LOAD_SMALL_INT_0_r01 1111 -#define _LOAD_SMALL_INT_0_r12 1112 -#define _LOAD_SMALL_INT_0_r23 1113 -#define _LOAD_SMALL_INT_1_r01 1114 -#define _LOAD_SMALL_INT_1_r12 1115 -#define _LOAD_SMALL_INT_1_r23 1116 -#define _LOAD_SMALL_INT_2_r01 1117 -#define _LOAD_SMALL_INT_2_r12 1118 -#define _LOAD_SMALL_INT_2_r23 1119 -#define _LOAD_SMALL_INT_3_r01 1120 -#define _LOAD_SMALL_INT_3_r12 1121 -#define _LOAD_SMALL_INT_3_r23 1122 -#define _LOAD_SPECIAL_r00 1123 -#define _LOAD_SUPER_ATTR_ATTR_r31 1124 -#define _LOAD_SUPER_ATTR_METHOD_r32 1125 -#define _MAKE_CALLARGS_A_TUPLE_r33 1126 -#define _MAKE_CELL_r00 1127 -#define _MAKE_FUNCTION_r11 1128 -#define _MAKE_WARM_r00 1129 -#define _MAKE_WARM_r11 1130 -#define _MAKE_WARM_r22 1131 -#define _MAKE_WARM_r33 1132 -#define _MAP_ADD_r20 1133 -#define _MATCH_CLASS_r31 1134 -#define _MATCH_KEYS_r23 1135 -#define _MATCH_MAPPING_r02 1136 -#define _MATCH_MAPPING_r12 1137 -#define _MATCH_MAPPING_r23 1138 -#define _MATCH_SEQUENCE_r02 1139 -#define _MATCH_SEQUENCE_r12 1140 -#define _MATCH_SEQUENCE_r23 1141 -#define _MAYBE_EXPAND_METHOD_r00 1142 -#define _MAYBE_EXPAND_METHOD_KW_r11 1143 -#define _MONITOR_CALL_r00 1144 -#define _MONITOR_CALL_KW_r11 1145 -#define _MONITOR_JUMP_BACKWARD_r00 1146 -#define _MONITOR_JUMP_BACKWARD_r11 1147 -#define _MONITOR_JUMP_BACKWARD_r22 1148 -#define _MONITOR_JUMP_BACKWARD_r33 1149 -#define _MONITOR_RESUME_r00 1150 -#define _NOP_r00 1151 -#define _NOP_r11 1152 -#define _NOP_r22 1153 -#define _NOP_r33 1154 -#define _POP_CALL_r20 1155 -#define _POP_CALL_LOAD_CONST_INLINE_BORROW_r21 1156 -#define _POP_CALL_ONE_r30 1157 -#define _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 1158 -#define _POP_CALL_TWO_r30 1159 -#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31 1160 -#define _POP_EXCEPT_r10 1161 -#define _POP_ITER_r20 1162 -#define _POP_JUMP_IF_FALSE_r00 1163 -#define _POP_JUMP_IF_FALSE_r10 1164 -#define _POP_JUMP_IF_FALSE_r21 1165 -#define _POP_JUMP_IF_FALSE_r32 1166 -#define _POP_JUMP_IF_TRUE_r00 1167 -#define _POP_JUMP_IF_TRUE_r10 1168 -#define _POP_JUMP_IF_TRUE_r21 1169 -#define _POP_JUMP_IF_TRUE_r32 1170 -#define _POP_TOP_r10 1171 -#define _POP_TOP_FLOAT_r00 1172 -#define _POP_TOP_FLOAT_r10 1173 -#define _POP_TOP_FLOAT_r21 1174 -#define _POP_TOP_FLOAT_r32 1175 -#define _POP_TOP_INT_r00 1176 -#define _POP_TOP_INT_r10 1177 -#define _POP_TOP_INT_r21 1178 -#define _POP_TOP_INT_r32 1179 -#define _POP_TOP_LOAD_CONST_INLINE_r11 1180 -#define _POP_TOP_LOAD_CONST_INLINE_BORROW_r11 1181 -#define _POP_TOP_NOP_r00 1182 -#define _POP_TOP_NOP_r10 1183 -#define _POP_TOP_NOP_r21 1184 -#define _POP_TOP_NOP_r32 1185 -#define _POP_TOP_UNICODE_r00 1186 -#define _POP_TOP_UNICODE_r10 1187 -#define _POP_TOP_UNICODE_r21 1188 -#define _POP_TOP_UNICODE_r32 1189 -#define _POP_TWO_r20 1190 -#define _POP_TWO_LOAD_CONST_INLINE_BORROW_r21 1191 -#define _PUSH_EXC_INFO_r02 1192 -#define _PUSH_EXC_INFO_r12 1193 -#define _PUSH_EXC_INFO_r23 1194 -#define _PUSH_FRAME_r10 1195 -#define _PUSH_NULL_r01 1196 -#define _PUSH_NULL_r12 1197 -#define _PUSH_NULL_r23 1198 -#define _PUSH_NULL_CONDITIONAL_r00 1199 -#define _PY_FRAME_GENERAL_r01 1200 -#define _PY_FRAME_KW_r11 1201 -#define _QUICKEN_RESUME_r00 1202 -#define _QUICKEN_RESUME_r11 1203 -#define _QUICKEN_RESUME_r22 1204 -#define _QUICKEN_RESUME_r33 1205 -#define _REPLACE_WITH_TRUE_r11 1206 -#define _RESUME_CHECK_r00 1207 -#define _RESUME_CHECK_r11 1208 -#define _RESUME_CHECK_r22 1209 -#define _RESUME_CHECK_r33 1210 -#define _RETURN_GENERATOR_r01 1211 -#define _RETURN_VALUE_r11 1212 -#define _SAVE_RETURN_OFFSET_r00 1213 -#define _SAVE_RETURN_OFFSET_r11 1214 -#define _SAVE_RETURN_OFFSET_r22 1215 -#define _SAVE_RETURN_OFFSET_r33 1216 -#define _SEND_r22 1217 -#define _SEND_GEN_FRAME_r22 1218 -#define _SETUP_ANNOTATIONS_r00 1219 -#define _SET_ADD_r10 1220 -#define _SET_FUNCTION_ATTRIBUTE_r01 1221 -#define _SET_FUNCTION_ATTRIBUTE_r11 1222 -#define _SET_FUNCTION_ATTRIBUTE_r21 1223 -#define _SET_FUNCTION_ATTRIBUTE_r32 1224 -#define _SET_IP_r00 1225 -#define _SET_IP_r11 1226 -#define _SET_IP_r22 1227 -#define _SET_IP_r33 1228 -#define _SET_UPDATE_r10 1229 -#define _SPILL_OR_RELOAD_r01 1230 -#define _SPILL_OR_RELOAD_r02 1231 -#define _SPILL_OR_RELOAD_r03 1232 -#define _SPILL_OR_RELOAD_r10 1233 -#define _SPILL_OR_RELOAD_r12 1234 -#define _SPILL_OR_RELOAD_r13 1235 -#define _SPILL_OR_RELOAD_r20 1236 -#define _SPILL_OR_RELOAD_r21 1237 -#define _SPILL_OR_RELOAD_r23 1238 -#define _SPILL_OR_RELOAD_r30 1239 -#define _SPILL_OR_RELOAD_r31 1240 -#define _SPILL_OR_RELOAD_r32 1241 -#define _START_EXECUTOR_r00 1242 -#define _STORE_ATTR_r20 1243 -#define _STORE_ATTR_INSTANCE_VALUE_r20 1244 -#define _STORE_ATTR_SLOT_r20 1245 -#define _STORE_ATTR_WITH_HINT_r20 1246 -#define _STORE_DEREF_r10 1247 -#define _STORE_FAST_r10 1248 -#define _STORE_FAST_0_r10 1249 -#define _STORE_FAST_1_r10 1250 -#define _STORE_FAST_2_r10 1251 -#define _STORE_FAST_3_r10 1252 -#define _STORE_FAST_4_r10 1253 -#define _STORE_FAST_5_r10 1254 -#define _STORE_FAST_6_r10 1255 -#define _STORE_FAST_7_r10 1256 -#define _STORE_FAST_LOAD_FAST_r11 1257 -#define _STORE_FAST_STORE_FAST_r20 1258 -#define _STORE_GLOBAL_r10 1259 -#define _STORE_NAME_r10 1260 -#define _STORE_SLICE_r30 1261 -#define _STORE_SUBSCR_r30 1262 -#define _STORE_SUBSCR_DICT_r30 1263 -#define _STORE_SUBSCR_LIST_INT_r32 1264 -#define _SWAP_r11 1265 -#define _SWAP_2_r02 1266 -#define _SWAP_2_r12 1267 -#define _SWAP_2_r22 1268 -#define _SWAP_2_r33 1269 -#define _SWAP_3_r03 1270 -#define _SWAP_3_r13 1271 -#define _SWAP_3_r23 1272 -#define _SWAP_3_r33 1273 -#define _TIER2_RESUME_CHECK_r00 1274 -#define _TIER2_RESUME_CHECK_r11 1275 -#define _TIER2_RESUME_CHECK_r22 1276 -#define _TIER2_RESUME_CHECK_r33 1277 -#define _TO_BOOL_r11 1278 -#define _TO_BOOL_BOOL_r01 1279 -#define _TO_BOOL_BOOL_r11 1280 -#define _TO_BOOL_BOOL_r22 1281 -#define _TO_BOOL_BOOL_r33 1282 -#define _TO_BOOL_INT_r11 1283 -#define _TO_BOOL_LIST_r11 1284 -#define _TO_BOOL_NONE_r01 1285 -#define _TO_BOOL_NONE_r11 1286 -#define _TO_BOOL_NONE_r22 1287 -#define _TO_BOOL_NONE_r33 1288 -#define _TO_BOOL_STR_r11 1289 -#define _TRACE_RECORD_r00 1290 -#define _UNARY_INVERT_r11 1291 -#define _UNARY_NEGATIVE_r11 1292 -#define _UNARY_NOT_r01 1293 -#define _UNARY_NOT_r11 1294 -#define _UNARY_NOT_r22 1295 -#define _UNARY_NOT_r33 1296 -#define _UNPACK_EX_r10 1297 -#define _UNPACK_SEQUENCE_r10 1298 -#define _UNPACK_SEQUENCE_LIST_r10 1299 -#define _UNPACK_SEQUENCE_TUPLE_r10 1300 -#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1301 -#define _WITH_EXCEPT_START_r33 1302 -#define _YIELD_VALUE_r11 1303 -#define MAX_UOP_REGS_ID 1303 +#define _CALL_LIST_APPEND_r02 628 +#define _CALL_LIST_APPEND_r12 629 +#define _CALL_LIST_APPEND_r22 630 +#define _CALL_LIST_APPEND_r32 631 +#define _CALL_METHOD_DESCRIPTOR_FAST_r01 632 +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r01 633 +#define _CALL_METHOD_DESCRIPTOR_NOARGS_r01 634 +#define _CALL_METHOD_DESCRIPTOR_O_r01 635 +#define _CALL_NON_PY_GENERAL_r01 636 +#define _CALL_STR_1_r32 637 +#define _CALL_TUPLE_1_r32 638 +#define _CALL_TYPE_1_r31 639 +#define _CHECK_AND_ALLOCATE_OBJECT_r00 640 +#define _CHECK_ATTR_CLASS_r01 641 +#define _CHECK_ATTR_CLASS_r11 642 +#define _CHECK_ATTR_CLASS_r22 643 +#define _CHECK_ATTR_CLASS_r33 644 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r01 645 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r11 646 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r22 647 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r33 648 +#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS_r00 649 +#define _CHECK_EG_MATCH_r22 650 +#define _CHECK_EXC_MATCH_r22 651 +#define _CHECK_FUNCTION_EXACT_ARGS_r00 652 +#define _CHECK_FUNCTION_VERSION_r00 653 +#define _CHECK_FUNCTION_VERSION_INLINE_r00 654 +#define _CHECK_FUNCTION_VERSION_INLINE_r11 655 +#define _CHECK_FUNCTION_VERSION_INLINE_r22 656 +#define _CHECK_FUNCTION_VERSION_INLINE_r33 657 +#define _CHECK_FUNCTION_VERSION_KW_r11 658 +#define _CHECK_IS_NOT_PY_CALLABLE_r00 659 +#define _CHECK_IS_NOT_PY_CALLABLE_KW_r11 660 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r01 661 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r11 662 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r22 663 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r33 664 +#define _CHECK_METHOD_VERSION_r00 665 +#define _CHECK_METHOD_VERSION_KW_r11 666 +#define _CHECK_PEP_523_r00 667 +#define _CHECK_PEP_523_r11 668 +#define _CHECK_PEP_523_r22 669 +#define _CHECK_PEP_523_r33 670 +#define _CHECK_PERIODIC_r00 671 +#define _CHECK_PERIODIC_AT_END_r00 672 +#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM_r00 673 +#define _CHECK_RECURSION_REMAINING_r00 674 +#define _CHECK_RECURSION_REMAINING_r11 675 +#define _CHECK_RECURSION_REMAINING_r22 676 +#define _CHECK_RECURSION_REMAINING_r33 677 +#define _CHECK_STACK_SPACE_r00 678 +#define _CHECK_STACK_SPACE_OPERAND_r00 679 +#define _CHECK_STACK_SPACE_OPERAND_r11 680 +#define _CHECK_STACK_SPACE_OPERAND_r22 681 +#define _CHECK_STACK_SPACE_OPERAND_r33 682 +#define _CHECK_VALIDITY_r00 683 +#define _CHECK_VALIDITY_r11 684 +#define _CHECK_VALIDITY_r22 685 +#define _CHECK_VALIDITY_r33 686 +#define _COLD_DYNAMIC_EXIT_r00 687 +#define _COLD_EXIT_r00 688 +#define _COMPARE_OP_r21 689 +#define _COMPARE_OP_FLOAT_r01 690 +#define _COMPARE_OP_FLOAT_r11 691 +#define _COMPARE_OP_FLOAT_r21 692 +#define _COMPARE_OP_FLOAT_r32 693 +#define _COMPARE_OP_INT_r21 694 +#define _COMPARE_OP_STR_r21 695 +#define _CONTAINS_OP_r21 696 +#define _CONTAINS_OP_DICT_r21 697 +#define _CONTAINS_OP_SET_r21 698 +#define _CONVERT_VALUE_r11 699 +#define _COPY_r01 700 +#define _COPY_1_r02 701 +#define _COPY_1_r12 702 +#define _COPY_1_r23 703 +#define _COPY_2_r03 704 +#define _COPY_2_r13 705 +#define _COPY_2_r23 706 +#define _COPY_3_r03 707 +#define _COPY_3_r13 708 +#define _COPY_3_r23 709 +#define _COPY_3_r33 710 +#define _COPY_FREE_VARS_r00 711 +#define _COPY_FREE_VARS_r11 712 +#define _COPY_FREE_VARS_r22 713 +#define _COPY_FREE_VARS_r33 714 +#define _CREATE_INIT_FRAME_r01 715 +#define _DELETE_ATTR_r10 716 +#define _DELETE_DEREF_r00 717 +#define _DELETE_FAST_r00 718 +#define _DELETE_GLOBAL_r00 719 +#define _DELETE_NAME_r00 720 +#define _DELETE_SUBSCR_r20 721 +#define _DEOPT_r00 722 +#define _DEOPT_r10 723 +#define _DEOPT_r20 724 +#define _DEOPT_r30 725 +#define _DICT_MERGE_r10 726 +#define _DICT_UPDATE_r10 727 +#define _DO_CALL_r01 728 +#define _DO_CALL_FUNCTION_EX_r31 729 +#define _DO_CALL_KW_r11 730 +#define _DYNAMIC_EXIT_r00 731 +#define _DYNAMIC_EXIT_r10 732 +#define _DYNAMIC_EXIT_r20 733 +#define _DYNAMIC_EXIT_r30 734 +#define _END_FOR_r10 735 +#define _END_SEND_r21 736 +#define _ERROR_POP_N_r00 737 +#define _EXIT_INIT_CHECK_r10 738 +#define _EXIT_TRACE_r00 739 +#define _EXIT_TRACE_r10 740 +#define _EXIT_TRACE_r20 741 +#define _EXIT_TRACE_r30 742 +#define _EXPAND_METHOD_r00 743 +#define _EXPAND_METHOD_KW_r11 744 +#define _FATAL_ERROR_r00 745 +#define _FATAL_ERROR_r11 746 +#define _FATAL_ERROR_r22 747 +#define _FATAL_ERROR_r33 748 +#define _FORMAT_SIMPLE_r11 749 +#define _FORMAT_WITH_SPEC_r21 750 +#define _FOR_ITER_r23 751 +#define _FOR_ITER_GEN_FRAME_r23 752 +#define _FOR_ITER_TIER_TWO_r23 753 +#define _GET_AITER_r11 754 +#define _GET_ANEXT_r12 755 +#define _GET_AWAITABLE_r11 756 +#define _GET_ITER_r12 757 +#define _GET_LEN_r12 758 +#define _GET_YIELD_FROM_ITER_r11 759 +#define _GUARD_BINARY_OP_EXTEND_r22 760 +#define _GUARD_CALLABLE_ISINSTANCE_r03 761 +#define _GUARD_CALLABLE_ISINSTANCE_r13 762 +#define _GUARD_CALLABLE_ISINSTANCE_r23 763 +#define _GUARD_CALLABLE_ISINSTANCE_r33 764 +#define _GUARD_CALLABLE_LEN_r03 765 +#define _GUARD_CALLABLE_LEN_r13 766 +#define _GUARD_CALLABLE_LEN_r23 767 +#define _GUARD_CALLABLE_LEN_r33 768 +#define _GUARD_CALLABLE_LIST_APPEND_r03 769 +#define _GUARD_CALLABLE_LIST_APPEND_r13 770 +#define _GUARD_CALLABLE_LIST_APPEND_r23 771 +#define _GUARD_CALLABLE_LIST_APPEND_r33 772 +#define _GUARD_CALLABLE_STR_1_r03 773 +#define _GUARD_CALLABLE_STR_1_r13 774 +#define _GUARD_CALLABLE_STR_1_r23 775 +#define _GUARD_CALLABLE_STR_1_r33 776 +#define _GUARD_CALLABLE_TUPLE_1_r03 777 +#define _GUARD_CALLABLE_TUPLE_1_r13 778 +#define _GUARD_CALLABLE_TUPLE_1_r23 779 +#define _GUARD_CALLABLE_TUPLE_1_r33 780 +#define _GUARD_CALLABLE_TYPE_1_r03 781 +#define _GUARD_CALLABLE_TYPE_1_r13 782 +#define _GUARD_CALLABLE_TYPE_1_r23 783 +#define _GUARD_CALLABLE_TYPE_1_r33 784 +#define _GUARD_DORV_NO_DICT_r01 785 +#define _GUARD_DORV_NO_DICT_r11 786 +#define _GUARD_DORV_NO_DICT_r22 787 +#define _GUARD_DORV_NO_DICT_r33 788 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 789 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 790 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 791 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 792 +#define _GUARD_GLOBALS_VERSION_r00 793 +#define _GUARD_GLOBALS_VERSION_r11 794 +#define _GUARD_GLOBALS_VERSION_r22 795 +#define _GUARD_GLOBALS_VERSION_r33 796 +#define _GUARD_IP_RETURN_GENERATOR_r00 797 +#define _GUARD_IP_RETURN_GENERATOR_r11 798 +#define _GUARD_IP_RETURN_GENERATOR_r22 799 +#define _GUARD_IP_RETURN_GENERATOR_r33 800 +#define _GUARD_IP_RETURN_VALUE_r00 801 +#define _GUARD_IP_RETURN_VALUE_r11 802 +#define _GUARD_IP_RETURN_VALUE_r22 803 +#define _GUARD_IP_RETURN_VALUE_r33 804 +#define _GUARD_IP_YIELD_VALUE_r00 805 +#define _GUARD_IP_YIELD_VALUE_r11 806 +#define _GUARD_IP_YIELD_VALUE_r22 807 +#define _GUARD_IP_YIELD_VALUE_r33 808 +#define _GUARD_IP__PUSH_FRAME_r00 809 +#define _GUARD_IP__PUSH_FRAME_r11 810 +#define _GUARD_IP__PUSH_FRAME_r22 811 +#define _GUARD_IP__PUSH_FRAME_r33 812 +#define _GUARD_IS_FALSE_POP_r00 813 +#define _GUARD_IS_FALSE_POP_r10 814 +#define _GUARD_IS_FALSE_POP_r21 815 +#define _GUARD_IS_FALSE_POP_r32 816 +#define _GUARD_IS_NONE_POP_r00 817 +#define _GUARD_IS_NONE_POP_r10 818 +#define _GUARD_IS_NONE_POP_r21 819 +#define _GUARD_IS_NONE_POP_r32 820 +#define _GUARD_IS_NOT_NONE_POP_r10 821 +#define _GUARD_IS_TRUE_POP_r00 822 +#define _GUARD_IS_TRUE_POP_r10 823 +#define _GUARD_IS_TRUE_POP_r21 824 +#define _GUARD_IS_TRUE_POP_r32 825 +#define _GUARD_KEYS_VERSION_r01 826 +#define _GUARD_KEYS_VERSION_r11 827 +#define _GUARD_KEYS_VERSION_r22 828 +#define _GUARD_KEYS_VERSION_r33 829 +#define _GUARD_NOS_DICT_r02 830 +#define _GUARD_NOS_DICT_r12 831 +#define _GUARD_NOS_DICT_r22 832 +#define _GUARD_NOS_DICT_r33 833 +#define _GUARD_NOS_FLOAT_r02 834 +#define _GUARD_NOS_FLOAT_r12 835 +#define _GUARD_NOS_FLOAT_r22 836 +#define _GUARD_NOS_FLOAT_r33 837 +#define _GUARD_NOS_INT_r02 838 +#define _GUARD_NOS_INT_r12 839 +#define _GUARD_NOS_INT_r22 840 +#define _GUARD_NOS_INT_r33 841 +#define _GUARD_NOS_LIST_r02 842 +#define _GUARD_NOS_LIST_r12 843 +#define _GUARD_NOS_LIST_r22 844 +#define _GUARD_NOS_LIST_r33 845 +#define _GUARD_NOS_NOT_NULL_r02 846 +#define _GUARD_NOS_NOT_NULL_r12 847 +#define _GUARD_NOS_NOT_NULL_r22 848 +#define _GUARD_NOS_NOT_NULL_r33 849 +#define _GUARD_NOS_NULL_r02 850 +#define _GUARD_NOS_NULL_r12 851 +#define _GUARD_NOS_NULL_r22 852 +#define _GUARD_NOS_NULL_r33 853 +#define _GUARD_NOS_OVERFLOWED_r02 854 +#define _GUARD_NOS_OVERFLOWED_r12 855 +#define _GUARD_NOS_OVERFLOWED_r22 856 +#define _GUARD_NOS_OVERFLOWED_r33 857 +#define _GUARD_NOS_TUPLE_r02 858 +#define _GUARD_NOS_TUPLE_r12 859 +#define _GUARD_NOS_TUPLE_r22 860 +#define _GUARD_NOS_TUPLE_r33 861 +#define _GUARD_NOS_UNICODE_r02 862 +#define _GUARD_NOS_UNICODE_r12 863 +#define _GUARD_NOS_UNICODE_r22 864 +#define _GUARD_NOS_UNICODE_r33 865 +#define _GUARD_NOT_EXHAUSTED_LIST_r02 866 +#define _GUARD_NOT_EXHAUSTED_LIST_r12 867 +#define _GUARD_NOT_EXHAUSTED_LIST_r22 868 +#define _GUARD_NOT_EXHAUSTED_LIST_r33 869 +#define _GUARD_NOT_EXHAUSTED_RANGE_r02 870 +#define _GUARD_NOT_EXHAUSTED_RANGE_r12 871 +#define _GUARD_NOT_EXHAUSTED_RANGE_r22 872 +#define _GUARD_NOT_EXHAUSTED_RANGE_r33 873 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 874 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 875 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 876 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 877 +#define _GUARD_THIRD_NULL_r03 878 +#define _GUARD_THIRD_NULL_r13 879 +#define _GUARD_THIRD_NULL_r23 880 +#define _GUARD_THIRD_NULL_r33 881 +#define _GUARD_TOS_ANY_SET_r01 882 +#define _GUARD_TOS_ANY_SET_r11 883 +#define _GUARD_TOS_ANY_SET_r22 884 +#define _GUARD_TOS_ANY_SET_r33 885 +#define _GUARD_TOS_DICT_r01 886 +#define _GUARD_TOS_DICT_r11 887 +#define _GUARD_TOS_DICT_r22 888 +#define _GUARD_TOS_DICT_r33 889 +#define _GUARD_TOS_FLOAT_r01 890 +#define _GUARD_TOS_FLOAT_r11 891 +#define _GUARD_TOS_FLOAT_r22 892 +#define _GUARD_TOS_FLOAT_r33 893 +#define _GUARD_TOS_INT_r01 894 +#define _GUARD_TOS_INT_r11 895 +#define _GUARD_TOS_INT_r22 896 +#define _GUARD_TOS_INT_r33 897 +#define _GUARD_TOS_LIST_r01 898 +#define _GUARD_TOS_LIST_r11 899 +#define _GUARD_TOS_LIST_r22 900 +#define _GUARD_TOS_LIST_r33 901 +#define _GUARD_TOS_OVERFLOWED_r01 902 +#define _GUARD_TOS_OVERFLOWED_r11 903 +#define _GUARD_TOS_OVERFLOWED_r22 904 +#define _GUARD_TOS_OVERFLOWED_r33 905 +#define _GUARD_TOS_SLICE_r01 906 +#define _GUARD_TOS_SLICE_r11 907 +#define _GUARD_TOS_SLICE_r22 908 +#define _GUARD_TOS_SLICE_r33 909 +#define _GUARD_TOS_TUPLE_r01 910 +#define _GUARD_TOS_TUPLE_r11 911 +#define _GUARD_TOS_TUPLE_r22 912 +#define _GUARD_TOS_TUPLE_r33 913 +#define _GUARD_TOS_UNICODE_r01 914 +#define _GUARD_TOS_UNICODE_r11 915 +#define _GUARD_TOS_UNICODE_r22 916 +#define _GUARD_TOS_UNICODE_r33 917 +#define _GUARD_TYPE_VERSION_r01 918 +#define _GUARD_TYPE_VERSION_r11 919 +#define _GUARD_TYPE_VERSION_r22 920 +#define _GUARD_TYPE_VERSION_r33 921 +#define _GUARD_TYPE_VERSION_AND_LOCK_r01 922 +#define _GUARD_TYPE_VERSION_AND_LOCK_r11 923 +#define _GUARD_TYPE_VERSION_AND_LOCK_r22 924 +#define _GUARD_TYPE_VERSION_AND_LOCK_r33 925 +#define _HANDLE_PENDING_AND_DEOPT_r00 926 +#define _HANDLE_PENDING_AND_DEOPT_r10 927 +#define _HANDLE_PENDING_AND_DEOPT_r20 928 +#define _HANDLE_PENDING_AND_DEOPT_r30 929 +#define _IMPORT_FROM_r12 930 +#define _IMPORT_NAME_r21 931 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 932 +#define _INIT_CALL_PY_EXACT_ARGS_r01 933 +#define _INIT_CALL_PY_EXACT_ARGS_0_r01 934 +#define _INIT_CALL_PY_EXACT_ARGS_1_r01 935 +#define _INIT_CALL_PY_EXACT_ARGS_2_r01 936 +#define _INIT_CALL_PY_EXACT_ARGS_3_r01 937 +#define _INIT_CALL_PY_EXACT_ARGS_4_r01 938 +#define _INSERT_NULL_r10 939 +#define _INSTRUMENTED_FOR_ITER_r23 940 +#define _INSTRUMENTED_INSTRUCTION_r00 941 +#define _INSTRUMENTED_JUMP_FORWARD_r00 942 +#define _INSTRUMENTED_JUMP_FORWARD_r11 943 +#define _INSTRUMENTED_JUMP_FORWARD_r22 944 +#define _INSTRUMENTED_JUMP_FORWARD_r33 945 +#define _INSTRUMENTED_LINE_r00 946 +#define _INSTRUMENTED_NOT_TAKEN_r00 947 +#define _INSTRUMENTED_NOT_TAKEN_r11 948 +#define _INSTRUMENTED_NOT_TAKEN_r22 949 +#define _INSTRUMENTED_NOT_TAKEN_r33 950 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 951 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 952 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 953 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 954 +#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 955 +#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 956 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 957 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 958 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 959 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 960 +#define _IS_NONE_r11 961 +#define _IS_OP_r21 962 +#define _ITER_CHECK_LIST_r02 963 +#define _ITER_CHECK_LIST_r12 964 +#define _ITER_CHECK_LIST_r22 965 +#define _ITER_CHECK_LIST_r33 966 +#define _ITER_CHECK_RANGE_r02 967 +#define _ITER_CHECK_RANGE_r12 968 +#define _ITER_CHECK_RANGE_r22 969 +#define _ITER_CHECK_RANGE_r33 970 +#define _ITER_CHECK_TUPLE_r02 971 +#define _ITER_CHECK_TUPLE_r12 972 +#define _ITER_CHECK_TUPLE_r22 973 +#define _ITER_CHECK_TUPLE_r33 974 +#define _ITER_JUMP_LIST_r02 975 +#define _ITER_JUMP_LIST_r12 976 +#define _ITER_JUMP_LIST_r22 977 +#define _ITER_JUMP_LIST_r33 978 +#define _ITER_JUMP_RANGE_r02 979 +#define _ITER_JUMP_RANGE_r12 980 +#define _ITER_JUMP_RANGE_r22 981 +#define _ITER_JUMP_RANGE_r33 982 +#define _ITER_JUMP_TUPLE_r02 983 +#define _ITER_JUMP_TUPLE_r12 984 +#define _ITER_JUMP_TUPLE_r22 985 +#define _ITER_JUMP_TUPLE_r33 986 +#define _ITER_NEXT_LIST_r23 987 +#define _ITER_NEXT_LIST_TIER_TWO_r23 988 +#define _ITER_NEXT_RANGE_r03 989 +#define _ITER_NEXT_RANGE_r13 990 +#define _ITER_NEXT_RANGE_r23 991 +#define _ITER_NEXT_TUPLE_r03 992 +#define _ITER_NEXT_TUPLE_r13 993 +#define _ITER_NEXT_TUPLE_r23 994 +#define _JUMP_BACKWARD_NO_INTERRUPT_r00 995 +#define _JUMP_BACKWARD_NO_INTERRUPT_r11 996 +#define _JUMP_BACKWARD_NO_INTERRUPT_r22 997 +#define _JUMP_BACKWARD_NO_INTERRUPT_r33 998 +#define _JUMP_TO_TOP_r00 999 +#define _LIST_APPEND_r10 1000 +#define _LIST_EXTEND_r10 1001 +#define _LOAD_ATTR_r10 1002 +#define _LOAD_ATTR_CLASS_r11 1003 +#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_r11 1004 +#define _LOAD_ATTR_INSTANCE_VALUE_r11 1005 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 1006 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 1007 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 1008 +#define _LOAD_ATTR_METHOD_NO_DICT_r02 1009 +#define _LOAD_ATTR_METHOD_NO_DICT_r12 1010 +#define _LOAD_ATTR_METHOD_NO_DICT_r23 1011 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 1012 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 1013 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 1014 +#define _LOAD_ATTR_MODULE_r11 1015 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 1016 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1017 +#define _LOAD_ATTR_PROPERTY_FRAME_r11 1018 +#define _LOAD_ATTR_SLOT_r11 1019 +#define _LOAD_ATTR_WITH_HINT_r11 1020 +#define _LOAD_BUILD_CLASS_r01 1021 +#define _LOAD_BYTECODE_r00 1022 +#define _LOAD_COMMON_CONSTANT_r01 1023 +#define _LOAD_COMMON_CONSTANT_r12 1024 +#define _LOAD_COMMON_CONSTANT_r23 1025 +#define _LOAD_CONST_r01 1026 +#define _LOAD_CONST_r12 1027 +#define _LOAD_CONST_r23 1028 +#define _LOAD_CONST_INLINE_r01 1029 +#define _LOAD_CONST_INLINE_r12 1030 +#define _LOAD_CONST_INLINE_r23 1031 +#define _LOAD_CONST_INLINE_BORROW_r01 1032 +#define _LOAD_CONST_INLINE_BORROW_r12 1033 +#define _LOAD_CONST_INLINE_BORROW_r23 1034 +#define _LOAD_CONST_UNDER_INLINE_r02 1035 +#define _LOAD_CONST_UNDER_INLINE_r12 1036 +#define _LOAD_CONST_UNDER_INLINE_r23 1037 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r02 1038 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r12 1039 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r23 1040 +#define _LOAD_DEREF_r01 1041 +#define _LOAD_FAST_r01 1042 +#define _LOAD_FAST_r12 1043 +#define _LOAD_FAST_r23 1044 +#define _LOAD_FAST_0_r01 1045 +#define _LOAD_FAST_0_r12 1046 +#define _LOAD_FAST_0_r23 1047 +#define _LOAD_FAST_1_r01 1048 +#define _LOAD_FAST_1_r12 1049 +#define _LOAD_FAST_1_r23 1050 +#define _LOAD_FAST_2_r01 1051 +#define _LOAD_FAST_2_r12 1052 +#define _LOAD_FAST_2_r23 1053 +#define _LOAD_FAST_3_r01 1054 +#define _LOAD_FAST_3_r12 1055 +#define _LOAD_FAST_3_r23 1056 +#define _LOAD_FAST_4_r01 1057 +#define _LOAD_FAST_4_r12 1058 +#define _LOAD_FAST_4_r23 1059 +#define _LOAD_FAST_5_r01 1060 +#define _LOAD_FAST_5_r12 1061 +#define _LOAD_FAST_5_r23 1062 +#define _LOAD_FAST_6_r01 1063 +#define _LOAD_FAST_6_r12 1064 +#define _LOAD_FAST_6_r23 1065 +#define _LOAD_FAST_7_r01 1066 +#define _LOAD_FAST_7_r12 1067 +#define _LOAD_FAST_7_r23 1068 +#define _LOAD_FAST_AND_CLEAR_r01 1069 +#define _LOAD_FAST_AND_CLEAR_r12 1070 +#define _LOAD_FAST_AND_CLEAR_r23 1071 +#define _LOAD_FAST_BORROW_r01 1072 +#define _LOAD_FAST_BORROW_r12 1073 +#define _LOAD_FAST_BORROW_r23 1074 +#define _LOAD_FAST_BORROW_0_r01 1075 +#define _LOAD_FAST_BORROW_0_r12 1076 +#define _LOAD_FAST_BORROW_0_r23 1077 +#define _LOAD_FAST_BORROW_1_r01 1078 +#define _LOAD_FAST_BORROW_1_r12 1079 +#define _LOAD_FAST_BORROW_1_r23 1080 +#define _LOAD_FAST_BORROW_2_r01 1081 +#define _LOAD_FAST_BORROW_2_r12 1082 +#define _LOAD_FAST_BORROW_2_r23 1083 +#define _LOAD_FAST_BORROW_3_r01 1084 +#define _LOAD_FAST_BORROW_3_r12 1085 +#define _LOAD_FAST_BORROW_3_r23 1086 +#define _LOAD_FAST_BORROW_4_r01 1087 +#define _LOAD_FAST_BORROW_4_r12 1088 +#define _LOAD_FAST_BORROW_4_r23 1089 +#define _LOAD_FAST_BORROW_5_r01 1090 +#define _LOAD_FAST_BORROW_5_r12 1091 +#define _LOAD_FAST_BORROW_5_r23 1092 +#define _LOAD_FAST_BORROW_6_r01 1093 +#define _LOAD_FAST_BORROW_6_r12 1094 +#define _LOAD_FAST_BORROW_6_r23 1095 +#define _LOAD_FAST_BORROW_7_r01 1096 +#define _LOAD_FAST_BORROW_7_r12 1097 +#define _LOAD_FAST_BORROW_7_r23 1098 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1099 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1100 +#define _LOAD_FAST_CHECK_r01 1101 +#define _LOAD_FAST_LOAD_FAST_r02 1102 +#define _LOAD_FAST_LOAD_FAST_r13 1103 +#define _LOAD_FROM_DICT_OR_DEREF_r11 1104 +#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1105 +#define _LOAD_GLOBAL_r00 1106 +#define _LOAD_GLOBAL_BUILTINS_r01 1107 +#define _LOAD_GLOBAL_MODULE_r01 1108 +#define _LOAD_LOCALS_r01 1109 +#define _LOAD_NAME_r01 1110 +#define _LOAD_SMALL_INT_r01 1111 +#define _LOAD_SMALL_INT_r12 1112 +#define _LOAD_SMALL_INT_r23 1113 +#define _LOAD_SMALL_INT_0_r01 1114 +#define _LOAD_SMALL_INT_0_r12 1115 +#define _LOAD_SMALL_INT_0_r23 1116 +#define _LOAD_SMALL_INT_1_r01 1117 +#define _LOAD_SMALL_INT_1_r12 1118 +#define _LOAD_SMALL_INT_1_r23 1119 +#define _LOAD_SMALL_INT_2_r01 1120 +#define _LOAD_SMALL_INT_2_r12 1121 +#define _LOAD_SMALL_INT_2_r23 1122 +#define _LOAD_SMALL_INT_3_r01 1123 +#define _LOAD_SMALL_INT_3_r12 1124 +#define _LOAD_SMALL_INT_3_r23 1125 +#define _LOAD_SPECIAL_r00 1126 +#define _LOAD_SUPER_ATTR_ATTR_r31 1127 +#define _LOAD_SUPER_ATTR_METHOD_r32 1128 +#define _MAKE_CALLARGS_A_TUPLE_r33 1129 +#define _MAKE_CELL_r00 1130 +#define _MAKE_FUNCTION_r11 1131 +#define _MAKE_WARM_r00 1132 +#define _MAKE_WARM_r11 1133 +#define _MAKE_WARM_r22 1134 +#define _MAKE_WARM_r33 1135 +#define _MAP_ADD_r20 1136 +#define _MATCH_CLASS_r31 1137 +#define _MATCH_KEYS_r23 1138 +#define _MATCH_MAPPING_r02 1139 +#define _MATCH_MAPPING_r12 1140 +#define _MATCH_MAPPING_r23 1141 +#define _MATCH_SEQUENCE_r02 1142 +#define _MATCH_SEQUENCE_r12 1143 +#define _MATCH_SEQUENCE_r23 1144 +#define _MAYBE_EXPAND_METHOD_r00 1145 +#define _MAYBE_EXPAND_METHOD_KW_r11 1146 +#define _MONITOR_CALL_r00 1147 +#define _MONITOR_CALL_KW_r11 1148 +#define _MONITOR_JUMP_BACKWARD_r00 1149 +#define _MONITOR_JUMP_BACKWARD_r11 1150 +#define _MONITOR_JUMP_BACKWARD_r22 1151 +#define _MONITOR_JUMP_BACKWARD_r33 1152 +#define _MONITOR_RESUME_r00 1153 +#define _NOP_r00 1154 +#define _NOP_r11 1155 +#define _NOP_r22 1156 +#define _NOP_r33 1157 +#define _POP_CALL_r20 1158 +#define _POP_CALL_LOAD_CONST_INLINE_BORROW_r21 1159 +#define _POP_CALL_ONE_r30 1160 +#define _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 1161 +#define _POP_CALL_TWO_r30 1162 +#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31 1163 +#define _POP_EXCEPT_r10 1164 +#define _POP_ITER_r20 1165 +#define _POP_JUMP_IF_FALSE_r00 1166 +#define _POP_JUMP_IF_FALSE_r10 1167 +#define _POP_JUMP_IF_FALSE_r21 1168 +#define _POP_JUMP_IF_FALSE_r32 1169 +#define _POP_JUMP_IF_TRUE_r00 1170 +#define _POP_JUMP_IF_TRUE_r10 1171 +#define _POP_JUMP_IF_TRUE_r21 1172 +#define _POP_JUMP_IF_TRUE_r32 1173 +#define _POP_TOP_r10 1174 +#define _POP_TOP_FLOAT_r00 1175 +#define _POP_TOP_FLOAT_r10 1176 +#define _POP_TOP_FLOAT_r21 1177 +#define _POP_TOP_FLOAT_r32 1178 +#define _POP_TOP_INT_r00 1179 +#define _POP_TOP_INT_r10 1180 +#define _POP_TOP_INT_r21 1181 +#define _POP_TOP_INT_r32 1182 +#define _POP_TOP_LOAD_CONST_INLINE_r11 1183 +#define _POP_TOP_LOAD_CONST_INLINE_BORROW_r11 1184 +#define _POP_TOP_NOP_r00 1185 +#define _POP_TOP_NOP_r10 1186 +#define _POP_TOP_NOP_r21 1187 +#define _POP_TOP_NOP_r32 1188 +#define _POP_TOP_UNICODE_r00 1189 +#define _POP_TOP_UNICODE_r10 1190 +#define _POP_TOP_UNICODE_r21 1191 +#define _POP_TOP_UNICODE_r32 1192 +#define _POP_TWO_r20 1193 +#define _POP_TWO_LOAD_CONST_INLINE_BORROW_r21 1194 +#define _PUSH_EXC_INFO_r02 1195 +#define _PUSH_EXC_INFO_r12 1196 +#define _PUSH_EXC_INFO_r23 1197 +#define _PUSH_FRAME_r10 1198 +#define _PUSH_NULL_r01 1199 +#define _PUSH_NULL_r12 1200 +#define _PUSH_NULL_r23 1201 +#define _PUSH_NULL_CONDITIONAL_r00 1202 +#define _PY_FRAME_GENERAL_r01 1203 +#define _PY_FRAME_KW_r11 1204 +#define _QUICKEN_RESUME_r00 1205 +#define _QUICKEN_RESUME_r11 1206 +#define _QUICKEN_RESUME_r22 1207 +#define _QUICKEN_RESUME_r33 1208 +#define _REPLACE_WITH_TRUE_r11 1209 +#define _RESUME_CHECK_r00 1210 +#define _RESUME_CHECK_r11 1211 +#define _RESUME_CHECK_r22 1212 +#define _RESUME_CHECK_r33 1213 +#define _RETURN_GENERATOR_r01 1214 +#define _RETURN_VALUE_r11 1215 +#define _SAVE_RETURN_OFFSET_r00 1216 +#define _SAVE_RETURN_OFFSET_r11 1217 +#define _SAVE_RETURN_OFFSET_r22 1218 +#define _SAVE_RETURN_OFFSET_r33 1219 +#define _SEND_r22 1220 +#define _SEND_GEN_FRAME_r22 1221 +#define _SETUP_ANNOTATIONS_r00 1222 +#define _SET_ADD_r10 1223 +#define _SET_FUNCTION_ATTRIBUTE_r01 1224 +#define _SET_FUNCTION_ATTRIBUTE_r11 1225 +#define _SET_FUNCTION_ATTRIBUTE_r21 1226 +#define _SET_FUNCTION_ATTRIBUTE_r32 1227 +#define _SET_IP_r00 1228 +#define _SET_IP_r11 1229 +#define _SET_IP_r22 1230 +#define _SET_IP_r33 1231 +#define _SET_UPDATE_r10 1232 +#define _SPILL_OR_RELOAD_r01 1233 +#define _SPILL_OR_RELOAD_r02 1234 +#define _SPILL_OR_RELOAD_r03 1235 +#define _SPILL_OR_RELOAD_r10 1236 +#define _SPILL_OR_RELOAD_r12 1237 +#define _SPILL_OR_RELOAD_r13 1238 +#define _SPILL_OR_RELOAD_r20 1239 +#define _SPILL_OR_RELOAD_r21 1240 +#define _SPILL_OR_RELOAD_r23 1241 +#define _SPILL_OR_RELOAD_r30 1242 +#define _SPILL_OR_RELOAD_r31 1243 +#define _SPILL_OR_RELOAD_r32 1244 +#define _START_EXECUTOR_r00 1245 +#define _STORE_ATTR_r20 1246 +#define _STORE_ATTR_INSTANCE_VALUE_r20 1247 +#define _STORE_ATTR_SLOT_r20 1248 +#define _STORE_ATTR_WITH_HINT_r20 1249 +#define _STORE_DEREF_r10 1250 +#define _STORE_FAST_r10 1251 +#define _STORE_FAST_0_r10 1252 +#define _STORE_FAST_1_r10 1253 +#define _STORE_FAST_2_r10 1254 +#define _STORE_FAST_3_r10 1255 +#define _STORE_FAST_4_r10 1256 +#define _STORE_FAST_5_r10 1257 +#define _STORE_FAST_6_r10 1258 +#define _STORE_FAST_7_r10 1259 +#define _STORE_FAST_LOAD_FAST_r11 1260 +#define _STORE_FAST_STORE_FAST_r20 1261 +#define _STORE_GLOBAL_r10 1262 +#define _STORE_NAME_r10 1263 +#define _STORE_SLICE_r30 1264 +#define _STORE_SUBSCR_r30 1265 +#define _STORE_SUBSCR_DICT_r31 1266 +#define _STORE_SUBSCR_LIST_INT_r32 1267 +#define _SWAP_r11 1268 +#define _SWAP_2_r02 1269 +#define _SWAP_2_r12 1270 +#define _SWAP_2_r22 1271 +#define _SWAP_2_r33 1272 +#define _SWAP_3_r03 1273 +#define _SWAP_3_r13 1274 +#define _SWAP_3_r23 1275 +#define _SWAP_3_r33 1276 +#define _TIER2_RESUME_CHECK_r00 1277 +#define _TIER2_RESUME_CHECK_r11 1278 +#define _TIER2_RESUME_CHECK_r22 1279 +#define _TIER2_RESUME_CHECK_r33 1280 +#define _TO_BOOL_r11 1281 +#define _TO_BOOL_BOOL_r01 1282 +#define _TO_BOOL_BOOL_r11 1283 +#define _TO_BOOL_BOOL_r22 1284 +#define _TO_BOOL_BOOL_r33 1285 +#define _TO_BOOL_INT_r11 1286 +#define _TO_BOOL_LIST_r11 1287 +#define _TO_BOOL_NONE_r01 1288 +#define _TO_BOOL_NONE_r11 1289 +#define _TO_BOOL_NONE_r22 1290 +#define _TO_BOOL_NONE_r33 1291 +#define _TO_BOOL_STR_r11 1292 +#define _TRACE_RECORD_r00 1293 +#define _UNARY_INVERT_r11 1294 +#define _UNARY_NEGATIVE_r11 1295 +#define _UNARY_NOT_r01 1296 +#define _UNARY_NOT_r11 1297 +#define _UNARY_NOT_r22 1298 +#define _UNARY_NOT_r33 1299 +#define _UNPACK_EX_r10 1300 +#define _UNPACK_SEQUENCE_r10 1301 +#define _UNPACK_SEQUENCE_LIST_r10 1302 +#define _UNPACK_SEQUENCE_TUPLE_r10 1303 +#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1304 +#define _WITH_EXCEPT_START_r33 1305 +#define _YIELD_VALUE_r11 1306 +#define MAX_UOP_REGS_ID 1306 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 5fa375a8ce6b4a..ec47c526ff122d 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -289,7 +289,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_GUARD_CALLABLE_ISINSTANCE] = HAS_DEOPT_FLAG, [_CALL_ISINSTANCE] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG, [_GUARD_CALLABLE_LIST_APPEND] = HAS_DEOPT_FLAG, - [_CALL_LIST_APPEND] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_LIST_APPEND] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG, [_CALL_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CALL_METHOD_DESCRIPTOR_NOARGS] = HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, @@ -1274,7 +1274,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { { -1, -1, -1 }, { -1, -1, -1 }, { -1, -1, -1 }, - { 0, 0, _STORE_SUBSCR_DICT_r30 }, + { 1, 0, _STORE_SUBSCR_DICT_r31 }, }, }, [_DELETE_SUBSCR] = { @@ -2655,12 +2655,12 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { }, }, [_CALL_LIST_APPEND] = { - .best = { 3, 3, 3, 3 }, + .best = { 0, 1, 2, 3 }, .entries = { - { -1, -1, -1 }, - { -1, -1, -1 }, - { -1, -1, -1 }, - { 0, 3, _CALL_LIST_APPEND_r30 }, + { 2, 0, _CALL_LIST_APPEND_r02 }, + { 2, 1, _CALL_LIST_APPEND_r12 }, + { 2, 2, _CALL_LIST_APPEND_r22 }, + { 2, 3, _CALL_LIST_APPEND_r32 }, }, }, [_CALL_METHOD_DESCRIPTOR_O] = { @@ -3499,7 +3499,7 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_SET_ADD_r10] = _SET_ADD, [_STORE_SUBSCR_r30] = _STORE_SUBSCR, [_STORE_SUBSCR_LIST_INT_r32] = _STORE_SUBSCR_LIST_INT, - [_STORE_SUBSCR_DICT_r30] = _STORE_SUBSCR_DICT, + [_STORE_SUBSCR_DICT_r31] = _STORE_SUBSCR_DICT, [_DELETE_SUBSCR_r20] = _DELETE_SUBSCR, [_CALL_INTRINSIC_1_r11] = _CALL_INTRINSIC_1, [_CALL_INTRINSIC_2_r21] = _CALL_INTRINSIC_2, @@ -3761,7 +3761,10 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_GUARD_CALLABLE_LIST_APPEND_r13] = _GUARD_CALLABLE_LIST_APPEND, [_GUARD_CALLABLE_LIST_APPEND_r23] = _GUARD_CALLABLE_LIST_APPEND, [_GUARD_CALLABLE_LIST_APPEND_r33] = _GUARD_CALLABLE_LIST_APPEND, - [_CALL_LIST_APPEND_r30] = _CALL_LIST_APPEND, + [_CALL_LIST_APPEND_r02] = _CALL_LIST_APPEND, + [_CALL_LIST_APPEND_r12] = _CALL_LIST_APPEND, + [_CALL_LIST_APPEND_r22] = _CALL_LIST_APPEND, + [_CALL_LIST_APPEND_r32] = _CALL_LIST_APPEND, [_CALL_METHOD_DESCRIPTOR_O_r01] = _CALL_METHOD_DESCRIPTOR_O, [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r01] = _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, [_CALL_METHOD_DESCRIPTOR_NOARGS_r01] = _CALL_METHOD_DESCRIPTOR_NOARGS, @@ -4044,7 +4047,10 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_CALL_LEN] = "_CALL_LEN", [_CALL_LEN_r33] = "_CALL_LEN_r33", [_CALL_LIST_APPEND] = "_CALL_LIST_APPEND", - [_CALL_LIST_APPEND_r30] = "_CALL_LIST_APPEND_r30", + [_CALL_LIST_APPEND_r02] = "_CALL_LIST_APPEND_r02", + [_CALL_LIST_APPEND_r12] = "_CALL_LIST_APPEND_r12", + [_CALL_LIST_APPEND_r22] = "_CALL_LIST_APPEND_r22", + [_CALL_LIST_APPEND_r32] = "_CALL_LIST_APPEND_r32", [_CALL_METHOD_DESCRIPTOR_FAST] = "_CALL_METHOD_DESCRIPTOR_FAST", [_CALL_METHOD_DESCRIPTOR_FAST_r01] = "_CALL_METHOD_DESCRIPTOR_FAST_r01", [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = "_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", @@ -4867,7 +4873,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_STORE_SUBSCR] = "_STORE_SUBSCR", [_STORE_SUBSCR_r30] = "_STORE_SUBSCR_r30", [_STORE_SUBSCR_DICT] = "_STORE_SUBSCR_DICT", - [_STORE_SUBSCR_DICT_r30] = "_STORE_SUBSCR_DICT_r30", + [_STORE_SUBSCR_DICT_r31] = "_STORE_SUBSCR_DICT_r31", [_STORE_SUBSCR_LIST_INT] = "_STORE_SUBSCR_LIST_INT", [_STORE_SUBSCR_LIST_INT_r32] = "_STORE_SUBSCR_LIST_INT_r32", [_SWAP] = "_SWAP", diff --git a/InternalDocs/README.md b/InternalDocs/README.md index 80744f30a5b591..3e8ab442315753 100644 --- a/InternalDocs/README.md +++ b/InternalDocs/README.md @@ -11,6 +11,11 @@ it is not, please report that through the [issue tracker](https://github.com/python/cpython/issues). +General Resources +--- + +- [Source Code Structure](structure.md) + Compiling Python Source Code --- diff --git a/InternalDocs/stackrefs.md b/InternalDocs/stackrefs.md index 2d8810262d45f7..5774be9c56d363 100644 --- a/InternalDocs/stackrefs.md +++ b/InternalDocs/stackrefs.md @@ -64,7 +64,6 @@ these values. Type checks use `PyStackRef_IsTaggedInt` and `PyStackRef_LongCheck ## Free threading considerations -With `Py_GIL_DISABLED`, `Py_TAG_DEFERRED` is an alias for `Py_TAG_REFCNT`. Objects that support deferred reference counting can be pushed to the evaluation stack and stored in local variables without directly incrementing the reference count because they are only freed during cyclic garbage collection. This avoids diff --git a/InternalDocs/structure.md b/InternalDocs/structure.md new file mode 100644 index 00000000000000..75c8476aa0ad98 --- /dev/null +++ b/InternalDocs/structure.md @@ -0,0 +1,40 @@ +# CPython source code + +This section gives an overview of CPython's code structure and provides +a summary of file locations for modules and built-ins. + + +## Source code layout + +For a Python module, the typical layout is: + +* `Lib/.py` +* `Modules/_.c` (if there's also a C accelerator module) +* `Lib/test/test_.py` +* `Doc/library/.rst` + +For an extension module, the typical layout is: + +* `Modules/module.c` +* `Lib/test/test_.py` +* `Doc/library/.rst` + +For builtin types, the typical layout is: + +* `Objects/object.c` +* `Lib/test/test_.py` +* [`Doc/library/stdtypes.rst`](../Doc/library/stdtypes.rst) + +For builtin functions, the typical layout is: + +* [`Python/bltinmodule.c`](../Python/bltinmodule.c) +* [`Lib/test/test_builtin.py`](../Lib/test/test_builtin.py) +* [`Doc/library/functions.rst`](../Doc/library/functions.rst) + +Some exceptions to these layouts are: + +* built-in type `int` is at [`Objects/longobject.c`](../Objects/longobject.c) +* built-in type `str` is at [`Objects/unicodeobject.c`](../Objects/unicodeobject.c) +* built-in module `sys` is at [`Python/sysmodule.c`](../Python/sysmodule.c) +* built-in module `marshal` is at [`Python/marshal.c`](../Python/marshal.c) +* Windows-only module `winreg` is at [`PC/winreg.c`](../PC/winreg.c) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index ab5b656e6e5d6c..aec92f3aee2472 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -5,12 +5,9 @@ import sysconfig as _sysconfig import types as _types -__version__ = "1.1.0" - from _ctypes import Union, Structure, Array from _ctypes import _Pointer from _ctypes import CFuncPtr as _CFuncPtr -from _ctypes import __version__ as _ctypes_version from _ctypes import RTLD_LOCAL, RTLD_GLOBAL from _ctypes import ArgumentError from _ctypes import SIZEOF_TIME_T @@ -18,9 +15,6 @@ from struct import calcsize as _calcsize -if __version__ != _ctypes_version: - raise Exception("Version number mismatch", __version__, _ctypes_version) - if _os.name == "nt": from _ctypes import COMError, CopyComPointer, FormatError @@ -673,3 +667,12 @@ def DllCanUnloadNow(): raise SystemError(f"Unexpected sizeof(time_t): {SIZEOF_TIME_T=}") _reset_cache() + + +def __getattr__(name): + if name == "__version__": + from warnings import _deprecated + + _deprecated("__version__", remove=(3, 20)) + return "1.1.0" # Do not change + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/profiling/sampling/_child_monitor.py b/Lib/profiling/sampling/_child_monitor.py new file mode 100644 index 00000000000000..e06c550d938b13 --- /dev/null +++ b/Lib/profiling/sampling/_child_monitor.py @@ -0,0 +1,279 @@ +""" +Child process monitoring for the sampling profiler. + +This module monitors a target process for child process creation and spawns +separate profiler instances for each discovered child. +""" + +import subprocess +import sys +import threading +import time + +import _remote_debugging + +# Polling interval for child process discovery +_CHILD_POLL_INTERVAL_SEC = 0.1 + +# Default timeout for waiting on child profilers +_DEFAULT_WAIT_TIMEOUT = 30.0 + +# Maximum number of child profilers to spawn (prevents resource exhaustion) +_MAX_CHILD_PROFILERS = 100 + +# Interval for cleaning up completed profilers (in polling cycles) +_CLEANUP_INTERVAL_CYCLES = 10 + + +def get_child_pids(pid, recursive=True): + """ + Get all child process IDs of the given process. + + Args: + pid: Process ID of the parent process + recursive: If True, return all descendants (children, grandchildren, etc.) + + Returns: + List of child PIDs + """ + return _remote_debugging.get_child_pids(pid, recursive=recursive) + + +def is_python_process(pid): + """ + Check if a process is a Python process. + + Args: + pid: Process ID to check + + Returns: + bool: True if the process appears to be a Python process, False otherwise + """ + return _remote_debugging.is_python_process(pid) + + +class ChildProcessMonitor: + """ + Monitors a target process for child processes and spawns profilers for them. + + Use as a context manager: + with ChildProcessMonitor(pid, cli_args, output_pattern) as monitor: + # monitoring runs here + monitor.wait_for_profilers() # optional: wait before cleanup + # cleanup happens automatically + """ + + def __init__(self, pid, cli_args, output_pattern): + """ + Initialize the child process monitor. + + Args: + pid: Parent process ID to monitor + cli_args: CLI arguments to pass to child profilers + output_pattern: Pattern for output files (format string with {pid}) + """ + self.parent_pid = pid + self.cli_args = cli_args + self.output_pattern = output_pattern + + self._known_children = set() + self._spawned_profilers = [] + self._lock = threading.Lock() + self._stop_event = threading.Event() + self._monitor_thread = None + self._poll_count = 0 + + def __enter__(self): + self._monitor_thread = threading.Thread( + target=self._monitor_loop, + daemon=True, + name=f"child-monitor-{self.parent_pid}", + ) + self._monitor_thread.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self._stop_event.set() + if self._monitor_thread is not None: + self._monitor_thread.join(timeout=2.0) + if self._monitor_thread.is_alive(): + print( + "Warning: Monitor thread did not stop cleanly", + file=sys.stderr, + ) + + # Wait for child profilers to complete naturally + self.wait_for_profilers() + + # Terminate any remaining profilers + with self._lock: + profilers_to_cleanup = list(self._spawned_profilers) + self._spawned_profilers.clear() + + for proc in profilers_to_cleanup: + self._cleanup_process(proc) + return False + + def _cleanup_process(self, proc, terminate_timeout=2.0, kill_timeout=1.0): + if proc.poll() is not None: + return # Already terminated + + proc.terminate() + try: + proc.wait(timeout=terminate_timeout) + except subprocess.TimeoutExpired: + proc.kill() + try: + proc.wait(timeout=kill_timeout) + except subprocess.TimeoutExpired: + # Last resort: wait indefinitely to avoid zombie + # SIGKILL should always work, but we must reap the process + try: + proc.wait() + except Exception: + pass + + @property + def spawned_profilers(self): + with self._lock: + return list(self._spawned_profilers) + + def wait_for_profilers(self, timeout=_DEFAULT_WAIT_TIMEOUT): + """ + Wait for all spawned child profilers to complete. + + Call this before exiting the context if you want profilers to finish + their work naturally rather than being terminated. + + Args: + timeout: Maximum time to wait in seconds + """ + profilers = self.spawned_profilers + if not profilers: + return + + print( + f"Waiting for {len(profilers)} child profiler(s) to complete...", + file=sys.stderr, + ) + + deadline = time.monotonic() + timeout + for proc in profilers: + remaining = deadline - time.monotonic() + if remaining <= 0: + break + try: + proc.wait(timeout=max(0.1, remaining)) + except subprocess.TimeoutExpired: + pass + + def _monitor_loop(self): + # Note: There is an inherent TOCTOU race between discovering a child + # process and checking if it's Python. This is expected for process monitoring. + while not self._stop_event.is_set(): + try: + self._poll_count += 1 + + # Periodically clean up completed profilers to avoid memory buildup + if self._poll_count % _CLEANUP_INTERVAL_CYCLES == 0: + self._cleanup_completed_profilers() + + children = set(get_child_pids(self.parent_pid, recursive=True)) + + with self._lock: + new_children = children - self._known_children + self._known_children.update(new_children) + + for child_pid in new_children: + # Only spawn profiler if this is actually a Python process + if is_python_process(child_pid): + self._spawn_profiler_for_child(child_pid) + + except ProcessLookupError: + # Parent process exited, stop monitoring + break + except Exception as e: + # Log error but continue monitoring + print( + f"Warning: Error in child monitor loop: {e}", + file=sys.stderr, + ) + + self._stop_event.wait(timeout=_CHILD_POLL_INTERVAL_SEC) + + def _cleanup_completed_profilers(self): + with self._lock: + # Keep only profilers that are still running + self._spawned_profilers = [ + p for p in self._spawned_profilers if p.poll() is None + ] + + def _spawn_profiler_for_child(self, child_pid): + if self._stop_event.is_set(): + return + + # Check if we've reached the maximum number of child profilers + with self._lock: + if len(self._spawned_profilers) >= _MAX_CHILD_PROFILERS: + print( + f"Warning: Max child profilers ({_MAX_CHILD_PROFILERS}) reached, " + f"skipping PID {child_pid}", + file=sys.stderr, + ) + return + + cmd = [ + sys.executable, + "-m", + "profiling.sampling", + "attach", + str(child_pid), + ] + cmd.extend(self._build_child_cli_args(child_pid)) + + proc = None + try: + proc = subprocess.Popen( + cmd, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + with self._lock: + if self._stop_event.is_set(): + self._cleanup_process( + proc, terminate_timeout=1.0, kill_timeout=1.0 + ) + return + self._spawned_profilers.append(proc) + + print( + f"Started profiler for child process {child_pid}", + file=sys.stderr, + ) + except Exception as e: + if proc is not None: + self._cleanup_process( + proc, terminate_timeout=1.0, kill_timeout=1.0 + ) + print( + f"Warning: Failed to start profiler for child {child_pid}: {e}", + file=sys.stderr, + ) + + def _build_child_cli_args(self, child_pid): + args = list(self.cli_args) + + if self.output_pattern: + # Use replace() instead of format() to handle user filenames with braces + output_file = self.output_pattern.replace("{pid}", str(child_pid)) + found_output = False + for i, arg in enumerate(args): + if arg in ("-o", "--output") and i + 1 < len(args): + args[i + 1] = output_file + found_output = True + break + if not found_output: + args.extend(["-o", output_file]) + + return args diff --git a/Lib/profiling/sampling/cli.py b/Lib/profiling/sampling/cli.py index ffc80edc1f6e74..e1ff3758c0d341 100644 --- a/Lib/profiling/sampling/cli.py +++ b/Lib/profiling/sampling/cli.py @@ -8,6 +8,7 @@ import subprocess import sys import time +from contextlib import nullcontext from .sample import sample, sample_live from .pstats_collector import PstatsCollector @@ -83,6 +84,86 @@ class CustomFormatter( "heatmap": HeatmapCollector, } +def _setup_child_monitor(args, parent_pid): + from ._child_monitor import ChildProcessMonitor + + # Build CLI args for child profilers (excluding --subprocesses to avoid recursion) + child_cli_args = _build_child_profiler_args(args) + + # Build output pattern + output_pattern = _build_output_pattern(args) + + return ChildProcessMonitor( + pid=parent_pid, + cli_args=child_cli_args, + output_pattern=output_pattern, + ) + + +def _get_child_monitor_context(args, pid): + if getattr(args, 'subprocesses', False): + return _setup_child_monitor(args, pid) + return nullcontext() + + +def _build_child_profiler_args(args): + child_args = [] + + # Sampling options + child_args.extend(["-i", str(args.interval)]) + child_args.extend(["-d", str(args.duration)]) + + if args.all_threads: + child_args.append("-a") + if args.realtime_stats: + child_args.append("--realtime-stats") + if args.native: + child_args.append("--native") + if not args.gc: + child_args.append("--no-gc") + if args.opcodes: + child_args.append("--opcodes") + if args.async_aware: + child_args.append("--async-aware") + async_mode = getattr(args, 'async_mode', 'running') + if async_mode != "running": + child_args.extend(["--async-mode", async_mode]) + + # Mode options + mode = getattr(args, 'mode', 'wall') + if mode != "wall": + child_args.extend(["--mode", mode]) + + # Format options (skip pstats as it's the default) + if args.format != "pstats": + child_args.append(f"--{args.format}") + + return child_args + + +def _build_output_pattern(args): + """Build output filename pattern for child profilers. + + The pattern uses {pid} as a placeholder which will be replaced with the + actual child PID using str.replace(), so user filenames with braces are safe. + """ + if args.outfile: + # User specified output - add PID to filename + base, ext = os.path.splitext(args.outfile) + if ext: + return f"{base}_{{pid}}{ext}" + else: + return f"{args.outfile}_{{pid}}" + else: + # Use default pattern based on format (consistent _ separator) + extension = FORMAT_EXTENSIONS.get(args.format, "txt") + if args.format == "heatmap": + return "heatmap_{pid}" + if args.format == "pstats": + # pstats defaults to stdout, but for subprocesses we need files + return "profile_{pid}.pstats" + return f"{args.format}_{{pid}}.{extension}" + def _parse_mode(mode_string): """Convert mode string to mode constant.""" @@ -255,6 +336,11 @@ def _add_sampling_options(parser): action="store_true", help="Enable async-aware profiling (uses task-based stack reconstruction)", ) + sampling_group.add_argument( + "--subprocesses", + action="store_true", + help="Also profile subprocesses. Each subprocess gets its own profiler and output file.", + ) def _add_mode_options(parser): @@ -413,7 +499,7 @@ def _generate_output_filename(format_type, pid): # For heatmap, use cleaner directory name without extension if format_type == "heatmap": return f"heatmap_{pid}" - return f"{format_type}.{pid}.{extension}" + return f"{format_type}_{pid}.{extension}" def _handle_output(collector, args, pid, mode): @@ -427,7 +513,12 @@ def _handle_output(collector, args, pid, mode): """ if args.format == "pstats": if args.outfile: - collector.export(args.outfile) + # If outfile is a directory, generate filename inside it + if os.path.isdir(args.outfile): + filename = os.path.join(args.outfile, _generate_output_filename(args.format, pid)) + collector.export(filename) + else: + collector.export(args.outfile) else: # Print to stdout with defaults applied sort_choice = args.sort if args.sort is not None else "nsamples" @@ -438,7 +529,11 @@ def _handle_output(collector, args, pid, mode): ) else: # Export to file - filename = args.outfile or _generate_output_filename(args.format, pid) + if args.outfile and os.path.isdir(args.outfile): + # If outfile is a directory, generate filename inside it + filename = os.path.join(args.outfile, _generate_output_filename(args.format, pid)) + else: + filename = args.outfile or _generate_output_filename(args.format, pid) collector.export(filename) @@ -455,6 +550,11 @@ def _validate_args(args, parser): "Live mode requires the curses module, which is not available." ) + # --subprocesses is incompatible with --live + if hasattr(args, 'subprocesses') and args.subprocesses: + if hasattr(args, 'live') and args.live: + parser.error("--subprocesses is incompatible with --live mode.") + # Async-aware mode is incompatible with --native, --no-gc, --mode, and --all-threads if args.async_aware: issues = [] @@ -663,22 +763,20 @@ def _handle_attach(args): # Create the appropriate collector collector = _create_collector(args.format, args.interval, skip_idle, args.opcodes) - # Sample the process - collector = sample( - args.pid, - collector, - duration_sec=args.duration, - all_threads=args.all_threads, - realtime_stats=args.realtime_stats, - mode=mode, - async_aware=args.async_mode if args.async_aware else None, - native=args.native, - gc=args.gc, - opcodes=args.opcodes, - ) - - # Handle output - _handle_output(collector, args, args.pid, mode) + with _get_child_monitor_context(args, args.pid): + collector = sample( + args.pid, + collector, + duration_sec=args.duration, + all_threads=args.all_threads, + realtime_stats=args.realtime_stats, + mode=mode, + async_aware=args.async_mode if args.async_aware else None, + native=args.native, + gc=args.gc, + opcodes=args.opcodes, + ) + _handle_output(collector, args, args.pid, mode) def _handle_run(args): @@ -734,32 +832,31 @@ def _handle_run(args): # Create the appropriate collector collector = _create_collector(args.format, args.interval, skip_idle, args.opcodes) - # Profile the subprocess - try: - collector = sample( - process.pid, - collector, - duration_sec=args.duration, - all_threads=args.all_threads, - realtime_stats=args.realtime_stats, - mode=mode, - async_aware=args.async_mode if args.async_aware else None, - native=args.native, - gc=args.gc, - opcodes=args.opcodes, - ) - - # Handle output - _handle_output(collector, args, process.pid, mode) - finally: - # Clean up the subprocess - if process.poll() is None: - process.terminate() - try: - process.wait(timeout=_PROCESS_KILL_TIMEOUT) - except subprocess.TimeoutExpired: - process.kill() - process.wait() + with _get_child_monitor_context(args, process.pid): + try: + collector = sample( + process.pid, + collector, + duration_sec=args.duration, + all_threads=args.all_threads, + realtime_stats=args.realtime_stats, + mode=mode, + async_aware=args.async_mode if args.async_aware else None, + native=args.native, + gc=args.gc, + opcodes=args.opcodes, + ) + _handle_output(collector, args, process.pid, mode) + finally: + # Terminate the main subprocess - child profilers finish when their + # target processes exit + if process.poll() is None: + process.terminate() + try: + process.wait(timeout=_PROCESS_KILL_TIMEOUT) + except subprocess.TimeoutExpired: + process.kill() + process.wait() def _handle_live_attach(args, pid): diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 0a50912ff0ea8c..84fd43fd396914 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -39,6 +39,7 @@ "has_fork_support", "requires_fork", "has_subprocess_support", "requires_subprocess", "has_socket_support", "requires_working_socket", + "has_remote_subprocess_debugging", "requires_remote_subprocess_debugging", "anticipate_failure", "load_package_tests", "detect_api_mismatch", "check__all__", "skip_if_buggy_ucrt_strfptime", "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer", @@ -643,6 +644,93 @@ def requires_working_socket(*, module=False): else: return unittest.skipUnless(has_socket_support, msg) + +@functools.cache +def has_remote_subprocess_debugging(): + """Check if we have permissions to debug subprocesses remotely. + + Returns True if we have permissions, False if we don't. + Checks for: + - Platform support (Linux, macOS, Windows only) + - On Linux: process_vm_readv support + - _remote_debugging module availability + - Actual subprocess debugging permissions (e.g., macOS entitlements) + Result is cached. + """ + # Check platform support + if sys.platform not in ("linux", "darwin", "win32"): + return False + + try: + import _remote_debugging + except ImportError: + return False + + # On Linux, check for process_vm_readv support + if sys.platform == "linux": + if not getattr(_remote_debugging, "PROCESS_VM_READV_SUPPORTED", False): + return False + + # First check if we can read our own process + if not _remote_debugging.is_python_process(os.getpid()): + return False + + # Check subprocess access - debugging child processes may require + # additional permissions depending on platform security settings + import socket + import subprocess + + # Create a socket for child to signal readiness + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + server.listen(1) + port = server.getsockname()[1] + + # Child connects to signal it's ready, then waits for parent to close + child_code = f""" +import socket +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect(("127.0.0.1", {port})) +s.recv(1) # Wait for parent to signal done +""" + proc = subprocess.Popen( + [sys.executable, "-c", child_code], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + try: + server.settimeout(5.0) + conn, _ = server.accept() + # Child is ready, test if we can probe it + result = _remote_debugging.is_python_process(proc.pid) + # Check if subprocess is still alive after probing + if proc.poll() is not None: + return False + conn.close() # Signal child to exit + return result + except (socket.timeout, OSError): + return False + finally: + server.close() + proc.kill() + proc.wait() + + +def requires_remote_subprocess_debugging(): + """Skip tests that require remote subprocess debugging permissions. + + This also implies subprocess support, so no need to use both + @requires_subprocess() and @requires_remote_subprocess_debugging(). + """ + if not has_subprocess_support: + return unittest.skip("requires subprocess support") + return unittest.skipUnless( + has_remote_subprocess_debugging(), + "requires remote subprocess debugging permissions" + ) + + # Does strftime() support glibc extension like '%4Y'? has_strftime_extensions = False if sys.platform != "win32": diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 0f6ed3d85f0330..e17367ca71ea38 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2245,6 +2245,18 @@ def testfunc(n): self.assertIn("_GUARD_NOS_LIST", uops) self.assertIn("_GUARD_CALLABLE_LIST_APPEND", uops) + def test_call_list_append_pop_top(self): + def testfunc(n): + a = [] + for i in range(n): + a.append(1) + return sum(a) + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + uops = get_opnames(ex) + self.assertIn("_CALL_LIST_APPEND", uops) + self.assertIn("_POP_TOP_NOP", uops) + def test_call_isinstance_is_true(self): def testfunc(n): x = 0 @@ -2513,10 +2525,29 @@ def testfunc(n): self.assertEqual(res, 10) self.assertIsNotNone(ex) uops = get_opnames(ex) + self.assertIn("_STORE_SUBSCR_LIST_INT", uops) self.assertNotIn("_POP_TOP", uops) self.assertNotIn("_POP_TOP_INT", uops) self.assertIn("_POP_TOP_NOP", uops) + def test_store_susbscr_dict(self): + def testfunc(n): + d = {} + for _ in range(n): + d['a'] = 1 + d['b'] = 2 + d['c'] = 3 + d['d'] = 4 + return sum(d.values()) + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, 10) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertIn("_STORE_SUBSCR_DICT", uops) + self.assertNotIn("_POP_TOP", uops) + self.assertIn("_POP_TOP_NOP", uops) + def test_attr_promotion_failure(self): # We're not testing for any specific uops here, just # testing it doesn't crash. diff --git a/Lib/test/test_ctypes/__init__.py b/Lib/test/test_ctypes/__init__.py index eb9126cbe18081..d848beb1ff1857 100644 --- a/Lib/test/test_ctypes/__init__.py +++ b/Lib/test/test_ctypes/__init__.py @@ -1,4 +1,5 @@ import os +import unittest from test import support from test.support import import_helper @@ -6,5 +7,21 @@ # skip tests if the _ctypes extension was not built import_helper.import_module('ctypes') + +class TestModule(unittest.TestCase): + def test_deprecated__version__(self): + import ctypes + import _ctypes + + for mod in (ctypes, _ctypes): + with self.subTest(mod=mod): + with self.assertWarnsRegex( + DeprecationWarning, + "'__version__' is deprecated and slated for removal in Python 3.20", + ) as cm: + getattr(mod, "__version__") + self.assertEqual(cm.filename, __file__) + + def load_tests(*args): return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index 4ebd8aeeb89e7e..e298f1db4e2ed4 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -15,6 +15,7 @@ SHORT_TIMEOUT, busy_retry, requires_gil_enabled, + requires_remote_subprocess_debugging, ) from test.support.script_helper import make_script from test.support.socket_helper import find_unused_port @@ -303,12 +304,7 @@ def _run_script_and_get_trace( if wait_for_signals: _wait_for_signal(client_socket, wait_for_signals) - try: - trace = trace_func(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + trace = trace_func(p.pid) return trace, script_name finally: _cleanup_sockets(client_socket, server_socket) @@ -412,6 +408,7 @@ def _extract_coroutine_stacks_lineno_only(self, stack_trace): # ============================================================================ +@requires_remote_subprocess_debugging() class TestGetStackTrace(RemoteInspectionTestBase): @skip_if_not_supported @unittest.skipIf( @@ -462,12 +459,7 @@ def foo(): client_socket, [b"ready:main", b"ready:thread"] ) - try: - stack_trace = get_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + stack_trace = get_stack_trace(p.pid) # Find expected thread stack by funcname found_thread = self._find_thread_with_frame( @@ -572,12 +564,7 @@ def new_eager_loop(): response = _wait_for_signal(client_socket, b"ready") self.assertIn(b"ready", response) - try: - stack_trace = get_async_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + stack_trace = get_async_stack_trace(p.pid) # Check all tasks are present tasks_names = [ @@ -755,12 +742,7 @@ async def main(): response = _wait_for_signal(client_socket, b"ready") self.assertIn(b"ready", response) - try: - stack_trace = get_async_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + stack_trace = get_async_stack_trace(p.pid) # For this simple asyncgen test, we only expect one task self.assertEqual(len(stack_trace[0].awaited_by), 1) @@ -842,12 +824,7 @@ async def main(): response = _wait_for_signal(client_socket, b"ready") self.assertIn(b"ready", response) - try: - stack_trace = get_async_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + stack_trace = get_async_stack_trace(p.pid) # Check all tasks are present tasks_names = [ @@ -968,12 +945,7 @@ async def main(): response = _wait_for_signal(client_socket, b"ready") self.assertIn(b"ready", response) - try: - stack_trace = get_async_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + stack_trace = get_async_stack_trace(p.pid) # Check all tasks are present tasks_names = [ @@ -1143,12 +1115,7 @@ async def main(): except RuntimeError as e: self.fail(str(e)) - try: - all_awaited_by = get_all_awaited_by(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + all_awaited_by = get_all_awaited_by(p.pid) # Expected: a list of two elements: 1 thread, 1 interp self.assertEqual(len(all_awaited_by), 2) @@ -1442,12 +1409,7 @@ def run_subinterp(): server_socket.close() server_socket = None - try: - stack_trace = get_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + stack_trace = get_stack_trace(p.pid) # Verify we have at least one interpreter self.assertGreaterEqual(len(stack_trace), 1) @@ -1637,12 +1599,7 @@ def run_subinterp2(): server_socket.close() server_socket = None - try: - stack_trace = get_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + stack_trace = get_stack_trace(p.pid) # Verify we have multiple interpreters self.assertGreaterEqual(len(stack_trace), 2) @@ -1745,34 +1702,29 @@ def main_work(): # Wait for ready and working signals _wait_for_signal(client_socket, [b"ready", b"working"]) - try: - # Get stack trace with all threads - unwinder_all = RemoteUnwinder(p.pid, all_threads=True) - for _ in range(MAX_TRIES): - all_traces = unwinder_all.get_stack_trace() - found = self._find_frame_in_trace( - all_traces, - lambda f: f.funcname == "main_work" - and f.location.lineno > 12, - ) - if found: - break - time.sleep(0.1) - else: - self.fail( - "Main thread did not start its busy work on time" - ) - - # Get stack trace with only GIL holder - unwinder_gil = RemoteUnwinder( - p.pid, only_active_thread=True + # Get stack trace with all threads + unwinder_all = RemoteUnwinder(p.pid, all_threads=True) + for _ in range(MAX_TRIES): + all_traces = unwinder_all.get_stack_trace() + found = self._find_frame_in_trace( + all_traces, + lambda f: f.funcname == "main_work" + and f.location.lineno > 12, ) - gil_traces = unwinder_gil.get_stack_trace() - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" + if found: + break + time.sleep(0.1) + else: + self.fail( + "Main thread did not start its busy work on time" ) + # Get stack trace with only GIL holder + unwinder_gil = RemoteUnwinder( + p.pid, only_active_thread=True + ) + gil_traces = unwinder_gil.get_stack_trace() + # Count threads total_threads = sum( len(interp.threads) for interp in all_traces @@ -1952,6 +1904,7 @@ def test_unsupported_platform_error(self): ) +@requires_remote_subprocess_debugging() class TestDetectionOfThreadStatus(RemoteInspectionTestBase): def _run_thread_status_test(self, mode, check_condition): """ @@ -2039,26 +1992,21 @@ def busy(): # Sample until we see expected thread states statuses = {} - try: - unwinder = RemoteUnwinder( - p.pid, - all_threads=True, - mode=mode, - skip_non_matching_threads=False, - ) - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) - - if check_condition( - statuses, sleeper_tid, busy_tid - ): - break - time.sleep(0.5) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=mode, + skip_non_matching_threads=False, + ) + for _ in range(MAX_TRIES): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if check_condition( + statuses, sleeper_tid, busy_tid + ): + break + time.sleep(0.5) return statuses, sleeper_tid, busy_tid finally: @@ -2196,40 +2144,35 @@ def busy_thread(): server_socket = None statuses = {} - try: - unwinder = RemoteUnwinder( - p.pid, - all_threads=True, - mode=PROFILING_MODE_ALL, - skip_non_matching_threads=False, - ) - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) - - # Check ALL mode provides both GIL and CPU info - if ( - sleeper_tid in statuses - and busy_tid in statuses - and not ( - statuses[sleeper_tid] - & THREAD_STATUS_ON_CPU - ) - and not ( - statuses[sleeper_tid] - & THREAD_STATUS_HAS_GIL - ) - and (statuses[busy_tid] & THREAD_STATUS_ON_CPU) - and ( - statuses[busy_tid] & THREAD_STATUS_HAS_GIL - ) - ): - break - time.sleep(0.5) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=PROFILING_MODE_ALL, + skip_non_matching_threads=False, + ) + for _ in range(MAX_TRIES): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + # Check ALL mode provides both GIL and CPU info + if ( + sleeper_tid in statuses + and busy_tid in statuses + and not ( + statuses[sleeper_tid] + & THREAD_STATUS_ON_CPU + ) + and not ( + statuses[sleeper_tid] + & THREAD_STATUS_HAS_GIL + ) + and (statuses[busy_tid] & THREAD_STATUS_ON_CPU) + and ( + statuses[busy_tid] & THREAD_STATUS_HAS_GIL + ) + ): + break + time.sleep(0.5) self.assertIsNotNone( sleeper_tid, "Sleeper thread id not received" @@ -2347,27 +2290,24 @@ def test_thread_status_exception_detection(self): self.assertIsNotNone(normal_tid, "Normal thread id not received") statuses = {} - try: - unwinder = RemoteUnwinder( - p.pid, - all_threads=True, - mode=PROFILING_MODE_ALL, - skip_non_matching_threads=False, - ) - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) - - if ( - exception_tid in statuses - and normal_tid in statuses - and (statuses[exception_tid] & THREAD_STATUS_HAS_EXCEPTION) - and not (statuses[normal_tid] & THREAD_STATUS_HAS_EXCEPTION) - ): - break - time.sleep(0.5) - except PermissionError: - self.skipTest("Insufficient permissions to read the stack trace") + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=PROFILING_MODE_ALL, + skip_non_matching_threads=False, + ) + for _ in range(MAX_TRIES): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if ( + exception_tid in statuses + and normal_tid in statuses + and (statuses[exception_tid] & THREAD_STATUS_HAS_EXCEPTION) + and not (statuses[normal_tid] & THREAD_STATUS_HAS_EXCEPTION) + ): + break + time.sleep(0.5) self.assertIn(exception_tid, statuses) self.assertIn(normal_tid, statuses) @@ -2393,30 +2333,28 @@ def test_thread_status_exception_mode_filtering(self): self.assertIsNotNone(exception_tid, "Exception thread id not received") self.assertIsNotNone(normal_tid, "Normal thread id not received") - try: - unwinder = RemoteUnwinder( - p.pid, - all_threads=True, - mode=PROFILING_MODE_EXCEPTION, - skip_non_matching_threads=True, - ) - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) - - if exception_tid in statuses: - self.assertNotIn( - normal_tid, - statuses, - "Normal thread should be filtered out in exception mode", - ) - return - time.sleep(0.5) - except PermissionError: - self.skipTest("Insufficient permissions to read the stack trace") + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=PROFILING_MODE_EXCEPTION, + skip_non_matching_threads=True, + ) + for _ in range(MAX_TRIES): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if exception_tid in statuses: + self.assertNotIn( + normal_tid, + statuses, + "Normal thread should be filtered out in exception mode", + ) + return + time.sleep(0.5) self.fail("Never found exception thread in exception mode") +@requires_remote_subprocess_debugging() class TestExceptionDetectionScenarios(RemoteInspectionTestBase): """Test exception detection across all scenarios. @@ -2557,47 +2495,43 @@ def _run_scenario_process(self, scenario): def _check_exception_status(self, p, thread_tid, expect_exception): """Helper to check if thread has expected exception status.""" - try: - unwinder = RemoteUnwinder( - p.pid, - all_threads=True, - mode=PROFILING_MODE_ALL, - skip_non_matching_threads=False, - ) - - # Collect multiple samples for reliability - results = [] - for _ in range(MAX_TRIES): - traces = unwinder.get_stack_trace() - statuses = self._get_thread_statuses(traces) + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=PROFILING_MODE_ALL, + skip_non_matching_threads=False, + ) - if thread_tid in statuses: - has_exc = bool(statuses[thread_tid] & THREAD_STATUS_HAS_EXCEPTION) - results.append(has_exc) + # Collect multiple samples for reliability + results = [] + for _ in range(MAX_TRIES): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) - if len(results) >= 3: - break + if thread_tid in statuses: + has_exc = bool(statuses[thread_tid] & THREAD_STATUS_HAS_EXCEPTION) + results.append(has_exc) - time.sleep(0.2) + if len(results) >= 3: + break - # Check majority of samples match expected - if not results: - self.fail("Never found target thread in stack traces") + time.sleep(0.2) - majority = sum(results) > len(results) // 2 - if expect_exception: - self.assertTrue( - majority, - f"Thread should have HAS_EXCEPTION flag, got {results}" - ) - else: - self.assertFalse( - majority, - f"Thread should NOT have HAS_EXCEPTION flag, got {results}" - ) + # Check majority of samples match expected + if not results: + self.fail("Never found target thread in stack traces") - except PermissionError: - self.skipTest("Insufficient permissions to read the stack trace") + majority = sum(results) > len(results) // 2 + if expect_exception: + self.assertTrue( + majority, + f"Thread should have HAS_EXCEPTION flag, got {results}" + ) + else: + self.assertFalse( + majority, + f"Thread should NOT have HAS_EXCEPTION flag, got {results}" + ) @unittest.skipIf( sys.platform not in ("linux", "darwin", "win32"), @@ -2669,6 +2603,7 @@ def test_finally_no_exception_no_flag(self): self._check_exception_status(p, thread_tid, expect_exception=False) +@requires_remote_subprocess_debugging() class TestFrameCaching(RemoteInspectionTestBase): """Test that frame caching produces correct results. @@ -2707,11 +2642,6 @@ def make_unwinder(cache_frames=True): ) yield p, client_socket, make_unwinder - - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) finally: _cleanup_sockets(client_socket, server_socket) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index c5cabc6477c8e6..59c6dc4587c93d 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1255,15 +1255,23 @@ class Spec2: def test_create_builtin(self): class Spec: - name = None + pass spec = Spec() + spec.name = "sys" + self.assertIs(_imp.create_builtin(spec), sys) + + spec.name = None with self.assertRaisesRegex(TypeError, 'name must be string, not NoneType'): _imp.create_builtin(spec) - spec.name = "" + # gh-142029 + spec.name = "nonexistent_lib" + with self.assertRaises(ModuleNotFoundError): + _imp.create_builtin(spec) # gh-142029 + spec.name = "" with self.assertRaisesRegex(ValueError, 'name must not be empty'): _imp.create_builtin(spec) diff --git a/Lib/test/test_io/test_textio.py b/Lib/test/test_io/test_textio.py index 6331ed2b958552..d725f9212ceaae 100644 --- a/Lib/test/test_io/test_textio.py +++ b/Lib/test/test_io/test_textio.py @@ -1544,6 +1544,22 @@ def write(self, data): self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size], buf._write_stack) + def test_issue142594(self): + wrapper = None + detached = False + class ReentrantRawIO(self.RawIOBase): + @property + def closed(self): + nonlocal detached + if wrapper is not None and not detached: + detached = True + wrapper.detach() + return False + + raw = ReentrantRawIO() + wrapper = self.TextIOWrapper(raw) + wrapper.close() # should not crash + class PyTextIOWrapperTest(TextIOWrapperTest, PyTestCase): shutdown_error = "LookupError: unknown encoding: ascii" diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_advanced.py b/Lib/test/test_profiling/test_sampling_profiler/test_advanced.py index 843fb3b7416375..ef9ea64b67af61 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_advanced.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_advanced.py @@ -20,15 +20,14 @@ SHORT_TIMEOUT, SuppressCrashReport, os_helper, - requires_subprocess, + requires_remote_subprocess_debugging, script_helper, ) -from .helpers import close_and_unlink, skip_if_not_supported, test_subprocess +from .helpers import close_and_unlink, test_subprocess -@requires_subprocess() -@skip_if_not_supported +@requires_remote_subprocess_debugging() class TestGCFrameTracking(unittest.TestCase): """Tests for GC frame tracking in the sampling profiler.""" @@ -62,19 +61,16 @@ def test_gc_frames_enabled(self): io.StringIO() as captured_output, mock.patch("sys.stdout", captured_output), ): - try: - from profiling.sampling.pstats_collector import PstatsCollector - collector = PstatsCollector(sample_interval_usec=5000, skip_idle=False) - profiling.sampling.sample.sample( - subproc.process.pid, - collector, - duration_sec=1, - native=False, - gc=True, - ) - collector.print_stats(show_summary=False) - except PermissionError: - self.skipTest("Insufficient permissions for remote profiling") + from profiling.sampling.pstats_collector import PstatsCollector + collector = PstatsCollector(sample_interval_usec=5000, skip_idle=False) + profiling.sampling.sample.sample( + subproc.process.pid, + collector, + duration_sec=1, + native=False, + gc=True, + ) + collector.print_stats(show_summary=False) output = captured_output.getvalue() @@ -92,19 +88,16 @@ def test_gc_frames_disabled(self): io.StringIO() as captured_output, mock.patch("sys.stdout", captured_output), ): - try: - from profiling.sampling.pstats_collector import PstatsCollector - collector = PstatsCollector(sample_interval_usec=5000, skip_idle=False) - profiling.sampling.sample.sample( - subproc.process.pid, - collector, - duration_sec=1, - native=False, - gc=False, - ) - collector.print_stats(show_summary=False) - except PermissionError: - self.skipTest("Insufficient permissions for remote profiling") + from profiling.sampling.pstats_collector import PstatsCollector + collector = PstatsCollector(sample_interval_usec=5000, skip_idle=False) + profiling.sampling.sample.sample( + subproc.process.pid, + collector, + duration_sec=1, + native=False, + gc=False, + ) + collector.print_stats(show_summary=False) output = captured_output.getvalue() @@ -116,8 +109,7 @@ def test_gc_frames_disabled(self): self.assertNotIn("", output) -@requires_subprocess() -@skip_if_not_supported +@requires_remote_subprocess_debugging() class TestNativeFrameTracking(unittest.TestCase): """Tests for native frame tracking in the sampling profiler.""" @@ -148,20 +140,15 @@ def test_native_frames_enabled(self): io.StringIO() as captured_output, mock.patch("sys.stdout", captured_output), ): - try: - from profiling.sampling.stack_collector import CollapsedStackCollector - collector = CollapsedStackCollector(1000, skip_idle=False) - profiling.sampling.sample.sample( - subproc.process.pid, - collector, - duration_sec=1, - native=True, - ) - collector.export(collapsed_file.name) - except PermissionError: - self.skipTest( - "Insufficient permissions for remote profiling" - ) + from profiling.sampling.stack_collector import CollapsedStackCollector + collector = CollapsedStackCollector(1000, skip_idle=False) + profiling.sampling.sample.sample( + subproc.process.pid, + collector, + duration_sec=1, + native=True, + ) + collector.export(collapsed_file.name) # Verify file was created and contains valid data self.assertTrue(os.path.exists(collapsed_file.name)) @@ -189,24 +176,20 @@ def test_native_frames_disabled(self): io.StringIO() as captured_output, mock.patch("sys.stdout", captured_output), ): - try: - from profiling.sampling.pstats_collector import PstatsCollector - collector = PstatsCollector(sample_interval_usec=5000, skip_idle=False) - profiling.sampling.sample.sample( - subproc.process.pid, - collector, - duration_sec=1, - ) - collector.print_stats(show_summary=False) - except PermissionError: - self.skipTest("Insufficient permissions for remote profiling") + from profiling.sampling.pstats_collector import PstatsCollector + collector = PstatsCollector(sample_interval_usec=5000, skip_idle=False) + profiling.sampling.sample.sample( + subproc.process.pid, + collector, + duration_sec=1, + ) + collector.print_stats(show_summary=False) output = captured_output.getvalue() # Native frames should NOT be present: self.assertNotIn("", output) -@requires_subprocess() -@skip_if_not_supported +@requires_remote_subprocess_debugging() class TestProcessPoolExecutorSupport(unittest.TestCase): """ Test that ProcessPoolExecutor works correctly with profiling.sampling. @@ -251,8 +234,5 @@ def worker(x): proc.kill() stdout, stderr = proc.communicate() - if "Permission Error" in stderr: - self.skipTest("Insufficient permissions for remote profiling") - self.assertIn("Results: [2, 4, 6]", stdout) self.assertNotIn("Can't pickle", stderr) diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_children.py b/Lib/test/test_profiling/test_sampling_profiler/test_children.py new file mode 100644 index 00000000000000..9b4c741727ad8c --- /dev/null +++ b/Lib/test/test_profiling/test_sampling_profiler/test_children.py @@ -0,0 +1,1065 @@ +"""Tests for --subprocesses subprocess profiling support.""" + +import argparse +import io +import os +import signal +import subprocess +import sys +import tempfile +import threading +import time +import unittest + +from test.support import ( + SHORT_TIMEOUT, + reap_children, + requires_remote_subprocess_debugging, +) + +from .helpers import _cleanup_process + +# String to check for in stderr when profiler lacks permissions (e.g., macOS) +_PERMISSION_ERROR_MSG = "Permission Error" + + +def _readline_with_timeout(file_obj, timeout): + # Thread-based readline with timeout - works across all platforms + # including Windows where select() doesn't work with pipes. + # Returns the line read, or None if timeout occurred. + result = [None] + exception = [None] + + def reader(): + try: + result[0] = file_obj.readline() + except Exception as e: + exception[0] = e + + thread = threading.Thread(target=reader, daemon=True) + thread.start() + thread.join(timeout=timeout) + + if thread.is_alive(): + return None + + if exception[0] is not None: + raise exception[0] + + return result[0] + + +def _wait_for_process_ready(proc, timeout): + # Wait for a subprocess to be ready using polling instead of fixed sleep. + # Returns True if process is ready, False if it exited or timeout. + deadline = time.time() + timeout + poll_interval = 0.01 + + while time.time() < deadline: + if proc.poll() is not None: + return False + + try: + if sys.platform == "linux": + if os.path.exists(f"/proc/{proc.pid}/exe"): + return True + else: + return True + except OSError: + pass + + time.sleep(poll_interval) + poll_interval = min(poll_interval * 2, 0.1) + + return proc.poll() is None + + +@requires_remote_subprocess_debugging() +class TestGetChildPids(unittest.TestCase): + """Tests for the get_child_pids function.""" + + def setUp(self): + reap_children() + + def tearDown(self): + reap_children() + + def test_get_child_pids_from_remote_debugging(self): + """Test get_child_pids from _remote_debugging module.""" + try: + import _remote_debugging + + # Test that the function exists + self.assertTrue(hasattr(_remote_debugging, "get_child_pids")) + + # Test with current process (should return empty or have children if any) + result = _remote_debugging.get_child_pids(os.getpid()) + self.assertIsInstance(result, list) + except (ImportError, AttributeError): + self.skipTest("_remote_debugging.get_child_pids not available") + + def test_get_child_pids_fallback(self): + """Test the fallback implementation for get_child_pids.""" + from profiling.sampling._child_monitor import get_child_pids + + # Test with current process + result = get_child_pids(os.getpid()) + self.assertIsInstance(result, list) + + @unittest.skipUnless(sys.platform == "linux", "Linux only") + def test_discover_child_process_linux(self): + """Test that we can discover child processes on Linux.""" + from profiling.sampling._child_monitor import get_child_pids + + # Create a child process + proc = subprocess.Popen( + [sys.executable, "-c", "import time; time.sleep(10)"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + try: + # Poll until child appears + deadline = time.time() + SHORT_TIMEOUT + children = [] + while time.time() < deadline: + children = get_child_pids(os.getpid()) + if proc.pid in children: + break + time.sleep(0.05) + + self.assertIn( + proc.pid, + children, + f"Child PID {proc.pid} not discovered within {SHORT_TIMEOUT}s. " + f"Found PIDs: {children}", + ) + finally: + _cleanup_process(proc) + + def test_recursive_child_discovery(self): + """Test that recursive=True finds grandchildren.""" + from profiling.sampling._child_monitor import get_child_pids + + # Create a child that spawns a grandchild and keeps a reference to it + # so we can clean it up via the child process + code = """ +import subprocess +import sys +import threading +grandchild = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(60)']) +print(grandchild.pid, flush=True) +# Wait for parent to send signal byte (cross-platform) +# Using threading with timeout so test doesn't hang if something goes wrong +# Timeout is 60s (2x test timeout) to ensure child outlives test in worst case +def wait_for_signal(): + try: + sys.stdin.buffer.read(1) + except: + pass +t = threading.Thread(target=wait_for_signal, daemon=True) +t.start() +t.join(timeout=60) +# Clean up grandchild before exiting +if grandchild.poll() is None: + grandchild.terminate() + try: + grandchild.wait(timeout=2) + except subprocess.TimeoutExpired: + grandchild.kill() + try: + grandchild.wait(timeout=2) + except subprocess.TimeoutExpired: + grandchild.wait() +""" + proc = subprocess.Popen( + [sys.executable, "-c", code], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + + grandchild_pid = None + try: + # Read grandchild PID with thread-based timeout + # This prevents indefinite blocking on all platforms + grandchild_pid_line = _readline_with_timeout( + proc.stdout, SHORT_TIMEOUT + ) + if grandchild_pid_line is None: + self.fail( + f"Timeout waiting for grandchild PID from child process " + f"(child PID: {proc.pid})" + ) + if not grandchild_pid_line: + self.fail( + f"Child process {proc.pid} closed stdout without printing " + f"grandchild PID" + ) + grandchild_pid = int(grandchild_pid_line.strip()) + + # Poll until grandchild is visible + deadline = time.time() + SHORT_TIMEOUT + pids_recursive = [] + while time.time() < deadline: + pids_recursive = get_child_pids(os.getpid(), recursive=True) + if grandchild_pid in pids_recursive: + break + time.sleep(0.05) + + self.assertIn( + proc.pid, + pids_recursive, + f"Child PID {proc.pid} not found in recursive discovery. " + f"Found: {pids_recursive}", + ) + self.assertIn( + grandchild_pid, + pids_recursive, + f"Grandchild PID {grandchild_pid} not found in recursive discovery. " + f"Found: {pids_recursive}", + ) + + # Non-recursive should find only direct child + pids_direct = get_child_pids(os.getpid(), recursive=False) + self.assertIn( + proc.pid, + pids_direct, + f"Child PID {proc.pid} not found in non-recursive discovery. " + f"Found: {pids_direct}", + ) + self.assertNotIn( + grandchild_pid, + pids_direct, + f"Grandchild PID {grandchild_pid} should NOT be in non-recursive " + f"discovery. Found: {pids_direct}", + ) + finally: + # Send signal byte to child to trigger cleanup, then close stdin + try: + proc.stdin.write(b"x") + proc.stdin.flush() + proc.stdin.close() + except OSError: + pass + proc.stdout.close() + _cleanup_process(proc) + # The grandchild may not have been cleaned up by the child process + # (e.g., if the child was killed). Explicitly terminate the + # grandchild to prevent PermissionError on Windows when removing + # temp directories. + if grandchild_pid is not None: + try: + os.kill(grandchild_pid, signal.SIGTERM) + except (OSError, ProcessLookupError): + pass # Process already exited + + def test_nonexistent_pid_returns_empty(self): + """Test that nonexistent PID returns empty list.""" + from profiling.sampling._child_monitor import get_child_pids + + # Use a very high PID that's unlikely to exist + result = get_child_pids(999999999) + self.assertEqual(result, []) + + +@requires_remote_subprocess_debugging() +class TestChildProcessMonitor(unittest.TestCase): + """Tests for the ChildProcessMonitor class.""" + + def setUp(self): + reap_children() + + def tearDown(self): + reap_children() + + def test_monitor_creation(self): + """Test that ChildProcessMonitor can be created.""" + from profiling.sampling._child_monitor import ChildProcessMonitor + + monitor = ChildProcessMonitor( + pid=os.getpid(), + cli_args=["-i", "100", "-d", "5"], + output_pattern="test_{pid}.pstats", + ) + self.assertEqual(monitor.parent_pid, os.getpid()) + self.assertEqual(monitor.cli_args, ["-i", "100", "-d", "5"]) + self.assertEqual(monitor.output_pattern, "test_{pid}.pstats") + + def test_monitor_lifecycle(self): + """Test monitor lifecycle via context manager.""" + from profiling.sampling._child_monitor import ChildProcessMonitor + + monitor = ChildProcessMonitor( + pid=os.getpid(), cli_args=[], output_pattern=None + ) + + # Before entering context, thread should not exist + self.assertIsNone(monitor._monitor_thread) + + with monitor: + # Inside context, thread should be running + self.assertIsNotNone(monitor._monitor_thread) + self.assertTrue(monitor._monitor_thread.is_alive()) + + # After exiting context, thread should be stopped + self.assertFalse(monitor._monitor_thread.is_alive()) + + def test_spawned_profilers_property(self): + """Test that spawned_profilers returns a copy of the list.""" + from profiling.sampling._child_monitor import ChildProcessMonitor + + monitor = ChildProcessMonitor( + pid=os.getpid(), cli_args=[], output_pattern=None + ) + + # Should return empty list initially + profilers = monitor.spawned_profilers + self.assertEqual(profilers, []) + self.assertIsNot(profilers, monitor._spawned_profilers) + + def test_context_manager(self): + """Test that ChildProcessMonitor works as a context manager.""" + from profiling.sampling._child_monitor import ChildProcessMonitor + + with ChildProcessMonitor( + pid=os.getpid(), cli_args=[], output_pattern=None + ) as monitor: + self.assertIsNotNone(monitor._monitor_thread) + self.assertTrue(monitor._monitor_thread.is_alive()) + + # After exiting context, thread should be stopped + self.assertFalse(monitor._monitor_thread.is_alive()) + + +@requires_remote_subprocess_debugging() +class TestCLIChildrenFlag(unittest.TestCase): + """Tests for the --subprocesses CLI flag.""" + + def setUp(self): + reap_children() + + def tearDown(self): + reap_children() + + def test_subprocesses_flag_parsed(self): + """Test that --subprocesses flag is recognized.""" + from profiling.sampling.cli import _add_sampling_options + + parser = argparse.ArgumentParser() + _add_sampling_options(parser) + + # Parse with --subprocesses + args = parser.parse_args(["--subprocesses"]) + self.assertTrue(args.subprocesses) + + # Parse without --subprocesses + args = parser.parse_args([]) + self.assertFalse(args.subprocesses) + + def test_subprocesses_incompatible_with_live(self): + """Test that --subprocesses is incompatible with --live.""" + from profiling.sampling.cli import _validate_args + + # Create mock args with both subprocesses and live + args = argparse.Namespace( + subprocesses=True, + live=True, + async_aware=False, + format="pstats", + mode="wall", + sort=None, + limit=None, + no_summary=False, + opcodes=False, + ) + + parser = argparse.ArgumentParser() + + with self.assertRaises(SystemExit): + _validate_args(args, parser) + + def test_build_child_profiler_args(self): + """Test building CLI args for child profilers.""" + from profiling.sampling.cli import _build_child_profiler_args + + args = argparse.Namespace( + interval=200, + duration=15, + all_threads=True, + realtime_stats=False, + native=True, + gc=True, + opcodes=False, + async_aware=False, + mode="cpu", + format="flamegraph", + ) + + child_args = _build_child_profiler_args(args) + + # Verify flag-value pairs are correctly paired (flag followed by value) + def assert_flag_value_pair(flag, value): + self.assertIn( + flag, + child_args, + f"Flag '{flag}' not found in args: {child_args}", + ) + flag_index = child_args.index(flag) + self.assertGreater( + len(child_args), + flag_index + 1, + f"No value after flag '{flag}' in args: {child_args}", + ) + self.assertEqual( + child_args[flag_index + 1], + str(value), + f"Flag '{flag}' should be followed by '{value}', got " + f"'{child_args[flag_index + 1]}' in args: {child_args}", + ) + + assert_flag_value_pair("-i", 200) + assert_flag_value_pair("-d", 15) + assert_flag_value_pair("--mode", "cpu") + + # Verify standalone flags are present + self.assertIn( + "-a", child_args, f"Flag '-a' not found in args: {child_args}" + ) + self.assertIn( + "--native", + child_args, + f"Flag '--native' not found in args: {child_args}", + ) + self.assertIn( + "--flamegraph", + child_args, + f"Flag '--flamegraph' not found in args: {child_args}", + ) + + def test_build_child_profiler_args_no_gc(self): + """Test building CLI args with --no-gc.""" + from profiling.sampling.cli import _build_child_profiler_args + + args = argparse.Namespace( + interval=100, + duration=5, + all_threads=False, + realtime_stats=False, + native=False, + gc=False, # Explicitly disabled + opcodes=False, + async_aware=False, + mode="wall", + format="pstats", + ) + + child_args = _build_child_profiler_args(args) + + self.assertIn( + "--no-gc", + child_args, + f"Flag '--no-gc' not found when gc=False. Args: {child_args}", + ) + + def test_build_output_pattern_with_outfile(self): + """Test output pattern generation with user-specified output.""" + from profiling.sampling.cli import _build_output_pattern + + # With extension + args = argparse.Namespace(outfile="output.html", format="flamegraph") + pattern = _build_output_pattern(args) + self.assertEqual(pattern, "output_{pid}.html") + + # Without extension + args = argparse.Namespace(outfile="output", format="pstats") + pattern = _build_output_pattern(args) + self.assertEqual(pattern, "output_{pid}") + + def test_build_output_pattern_default(self): + """Test output pattern generation with default output.""" + from profiling.sampling.cli import _build_output_pattern + + # Flamegraph format + args = argparse.Namespace(outfile=None, format="flamegraph") + pattern = _build_output_pattern(args) + self.assertIn("{pid}", pattern) + self.assertIn("flamegraph", pattern) + self.assertTrue(pattern.endswith(".html")) + + # Heatmap format + args = argparse.Namespace(outfile=None, format="heatmap") + pattern = _build_output_pattern(args) + self.assertEqual(pattern, "heatmap_{pid}") + + +@requires_remote_subprocess_debugging() +class TestChildrenIntegration(unittest.TestCase): + """Integration tests for --subprocesses functionality.""" + + def setUp(self): + reap_children() + + def tearDown(self): + reap_children() + + def test_setup_child_monitor(self): + """Test setting up a child monitor from args.""" + from profiling.sampling.cli import _setup_child_monitor + + args = argparse.Namespace( + interval=100, + duration=5, + all_threads=False, + realtime_stats=False, + native=False, + gc=True, + opcodes=False, + async_aware=False, + mode="wall", + format="pstats", + outfile=None, + ) + + monitor = _setup_child_monitor(args, os.getpid()) + # Use addCleanup to ensure monitor is properly cleaned up even if + # assertions fail + self.addCleanup(monitor.__exit__, None, None, None) + + self.assertIsNotNone(monitor) + self.assertEqual( + monitor.parent_pid, + os.getpid(), + f"Monitor parent_pid should be {os.getpid()}, got {monitor.parent_pid}", + ) + + +@requires_remote_subprocess_debugging() +class TestIsPythonProcess(unittest.TestCase): + """Tests for the is_python_process function.""" + + def setUp(self): + reap_children() + + def tearDown(self): + reap_children() + + def test_is_python_process_current_process(self): + """Test that current process is detected as Python.""" + from profiling.sampling._child_monitor import is_python_process + + # Current process should be Python + result = is_python_process(os.getpid()) + self.assertTrue( + result, + f"Current process (PID {os.getpid()}) should be detected as Python", + ) + + def test_is_python_process_python_subprocess(self): + """Test that a Python subprocess is detected as Python.""" + from profiling.sampling._child_monitor import is_python_process + + # Start a Python subprocess + proc = subprocess.Popen( + [sys.executable, "-c", "import time; time.sleep(10)"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + try: + # Poll until Python runtime structures are initialized + # (is_python_process probes for runtime structures which take + # time to initialize after process start) + deadline = time.time() + SHORT_TIMEOUT + detected = False + while time.time() < deadline: + if proc.poll() is not None: + self.fail(f"Process {proc.pid} exited unexpectedly") + if is_python_process(proc.pid): + detected = True + break + time.sleep(0.05) + + self.assertTrue( + detected, + f"Python subprocess (PID {proc.pid}) should be detected as Python " + f"within {SHORT_TIMEOUT}s", + ) + finally: + _cleanup_process(proc) + + @unittest.skipUnless(sys.platform == "linux", "Linux only test") + def test_is_python_process_non_python_subprocess(self): + """Test that a non-Python subprocess is not detected as Python.""" + from profiling.sampling._child_monitor import is_python_process + + # Start a non-Python subprocess (sleep command) + proc = subprocess.Popen( + ["sleep", "10"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + try: + # Wait for process to be ready using polling + self.assertTrue( + _wait_for_process_ready(proc, SHORT_TIMEOUT), + f"Process {proc.pid} should be ready within {SHORT_TIMEOUT}s", + ) + + self.assertFalse( + is_python_process(proc.pid), + f"Non-Python subprocess 'sleep' (PID {proc.pid}) should NOT be " + f"detected as Python", + ) + finally: + _cleanup_process(proc) + + def test_is_python_process_nonexistent_pid(self): + """Test that nonexistent PID returns False.""" + from profiling.sampling._child_monitor import is_python_process + + # Use a very high PID that's unlikely to exist + result = is_python_process(999999999) + self.assertFalse( + result, + "Nonexistent PID 999999999 should return False", + ) + + def test_is_python_process_exited_process(self): + """Test handling of a process that exits quickly.""" + from profiling.sampling._child_monitor import is_python_process + + # Start a process that exits immediately + proc = subprocess.Popen( + [sys.executable, "-c", "pass"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + # Wait for it to exit + proc.wait(timeout=SHORT_TIMEOUT) + + # Should return False for exited process (not raise) + result = is_python_process(proc.pid) + self.assertFalse( + result, f"Exited process (PID {proc.pid}) should return False" + ) + + +@requires_remote_subprocess_debugging() +class TestMaxChildProfilersLimit(unittest.TestCase): + """Tests for the _MAX_CHILD_PROFILERS limit.""" + + def setUp(self): + reap_children() + + def tearDown(self): + reap_children() + + def test_max_profilers_constant_exists(self): + """Test that _MAX_CHILD_PROFILERS constant is defined.""" + from profiling.sampling._child_monitor import _MAX_CHILD_PROFILERS + + self.assertEqual( + _MAX_CHILD_PROFILERS, + 100, + f"_MAX_CHILD_PROFILERS should be 100, got {_MAX_CHILD_PROFILERS}", + ) + + def test_cleanup_interval_constant_exists(self): + """Test that _CLEANUP_INTERVAL_CYCLES constant is defined.""" + from profiling.sampling._child_monitor import _CLEANUP_INTERVAL_CYCLES + + self.assertEqual( + _CLEANUP_INTERVAL_CYCLES, + 10, + f"_CLEANUP_INTERVAL_CYCLES should be 10, got {_CLEANUP_INTERVAL_CYCLES}", + ) + + def test_monitor_respects_max_limit(self): + """Test that monitor refuses to spawn more than _MAX_CHILD_PROFILERS.""" + from profiling.sampling._child_monitor import ( + ChildProcessMonitor, + _MAX_CHILD_PROFILERS, + ) + from unittest.mock import MagicMock, patch + + # Create a monitor + monitor = ChildProcessMonitor( + pid=os.getpid(), + cli_args=["-i", "100", "-d", "5"], + output_pattern="test_{pid}.pstats", + ) + + # Manually fill up the profilers list to the limit + mock_profilers = [MagicMock() for _ in range(_MAX_CHILD_PROFILERS)] + for mock_proc in mock_profilers: + mock_proc.poll.return_value = None # Simulate running process + monitor._spawned_profilers = mock_profilers + + # Try to spawn another profiler - should be rejected + stderr_capture = io.StringIO() + with patch("sys.stderr", stderr_capture): + monitor._spawn_profiler_for_child(99999) + + # Verify warning was printed + stderr_output = stderr_capture.getvalue() + self.assertIn( + "Max child profilers", + stderr_output, + f"Expected warning about max profilers, got: {stderr_output}", + ) + self.assertIn( + str(_MAX_CHILD_PROFILERS), + stderr_output, + f"Warning should mention limit ({_MAX_CHILD_PROFILERS}): {stderr_output}", + ) + + # Verify no new profiler was added + self.assertEqual( + len(monitor._spawned_profilers), + _MAX_CHILD_PROFILERS, + f"Should still have {_MAX_CHILD_PROFILERS} profilers, got " + f"{len(monitor._spawned_profilers)}", + ) + + +@requires_remote_subprocess_debugging() +class TestWaitForProfilers(unittest.TestCase): + """Tests for the wait_for_profilers method.""" + + def setUp(self): + reap_children() + + def tearDown(self): + reap_children() + + def test_wait_for_profilers_empty_list(self): + """Test that wait_for_profilers returns immediately with no profilers.""" + from profiling.sampling._child_monitor import ChildProcessMonitor + + monitor = ChildProcessMonitor( + pid=os.getpid(), cli_args=[], output_pattern=None + ) + + # Should return immediately without printing anything + stderr_capture = io.StringIO() + with unittest.mock.patch("sys.stderr", stderr_capture): + start = time.time() + monitor.wait_for_profilers(timeout=10.0) + elapsed = time.time() - start + + # Should complete very quickly (less than 1 second) + self.assertLess( + elapsed, + 1.0, + f"wait_for_profilers with empty list took {elapsed:.2f}s, expected < 1s", + ) + # No "Waiting for..." message should be printed + self.assertNotIn( + "Waiting for", + stderr_capture.getvalue(), + "Should not print waiting message when no profilers", + ) + + def test_wait_for_profilers_with_completed_process(self): + """Test waiting for profilers that complete quickly.""" + from profiling.sampling._child_monitor import ChildProcessMonitor + + monitor = ChildProcessMonitor( + pid=os.getpid(), cli_args=[], output_pattern=None + ) + + # Start a process that exits quickly + proc = subprocess.Popen( + [sys.executable, "-c", "pass"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + # Add to spawned profilers + monitor._spawned_profilers.append(proc) + + try: + stderr_capture = io.StringIO() + with unittest.mock.patch("sys.stderr", stderr_capture): + start = time.time() + monitor.wait_for_profilers(timeout=SHORT_TIMEOUT) + elapsed = time.time() - start + + # Should complete quickly since process exits fast + self.assertLess( + elapsed, + 5.0, + f"wait_for_profilers took {elapsed:.2f}s for quick process", + ) + # Should print waiting message + self.assertIn( + "Waiting for 1 child profiler", + stderr_capture.getvalue(), + "Should print waiting message", + ) + finally: + _cleanup_process(proc) + + def test_wait_for_profilers_timeout(self): + """Test that wait_for_profilers respects timeout.""" + from profiling.sampling._child_monitor import ChildProcessMonitor + + monitor = ChildProcessMonitor( + pid=os.getpid(), cli_args=[], output_pattern=None + ) + + # Start a process that runs for a long time + proc = subprocess.Popen( + [sys.executable, "-c", "import time; time.sleep(60)"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + # Add to spawned profilers + monitor._spawned_profilers.append(proc) + + try: + stderr_capture = io.StringIO() + with unittest.mock.patch("sys.stderr", stderr_capture): + start = time.time() + # Use short timeout + monitor.wait_for_profilers(timeout=0.5) + elapsed = time.time() - start + + # Should timeout after approximately 0.5 seconds + self.assertGreater( + elapsed, + 0.4, + f"wait_for_profilers returned too quickly ({elapsed:.2f}s)", + ) + self.assertLess( + elapsed, + 2.0, + f"wait_for_profilers took too long ({elapsed:.2f}s), timeout not respected", + ) + finally: + _cleanup_process(proc) + + def test_wait_for_profilers_multiple(self): + """Test waiting for multiple profilers.""" + from profiling.sampling._child_monitor import ChildProcessMonitor + + monitor = ChildProcessMonitor( + pid=os.getpid(), cli_args=[], output_pattern=None + ) + + # Start multiple processes + procs = [] + for _ in range(3): + proc = subprocess.Popen( + [sys.executable, "-c", "import time; time.sleep(0.1)"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + procs.append(proc) + monitor._spawned_profilers.append(proc) + + try: + stderr_capture = io.StringIO() + with unittest.mock.patch("sys.stderr", stderr_capture): + monitor.wait_for_profilers(timeout=SHORT_TIMEOUT) + + # Should report correct count + self.assertIn( + "Waiting for 3 child profiler", + stderr_capture.getvalue(), + "Should report correct profiler count", + ) + finally: + for proc in procs: + _cleanup_process(proc) + + +@requires_remote_subprocess_debugging() +class TestEndToEndChildrenCLI(unittest.TestCase): + """End-to-end tests for --subprocesses CLI flag.""" + + def setUp(self): + reap_children() + + def tearDown(self): + reap_children() + + def test_subprocesses_flag_spawns_child_and_creates_output(self): + """Test that --subprocesses flag works end-to-end with actual subprocesses.""" + # Create a temporary directory for output files + with tempfile.TemporaryDirectory() as tmpdir: + # Create a script that spawns a child Python process + parent_script = f""" +import subprocess +import sys +import time + +# Spawn a child that does some work +child = subprocess.Popen([ + sys.executable, '-c', + 'import time; [i**2 for i in range(1000)]; time.sleep(2)' +]) +# Do some work in parent +for i in range(1000): + _ = i ** 2 +time.sleep(2) +child.wait() +""" + script_file = os.path.join(tmpdir, "parent_script.py") + with open(script_file, "w") as f: + f.write(parent_script) + + output_file = os.path.join(tmpdir, "profile.pstats") + + # Run the profiler with --subprocesses flag + result = subprocess.run( + [ + sys.executable, + "-m", + "profiling.sampling", + "run", + "--subprocesses", + "-d", + "3", + "-i", + "10000", + "-o", + output_file, + script_file, + ], + capture_output=True, + text=True, + timeout=SHORT_TIMEOUT, + ) + + # Check that parent output file was created + self.assertTrue( + os.path.exists(output_file), + f"Parent profile output not created. " + f"stdout: {result.stdout}, stderr: {result.stderr}", + ) + + # Check for child profiler output files (pattern: profile_{pid}.pstats) + output_files = os.listdir(tmpdir) + child_profiles = [ + f + for f in output_files + if f.startswith("profile_") and f.endswith(".pstats") + ] + + # Note: Child profiling is best-effort; the child may exit before + # profiler attaches, or the process may not be detected as Python. + # We just verify the mechanism doesn't crash. + if result.returncode != 0: + self.fail( + f"Profiler exited with code {result.returncode}. " + f"stdout: {result.stdout}, stderr: {result.stderr}" + ) + + def test_subprocesses_flag_with_flamegraph_output(self): + """Test --subprocesses with flamegraph output format.""" + with tempfile.TemporaryDirectory() as tmpdir: + # Simple parent that spawns a child + parent_script = f""" +import subprocess +import sys +import time +child = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(1)']) +time.sleep(1) +child.wait() +""" + script_file = os.path.join(tmpdir, "parent.py") + with open(script_file, "w") as f: + f.write(parent_script) + + output_file = os.path.join(tmpdir, "flame.html") + + result = subprocess.run( + [ + sys.executable, + "-m", + "profiling.sampling", + "run", + "--subprocesses", + "-d", + "2", + "-i", + "10000", + "--flamegraph", + "-o", + output_file, + script_file, + ], + capture_output=True, + text=True, + timeout=SHORT_TIMEOUT, + ) + + self.assertTrue( + os.path.exists(output_file), + f"Flamegraph output not created. stderr: {result.stderr}", + ) + + # Verify it's valid HTML + with open(output_file, "r") as f: + content = f.read() + self.assertIn( + "` may return inaccurate values when the mock +is called concurrently from multiple threads. diff --git a/Misc/NEWS.d/next/Library/2025-12-13-21-19-28.gh-issue-76007.6fs_gT.rst b/Misc/NEWS.d/next/Library/2025-12-13-21-19-28.gh-issue-76007.6fs_gT.rst new file mode 100644 index 00000000000000..99f73bb094c620 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-13-21-19-28.gh-issue-76007.6fs_gT.rst @@ -0,0 +1 @@ +Deprecate ``__version__`` from :mod:`ctypes`. Patch by Hugo van Kemenade. diff --git a/Misc/NEWS.d/next/Library/2025-12-14-18-30-48.gh-issue-142594.belDmD.rst b/Misc/NEWS.d/next/Library/2025-12-14-18-30-48.gh-issue-142594.belDmD.rst new file mode 100644 index 00000000000000..ee6a958933f7c8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-14-18-30-48.gh-issue-142594.belDmD.rst @@ -0,0 +1,2 @@ +Fix crash in ``TextIOWrapper.close()`` when the underlying buffer's +``closed`` property calls :meth:`~io.TextIOBase.detach`. diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 1be83b455261ea..acb08400e24e2e 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -41,7 +41,7 @@ @MODULE__PICKLE_TRUE@_pickle _pickle.c @MODULE__QUEUE_TRUE@_queue _queuemodule.c @MODULE__RANDOM_TRUE@_random _randommodule.c -@MODULE__REMOTE_DEBUGGING_TRUE@_remote_debugging _remote_debugging/module.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/frame_cache.c _remote_debugging/threads.c _remote_debugging/asyncio.c +@MODULE__REMOTE_DEBUGGING_TRUE@_remote_debugging _remote_debugging/module.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/frame_cache.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/subprocess.c @MODULE__STRUCT_TRUE@_struct _struct.c # build supports subinterpreters diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 0e6a1e93e04f33..0b2a5d7093e1ea 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2076,8 +2076,8 @@ class _asyncio.Task "TaskObj *" "&Task_Type" static int task_call_step_soon(asyncio_state *state, TaskObj *, PyObject *); static PyObject *task_wakeup(PyObject *op, PyObject *arg); -static PyObject * task_step(asyncio_state *, TaskObj *, PyObject *); -static int task_eager_start(asyncio_state *state, TaskObj *task); +static PyObject *task_step(asyncio_state *, TaskObj *, PyObject *); +static int task_eager_start(_PyThreadStateImpl *ts, asyncio_state *state, TaskObj *task); /* ----- Task._step wrapper */ @@ -2195,15 +2195,14 @@ static PyMethodDef TaskWakeupDef = { /* ----- Task introspection helpers */ static void -register_task(TaskObj *task) +register_task(_PyThreadStateImpl *ts, TaskObj *task) { if (task->task_node.next != NULL) { // already registered assert(task->task_node.prev != NULL); return; } - _PyThreadStateImpl *tstate = (_PyThreadStateImpl *) _PyThreadState_GET(); - struct llist_node *head = &tstate->asyncio_tasks_head; + struct llist_node *head = &ts->asyncio_tasks_head; llist_insert_tail(head, &task->task_node); } @@ -2241,10 +2240,8 @@ unregister_task(TaskObj *task) } static int -enter_task(PyObject *loop, PyObject *task) +enter_task(_PyThreadStateImpl *ts, PyObject *loop, PyObject *task) { - _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); - if (ts->asyncio_running_loop != loop) { PyErr_Format(PyExc_RuntimeError, "loop %R is not the running loop", loop); return -1; @@ -2264,10 +2261,8 @@ enter_task(PyObject *loop, PyObject *task) } static int -leave_task(PyObject *loop, PyObject *task) +leave_task(_PyThreadStateImpl *ts, PyObject *loop, PyObject *task) { - _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); - if (ts->asyncio_running_loop != loop) { PyErr_Format(PyExc_RuntimeError, "loop %R is not the running loop", loop); return -1; @@ -2286,10 +2281,8 @@ leave_task(PyObject *loop, PyObject *task) } static PyObject * -swap_current_task(PyObject *loop, PyObject *task) +swap_current_task(_PyThreadStateImpl *ts, PyObject *loop, PyObject *task) { - _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); - if (ts->asyncio_running_loop != loop) { PyErr_Format(PyExc_RuntimeError, "loop %R is not the running loop", loop); return NULL; @@ -2384,7 +2377,7 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, if (self->task_name == NULL) { return -1; } - + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); if (eager_start) { PyObject *res = PyObject_CallMethodNoArgs(loop, &_Py_ID(is_running)); if (res == NULL) { @@ -2393,7 +2386,7 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, int is_loop_running = Py_IsTrue(res); Py_DECREF(res); if (is_loop_running) { - if (task_eager_start(state, self)) { + if (task_eager_start(ts, state, self)) { return -1; } return 0; @@ -2408,7 +2401,7 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, // works correctly in non-owning threads. _PyObject_SetMaybeWeakref((PyObject *)self); #endif - register_task(self); + register_task(ts, self); return 0; } @@ -3452,7 +3445,9 @@ task_step(asyncio_state *state, TaskObj *task, PyObject *exc) { PyObject *res; - if (enter_task(task->task_loop, (PyObject*)task) < 0) { + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); + + if (enter_task(ts, task->task_loop, (PyObject*)task) < 0) { return NULL; } @@ -3460,12 +3455,12 @@ task_step(asyncio_state *state, TaskObj *task, PyObject *exc) if (res == NULL) { PyObject *exc = PyErr_GetRaisedException(); - leave_task(task->task_loop, (PyObject*)task); + leave_task(ts, task->task_loop, (PyObject*)task); _PyErr_ChainExceptions1(exc); return NULL; } else { - if (leave_task(task->task_loop, (PyObject*)task) < 0) { + if (leave_task(ts, task->task_loop, (PyObject*)task) < 0) { Py_DECREF(res); return NULL; } @@ -3476,10 +3471,10 @@ task_step(asyncio_state *state, TaskObj *task, PyObject *exc) } static int -task_eager_start(asyncio_state *state, TaskObj *task) +task_eager_start(_PyThreadStateImpl *ts, asyncio_state *state, TaskObj *task) { assert(task != NULL); - PyObject *prevtask = swap_current_task(task->task_loop, (PyObject *)task); + PyObject *prevtask = swap_current_task(ts, task->task_loop, (PyObject *)task); if (prevtask == NULL) { return -1; } @@ -3487,9 +3482,9 @@ task_eager_start(asyncio_state *state, TaskObj *task) // if the task completes eagerly (without suspending) then it will unregister itself // in future_schedule_callbacks when done, otherwise // it will continue as a regular (non-eager) asyncio task - register_task(task); + register_task(ts, task); - if (PyContext_Enter(task->task_context) == -1) { + if (_PyContext_Enter(&ts->base, task->task_context) == -1) { Py_DECREF(prevtask); return -1; } @@ -3508,7 +3503,7 @@ task_eager_start(asyncio_state *state, TaskObj *task) Py_DECREF(stepres); } - PyObject *curtask = swap_current_task(task->task_loop, prevtask); + PyObject *curtask = swap_current_task(ts, task->task_loop, prevtask); Py_DECREF(prevtask); if (curtask == NULL) { retval = -1; @@ -3517,7 +3512,7 @@ task_eager_start(asyncio_state *state, TaskObj *task) Py_DECREF(curtask); } - if (PyContext_Exit(task->task_context) == -1) { + if (_PyContext_Exit(&ts->base, task->task_context) == -1) { retval = -1; } @@ -3712,7 +3707,8 @@ _asyncio__register_task_impl(PyObject *module, PyObject *task) if (Task_Check(state, task)) { // task is an asyncio.Task instance or subclass, use efficient // linked-list implementation. - register_task((TaskObj *)task); + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); + register_task(ts, (TaskObj *)task); Py_RETURN_NONE; } // As task does not inherit from asyncio.Task, fallback to less efficient @@ -3745,7 +3741,8 @@ _asyncio__register_eager_task_impl(PyObject *module, PyObject *task) if (Task_Check(state, task)) { // task is an asyncio.Task instance or subclass, use efficient // linked-list implementation. - register_task((TaskObj *)task); + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); + register_task(ts, (TaskObj *)task); Py_RETURN_NONE; } @@ -3832,7 +3829,8 @@ static PyObject * _asyncio__enter_task_impl(PyObject *module, PyObject *loop, PyObject *task) /*[clinic end generated code: output=a22611c858035b73 input=de1b06dca70d8737]*/ { - if (enter_task(loop, task) < 0) { + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); + if (enter_task(ts, loop, task) < 0) { return NULL; } Py_RETURN_NONE; @@ -3856,7 +3854,8 @@ static PyObject * _asyncio__leave_task_impl(PyObject *module, PyObject *loop, PyObject *task) /*[clinic end generated code: output=0ebf6db4b858fb41 input=51296a46313d1ad8]*/ { - if (leave_task(loop, task) < 0) { + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); + if (leave_task(ts, loop, task) < 0) { return NULL; } Py_RETURN_NONE; @@ -3880,7 +3879,8 @@ _asyncio__swap_current_task_impl(PyObject *module, PyObject *loop, PyObject *task) /*[clinic end generated code: output=9f88de958df74c7e input=c9c72208d3d38b6c]*/ { - return swap_current_task(loop, task); + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); + return swap_current_task(ts, loop, task); } diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 91fd23d413de21..774ac71ce9ec56 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -6334,7 +6334,6 @@ _ctypes_add_objects(PyObject *mod) MOD_ADD("FUNCFLAG_USE_ERRNO", PyLong_FromLong(FUNCFLAG_USE_ERRNO)); MOD_ADD("FUNCFLAG_USE_LASTERROR", PyLong_FromLong(FUNCFLAG_USE_LASTERROR)); MOD_ADD("FUNCFLAG_PYTHONAPI", PyLong_FromLong(FUNCFLAG_PYTHONAPI)); - MOD_ADD("__version__", PyUnicode_FromString("1.1.0")); MOD_ADD("_memmove_addr", PyLong_FromVoidPtr(memmove)); MOD_ADD("_memset_addr", PyLong_FromVoidPtr(memset)); diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index a8c16547e4b217..9a1c1ff8bb9cda 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -1990,8 +1990,32 @@ buffer_info(PyObject *self, PyObject *arg) } +static PyObject * +_ctypes_getattr(PyObject *Py_UNUSED(self), PyObject *args) +{ + PyObject *name; + if (!PyArg_UnpackTuple(args, "__getattr__", 1, 1, &name)) { + return NULL; + } + + if (PyUnicode_Check(name) && PyUnicode_EqualToUTF8(name, "__version__")) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "'__version__' is deprecated and slated for " + "removal in Python 3.20", + 1) < 0) { + return NULL; + } + return PyUnicode_FromString("1.1.0"); // Do not change + } + + PyErr_Format(PyExc_AttributeError, + "module '_ctypes' has no attribute %R", name); + return NULL; +} + PyMethodDef _ctypes_module_methods[] = { + {"__getattr__", _ctypes_getattr, METH_VARARGS}, {"get_errno", get_errno, METH_NOARGS}, {"set_errno", set_errno, METH_VARARGS}, {"_unpickle", unpickle, METH_VARARGS }, diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 65da300abcf3bc..f9881952561292 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -3150,6 +3150,9 @@ _io_TextIOWrapper_close_impl(textio *self) if (r > 0) { Py_RETURN_NONE; /* stream already closed */ } + if (self->detached) { + Py_RETURN_NONE; /* gh-142594 null pointer issue */ + } else { PyObject *exc = NULL; if (self->finalizing) { diff --git a/Modules/_remote_debugging/_remote_debugging.h b/Modules/_remote_debugging/_remote_debugging.h index fcb75b841b742e..2f3efedd1e0ed5 100644 --- a/Modules/_remote_debugging/_remote_debugging.h +++ b/Modules/_remote_debugging/_remote_debugging.h @@ -8,23 +8,24 @@ #ifndef Py_REMOTE_DEBUGGING_H #define Py_REMOTE_DEBUGGING_H +/* _GNU_SOURCE must be defined before any system headers */ +#define _GNU_SOURCE + #ifdef __cplusplus extern "C" { #endif -#define _GNU_SOURCE - #ifndef Py_BUILD_CORE_BUILTIN # define Py_BUILD_CORE_MODULE 1 #endif #include "Python.h" -#include // _Py_DebugOffsets -#include // FRAME_SUSPENDED_YIELD_FROM -#include // FRAME_OWNED_BY_INTERPRETER -#include // struct llist_node -#include // _PyLong_GetZero -#include // Py_TAG_BITS +#include "internal/pycore_debug_offsets.h" // _Py_DebugOffsets +#include "internal/pycore_frame.h" // FRAME_SUSPENDED_YIELD_FROM +#include "internal/pycore_interpframe.h" // FRAME_OWNED_BY_INTERPRETER +#include "internal/pycore_llist.h" // struct llist_node +#include "internal/pycore_long.h" // _PyLong_GetZero +#include "internal/pycore_stackref.h" // Py_TAG_BITS #include "../../Python/remote_debug.h" #include @@ -40,10 +41,17 @@ extern "C" { # define HAVE_PROCESS_VM_READV 0 #endif -#if defined(__APPLE__) && TARGET_OS_OSX -#include -#include -#define MAX_NATIVE_THREADS 4096 +#if defined(__APPLE__) +#include +# if !defined(TARGET_OS_OSX) + /* Older macOS SDKs do not define TARGET_OS_OSX */ +# define TARGET_OS_OSX 1 +# endif +# if TARGET_OS_OSX +# include +# include +# define MAX_NATIVE_THREADS 4096 +# endif #endif #ifdef MS_WINDOWS @@ -581,6 +589,16 @@ extern int process_thread_for_async_stack_trace( void *context ); +/* ============================================================================ + * SUBPROCESS ENUMERATION FUNCTION DECLARATIONS + * ============================================================================ */ + +/* Get all child PIDs of a process. + * Returns a new Python list of PIDs, or NULL on error with exception set. + * If recursive is true, includes all descendants (children, grandchildren, etc.) + */ +extern PyObject *enumerate_child_pids(pid_t target_pid, int recursive); + #ifdef __cplusplus } #endif diff --git a/Modules/_remote_debugging/clinic/module.c.h b/Modules/_remote_debugging/clinic/module.c.h index 353929c4643dbd..5cbf64517af608 100644 --- a/Modules/_remote_debugging/clinic/module.c.h +++ b/Modules/_remote_debugging/clinic/module.c.h @@ -433,4 +433,153 @@ _remote_debugging_RemoteUnwinder_get_stats(PyObject *self, PyObject *Py_UNUSED(i return return_value; } -/*[clinic end generated code: output=1943fb7a56197e39 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_remote_debugging_get_child_pids__doc__, +"get_child_pids($module, /, pid, *, recursive=True)\n" +"--\n" +"\n" +"Get all child process IDs of the given process.\n" +"\n" +" pid\n" +" Process ID of the parent process\n" +" recursive\n" +" If True, return all descendants (children, grandchildren, etc.).\n" +" If False, return only direct children.\n" +"\n" +"Returns a list of child process IDs. Returns an empty list if no children\n" +"are found.\n" +"\n" +"This function provides a snapshot of child processes at a moment in time.\n" +"Child processes may exit or new ones may be created after the list is returned.\n" +"\n" +"Raises:\n" +" OSError: If unable to enumerate processes\n" +" NotImplementedError: If not supported on this platform"); + +#define _REMOTE_DEBUGGING_GET_CHILD_PIDS_METHODDEF \ + {"get_child_pids", _PyCFunction_CAST(_remote_debugging_get_child_pids), METH_FASTCALL|METH_KEYWORDS, _remote_debugging_get_child_pids__doc__}, + +static PyObject * +_remote_debugging_get_child_pids_impl(PyObject *module, int pid, + int recursive); + +static PyObject * +_remote_debugging_get_child_pids(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(pid), &_Py_ID(recursive), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"pid", "recursive", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "get_child_pids", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + int pid; + int recursive = 1; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + pid = PyLong_AsInt(args[0]); + if (pid == -1 && PyErr_Occurred()) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + recursive = PyObject_IsTrue(args[1]); + if (recursive < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = _remote_debugging_get_child_pids_impl(module, pid, recursive); + +exit: + return return_value; +} + +PyDoc_STRVAR(_remote_debugging_is_python_process__doc__, +"is_python_process($module, /, pid)\n" +"--\n" +"\n" +"Check if a process is a Python process."); + +#define _REMOTE_DEBUGGING_IS_PYTHON_PROCESS_METHODDEF \ + {"is_python_process", _PyCFunction_CAST(_remote_debugging_is_python_process), METH_FASTCALL|METH_KEYWORDS, _remote_debugging_is_python_process__doc__}, + +static PyObject * +_remote_debugging_is_python_process_impl(PyObject *module, int pid); + +static PyObject * +_remote_debugging_is_python_process(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(pid), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"pid", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_python_process", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + int pid; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + pid = PyLong_AsInt(args[0]); + if (pid == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = _remote_debugging_is_python_process_impl(module, pid); + +exit: + return return_value; +} +/*[clinic end generated code: output=dc0550ad3d6a409c input=a9049054013a1b77]*/ diff --git a/Modules/_remote_debugging/module.c b/Modules/_remote_debugging/module.c index a194d88c3c3ca0..fc58e2428b2009 100644 --- a/Modules/_remote_debugging/module.c +++ b/Modules/_remote_debugging/module.c @@ -350,7 +350,7 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, } // Validate that the debug offsets are valid - if(validate_debug_offsets(&self->debug_offsets) == -1) { + if (validate_debug_offsets(&self->debug_offsets) == -1) { set_exception_cause(self, PyExc_RuntimeError, "Invalid debug offsets found"); return -1; } @@ -933,7 +933,7 @@ RemoteUnwinder_dealloc(PyObject *op) _Py_hashtable_destroy(self->code_object_cache); } #ifdef MS_WINDOWS - if(self->win_process_buffer != NULL) { + if (self->win_process_buffer != NULL) { PyMem_Free(self->win_process_buffer); } #endif @@ -1122,7 +1122,74 @@ static PyModuleDef_Slot remote_debugging_slots[] = { {0, NULL}, }; +/* ============================================================================ + * MODULE-LEVEL FUNCTIONS + * ============================================================================ */ + +/*[clinic input] +_remote_debugging.get_child_pids + + pid: int + Process ID of the parent process + * + recursive: bool = True + If True, return all descendants (children, grandchildren, etc.). + If False, return only direct children. + +Get all child process IDs of the given process. + +Returns a list of child process IDs. Returns an empty list if no children +are found. + +This function provides a snapshot of child processes at a moment in time. +Child processes may exit or new ones may be created after the list is returned. + +Raises: + OSError: If unable to enumerate processes + NotImplementedError: If not supported on this platform +[clinic start generated code]*/ + +static PyObject * +_remote_debugging_get_child_pids_impl(PyObject *module, int pid, + int recursive) +/*[clinic end generated code: output=1ae2289c6b953e4b input=3395cbe7f17066c9]*/ +{ + return enumerate_child_pids((pid_t)pid, recursive); +} + +/*[clinic input] +_remote_debugging.is_python_process + + pid: int + +Check if a process is a Python process. +[clinic start generated code]*/ + +static PyObject * +_remote_debugging_is_python_process_impl(PyObject *module, int pid) +/*[clinic end generated code: output=22947dc8afcac362 input=13488e28c7295d84]*/ +{ + proc_handle_t handle; + + if (_Py_RemoteDebug_InitProcHandle(&handle, pid) < 0) { + PyErr_Clear(); + Py_RETURN_FALSE; + } + + uintptr_t runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(&handle); + _Py_RemoteDebug_CleanupProcHandle(&handle); + + if (runtime_start_address == 0) { + PyErr_Clear(); + Py_RETURN_FALSE; + } + + Py_RETURN_TRUE; +} + static PyMethodDef remote_debugging_methods[] = { + _REMOTE_DEBUGGING_GET_CHILD_PIDS_METHODDEF + _REMOTE_DEBUGGING_IS_PYTHON_PROCESS_METHODDEF {NULL, NULL, 0, NULL}, }; diff --git a/Modules/_remote_debugging/subprocess.c b/Modules/_remote_debugging/subprocess.c new file mode 100644 index 00000000000000..2056217664a9ee --- /dev/null +++ b/Modules/_remote_debugging/subprocess.c @@ -0,0 +1,459 @@ +/****************************************************************************** + * Remote Debugging Module - Subprocess Enumeration + * + * This file contains platform-specific functions for enumerating child + * processes of a given PID. + ******************************************************************************/ + +#include "_remote_debugging.h" + +#ifndef MS_WINDOWS +#include +#include +#endif + +#ifdef MS_WINDOWS +#include +#endif + +/* ============================================================================ + * INTERNAL DATA STRUCTURES + * ============================================================================ */ + +/* Simple dynamic array for collecting PIDs */ +typedef struct { + pid_t *pids; + size_t count; + size_t capacity; +} pid_array_t; + +static int +pid_array_init(pid_array_t *arr) +{ + arr->capacity = 64; + arr->count = 0; + arr->pids = (pid_t *)PyMem_Malloc(arr->capacity * sizeof(pid_t)); + if (arr->pids == NULL) { + PyErr_NoMemory(); + return -1; + } + return 0; +} + +static void +pid_array_cleanup(pid_array_t *arr) +{ + if (arr->pids != NULL) { + PyMem_Free(arr->pids); + arr->pids = NULL; + } + arr->count = 0; + arr->capacity = 0; +} + +static int +pid_array_append(pid_array_t *arr, pid_t pid) +{ + if (arr->count >= arr->capacity) { + /* Check for overflow before multiplication */ + if (arr->capacity > SIZE_MAX / 2) { + PyErr_SetString(PyExc_OverflowError, "PID array capacity overflow"); + return -1; + } + size_t new_capacity = arr->capacity * 2; + /* Check allocation size won't overflow */ + if (new_capacity > SIZE_MAX / sizeof(pid_t)) { + PyErr_SetString(PyExc_OverflowError, "PID array size overflow"); + return -1; + } + pid_t *new_pids = (pid_t *)PyMem_Realloc(arr->pids, new_capacity * sizeof(pid_t)); + if (new_pids == NULL) { + PyErr_NoMemory(); + return -1; + } + arr->pids = new_pids; + arr->capacity = new_capacity; + } + arr->pids[arr->count++] = pid; + return 0; +} + +static int +pid_array_contains(pid_array_t *arr, pid_t pid) +{ + for (size_t i = 0; i < arr->count; i++) { + if (arr->pids[i] == pid) { + return 1; + } + } + return 0; +} + +/* ============================================================================ + * SHARED BFS HELPER + * ============================================================================ */ + +/* Find child PIDs using BFS traversal of the pid->ppid mapping. + * all_pids and ppids must have the same count (parallel arrays). + * Returns 0 on success, -1 on error. */ +static int +find_children_bfs(pid_t target_pid, int recursive, + pid_t *all_pids, pid_t *ppids, size_t pid_count, + pid_array_t *result) +{ + int retval = -1; + pid_array_t to_process = {0}; + + if (pid_array_init(&to_process) < 0) { + goto done; + } + if (pid_array_append(&to_process, target_pid) < 0) { + goto done; + } + + size_t process_idx = 0; + while (process_idx < to_process.count) { + pid_t current_pid = to_process.pids[process_idx++]; + + for (size_t i = 0; i < pid_count; i++) { + if (ppids[i] != current_pid) { + continue; + } + pid_t child_pid = all_pids[i]; + if (pid_array_contains(result, child_pid)) { + continue; + } + if (pid_array_append(result, child_pid) < 0) { + goto done; + } + if (recursive && pid_array_append(&to_process, child_pid) < 0) { + goto done; + } + } + + if (!recursive) { + break; + } + } + + retval = 0; + +done: + pid_array_cleanup(&to_process); + return retval; +} + +/* ============================================================================ + * LINUX IMPLEMENTATION + * ============================================================================ */ + +#if defined(__linux__) + +/* Parse /proc/{pid}/stat to get parent PID */ +static pid_t +get_ppid_linux(pid_t pid) +{ + char stat_path[64]; + char buffer[2048]; + + snprintf(stat_path, sizeof(stat_path), "/proc/%d/stat", (int)pid); + + int fd = open(stat_path, O_RDONLY); + if (fd == -1) { + return -1; + } + + ssize_t n = read(fd, buffer, sizeof(buffer) - 1); + close(fd); + + if (n <= 0) { + return -1; + } + buffer[n] = '\0'; + + /* Find closing paren of comm field - stat format: pid (comm) state ppid ... */ + char *p = strrchr(buffer, ')'); + if (!p) { + return -1; + } + + /* Skip ") " with bounds checking */ + char *end = buffer + n; + p += 2; + if (p >= end) { + return -1; + } + if (*p == ' ') { + p++; + if (p >= end) { + return -1; + } + } + + /* Parse: state ppid */ + char state; + int ppid; + if (sscanf(p, "%c %d", &state, &ppid) != 2) { + return -1; + } + + return (pid_t)ppid; +} + +static int +get_child_pids_platform(pid_t target_pid, int recursive, pid_array_t *result) +{ + int retval = -1; + pid_array_t all_pids = {0}; + pid_array_t ppids = {0}; + DIR *proc_dir = NULL; + + if (pid_array_init(&all_pids) < 0) { + goto done; + } + + if (pid_array_init(&ppids) < 0) { + goto done; + } + + proc_dir = opendir("/proc"); + if (!proc_dir) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, "/proc"); + goto done; + } + + /* Single pass: collect PIDs and their PPIDs together */ + struct dirent *entry; + while ((entry = readdir(proc_dir)) != NULL) { + /* Skip non-numeric entries (also skips . and ..) */ + if (entry->d_name[0] < '1' || entry->d_name[0] > '9') { + continue; + } + char *endptr; + long pid_long = strtol(entry->d_name, &endptr, 10); + if (*endptr != '\0' || pid_long <= 0) { + continue; + } + pid_t pid = (pid_t)pid_long; + pid_t ppid = get_ppid_linux(pid); + if (ppid < 0) { + continue; + } + if (pid_array_append(&all_pids, pid) < 0 || + pid_array_append(&ppids, ppid) < 0) { + goto done; + } + } + + closedir(proc_dir); + proc_dir = NULL; + + if (find_children_bfs(target_pid, recursive, + all_pids.pids, ppids.pids, all_pids.count, + result) < 0) { + goto done; + } + + retval = 0; + +done: + if (proc_dir) { + closedir(proc_dir); + } + pid_array_cleanup(&all_pids); + pid_array_cleanup(&ppids); + return retval; +} + +#endif /* __linux__ */ + +/* ============================================================================ + * MACOS IMPLEMENTATION + * ============================================================================ */ + +#if defined(__APPLE__) && TARGET_OS_OSX + +#include + +static int +get_child_pids_platform(pid_t target_pid, int recursive, pid_array_t *result) +{ + int retval = -1; + pid_t *pid_list = NULL; + pid_t *ppids = NULL; + + /* Get count of all PIDs */ + int n_pids = proc_listallpids(NULL, 0); + if (n_pids <= 0) { + PyErr_SetString(PyExc_OSError, "Failed to get process count"); + goto done; + } + + /* Allocate buffer for PIDs (add some slack for new processes) */ + int buffer_size = n_pids + 64; + pid_list = (pid_t *)PyMem_Malloc(buffer_size * sizeof(pid_t)); + if (!pid_list) { + PyErr_NoMemory(); + goto done; + } + + /* Get actual PIDs */ + int actual = proc_listallpids(pid_list, buffer_size * sizeof(pid_t)); + if (actual <= 0) { + PyErr_SetString(PyExc_OSError, "Failed to list PIDs"); + goto done; + } + + /* Build pid -> ppid mapping */ + ppids = (pid_t *)PyMem_Malloc(actual * sizeof(pid_t)); + if (!ppids) { + PyErr_NoMemory(); + goto done; + } + + /* Get parent PIDs for each process */ + int valid_count = 0; + for (int i = 0; i < actual; i++) { + struct proc_bsdinfo proc_info; + int ret = proc_pidinfo(pid_list[i], PROC_PIDTBSDINFO, 0, + &proc_info, sizeof(proc_info)); + if (ret != sizeof(proc_info)) { + continue; + } + pid_list[valid_count] = pid_list[i]; + ppids[valid_count] = proc_info.pbi_ppid; + valid_count++; + } + + if (find_children_bfs(target_pid, recursive, + pid_list, ppids, valid_count, + result) < 0) { + goto done; + } + + retval = 0; + +done: + PyMem_Free(pid_list); + PyMem_Free(ppids); + return retval; +} + +#endif /* __APPLE__ && TARGET_OS_OSX */ + +/* ============================================================================ + * WINDOWS IMPLEMENTATION + * ============================================================================ */ + +#ifdef MS_WINDOWS + +static int +get_child_pids_platform(pid_t target_pid, int recursive, pid_array_t *result) +{ + int retval = -1; + pid_array_t all_pids = {0}; + pid_array_t ppids = {0}; + HANDLE snapshot = INVALID_HANDLE_VALUE; + + snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + goto done; + } + + if (pid_array_init(&all_pids) < 0) { + goto done; + } + + if (pid_array_init(&ppids) < 0) { + goto done; + } + + /* Single pass: collect PIDs and PPIDs together */ + PROCESSENTRY32 pe; + pe.dwSize = sizeof(PROCESSENTRY32); + if (Process32First(snapshot, &pe)) { + do { + if (pid_array_append(&all_pids, (pid_t)pe.th32ProcessID) < 0 || + pid_array_append(&ppids, (pid_t)pe.th32ParentProcessID) < 0) { + goto done; + } + } while (Process32Next(snapshot, &pe)); + } + + CloseHandle(snapshot); + snapshot = INVALID_HANDLE_VALUE; + + if (find_children_bfs(target_pid, recursive, + all_pids.pids, ppids.pids, all_pids.count, + result) < 0) { + goto done; + } + + retval = 0; + +done: + if (snapshot != INVALID_HANDLE_VALUE) { + CloseHandle(snapshot); + } + pid_array_cleanup(&all_pids); + pid_array_cleanup(&ppids); + return retval; +} + +#endif /* MS_WINDOWS */ + +/* ============================================================================ + * UNSUPPORTED PLATFORM STUB + * ============================================================================ */ + +#if !defined(__linux__) && !(defined(__APPLE__) && TARGET_OS_OSX) && !defined(MS_WINDOWS) + +static int +get_child_pids_platform(pid_t target_pid, int recursive, pid_array_t *result) +{ + PyErr_SetString(PyExc_NotImplementedError, + "Subprocess enumeration not supported on this platform"); + return -1; +} + +#endif + +/* ============================================================================ + * PUBLIC API + * ============================================================================ */ + +PyObject * +enumerate_child_pids(pid_t target_pid, int recursive) +{ + pid_array_t result; + + if (pid_array_init(&result) < 0) { + return NULL; + } + + if (get_child_pids_platform(target_pid, recursive, &result) < 0) { + pid_array_cleanup(&result); + return NULL; + } + + /* Convert to Python list */ + PyObject *list = PyList_New(result.count); + if (list == NULL) { + pid_array_cleanup(&result); + return NULL; + } + + for (size_t i = 0; i < result.count; i++) { + PyObject *pid_obj = PyLong_FromLong((long)result.pids[i]); + if (pid_obj == NULL) { + Py_DECREF(list); + pid_array_cleanup(&result); + return NULL; + } + PyList_SET_ITEM(list, i, pid_obj); + } + + pid_array_cleanup(&result); + return list; +} diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 99e1c9b13f7879..25cc0bfcbaba45 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -914,6 +914,10 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, return -1; } + /* Should be caused by first init or the resize to 0. */ + assert(self->ob_bytes_object == Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_BYTES)); + assert(self->ob_exports == 0); + /* Make a quick exit if no first argument */ if (arg == NULL) { if (encoding != NULL || errors != NULL) { @@ -935,9 +939,20 @@ bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, return -1; } encoded = PyUnicode_AsEncodedString(arg, encoding, errors); - if (encoded == NULL) + if (encoded == NULL) { return -1; + } assert(PyBytes_Check(encoded)); + + /* Most encodes return a new unique bytes, just use it as buffer. */ + if (_PyObject_IsUniquelyReferenced(encoded) + && PyBytes_CheckExact(encoded)) + { + Py_ssize_t size = Py_SIZE(encoded); + self->ob_bytes_object = encoded; + bytearray_reinit_from_bytes(self, size, size); + return 0; + } new = bytearray_iconcat((PyObject*)self, encoded); Py_DECREF(encoded); if (new == NULL) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index ac4a46dab107e8..49a42a35acb8fd 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1599,7 +1599,7 @@ lookup_threadsafe_unicode(PyDictKeysObject *dk, PyObject *key, Py_hash_t hash, _ return DKIX_EMPTY; } if (_PyObject_HasDeferredRefcount(value)) { - *value_addr = (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_DEFERRED }; + *value_addr = (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_REFCNT }; return ix; } if (_Py_TryIncrefCompare(addr_of_value, value)) { diff --git a/PCbuild/_remote_debugging.vcxproj b/PCbuild/_remote_debugging.vcxproj index c91c9cf3652363..830b7b8744862c 100644 --- a/PCbuild/_remote_debugging.vcxproj +++ b/PCbuild/_remote_debugging.vcxproj @@ -105,6 +105,7 @@ + diff --git a/PCbuild/_remote_debugging.vcxproj.filters b/PCbuild/_remote_debugging.vcxproj.filters index b37a2c5575c9f5..793a3256c52d58 100644 --- a/PCbuild/_remote_debugging.vcxproj.filters +++ b/PCbuild/_remote_debugging.vcxproj.filters @@ -33,6 +33,9 @@ Source Files + + Source Files + diff --git a/Python/bytecodes.c b/Python/bytecodes.c index daa3d218e387f9..3e6147961f1822 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1158,9 +1158,9 @@ dummy_func( } macro(STORE_SUBSCR_DICT) = - _GUARD_NOS_DICT + unused/1 + _STORE_SUBSCR_DICT; + _GUARD_NOS_DICT + unused/1 + _STORE_SUBSCR_DICT + POP_TOP; - op(_STORE_SUBSCR_DICT, (value, dict_st, sub -- )) { + op(_STORE_SUBSCR_DICT, (value, dict_st, sub -- st)) { PyObject *dict = PyStackRef_AsPyObjectBorrow(dict_st); assert(PyDict_CheckExact(dict)); @@ -1168,8 +1168,12 @@ dummy_func( int err = _PyDict_SetItem_Take2((PyDictObject *)dict, PyStackRef_AsPyObjectSteal(sub), PyStackRef_AsPyObjectSteal(value)); - PyStackRef_CLOSE(dict_st); - ERROR_IF(err); + if (err) { + PyStackRef_CLOSE(dict_st); + ERROR_IF(1); + } + DEAD(dict_st); + st = dict_st; } inst(DELETE_SUBSCR, (container, sub --)) { @@ -4350,7 +4354,9 @@ dummy_func( _GUARD_CALLABLE_LIST_APPEND + _GUARD_NOS_NOT_NULL + _GUARD_NOS_LIST + - _CALL_LIST_APPEND; + _CALL_LIST_APPEND + + POP_TOP + + POP_TOP; op(_GUARD_CALLABLE_LIST_APPEND, (callable, unused, unused -- callable, unused, unused)){ PyObject *callable_o = PyStackRef_AsPyObjectBorrow(callable); @@ -4359,7 +4365,7 @@ dummy_func( } // This is secretly a super-instruction - op(_CALL_LIST_APPEND, (callable, self, arg -- )) { + op(_CALL_LIST_APPEND, (callable, self, arg -- c, s)) { assert(oparg == 1); PyObject *self_o = PyStackRef_AsPyObjectBorrow(self); @@ -4367,9 +4373,12 @@ dummy_func( STAT_INC(CALL, hit); int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); - PyStackRef_CLOSE(self); - PyStackRef_CLOSE(callable); - ERROR_IF(err); + if (err) { + ERROR_NO_POP(); + } + c = callable; + s = self; + INPUTS_DEAD(); #if TIER_ONE // Skip the following POP_TOP. This is done here in tier one, and // during trace projection in tier two: diff --git a/Python/context.c b/Python/context.c index 2f978b1c0abc43..620e78ab1f9ec8 100644 --- a/Python/context.c +++ b/Python/context.c @@ -190,7 +190,7 @@ context_switched(PyThreadState *ts) } -static int +int _PyContext_Enter(PyThreadState *ts, PyObject *octx) { ENSURE_Context(octx, -1) @@ -220,7 +220,7 @@ PyContext_Enter(PyObject *octx) } -static int +int _PyContext_Exit(PyThreadState *ts, PyObject *octx) { ENSURE_Context(octx, -1) diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 2a1156091e3d37..e7f6bb2ed0ce0d 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -5765,12 +5765,13 @@ break; } - case _STORE_SUBSCR_DICT_r30: { + case _STORE_SUBSCR_DICT_r31: { CHECK_CURRENT_CACHED_VALUES(3); assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); _PyStackRef sub; _PyStackRef dict_st; _PyStackRef value; + _PyStackRef st; _PyStackRef _stack_item_0 = _tos_cache0; _PyStackRef _stack_item_1 = _tos_cache1; _PyStackRef _stack_item_2 = _tos_cache2; @@ -5790,19 +5791,22 @@ PyStackRef_AsPyObjectSteal(sub), PyStackRef_AsPyObjectSteal(value)); stack_pointer = _PyFrame_GetStackPointer(frame); - stack_pointer += -3; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(dict_st); - stack_pointer = _PyFrame_GetStackPointer(frame); if (err) { + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(dict_st); + stack_pointer = _PyFrame_GetStackPointer(frame); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } - _tos_cache0 = PyStackRef_ZERO_BITS; + st = dict_st; + _tos_cache0 = st; _tos_cache1 = PyStackRef_ZERO_BITS; _tos_cache2 = PyStackRef_ZERO_BITS; - SET_CURRENT_CACHED_VALUES(0); + SET_CURRENT_CACHED_VALUES(1); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); break; } @@ -13653,12 +13657,148 @@ break; } - case _CALL_LIST_APPEND_r30: { + case _CALL_LIST_APPEND_r02: { + CHECK_CURRENT_CACHED_VALUES(0); + assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); + _PyStackRef arg; + _PyStackRef self; + _PyStackRef callable; + _PyStackRef c; + _PyStackRef s; + oparg = CURRENT_OPARG(); + arg = stack_pointer[-1]; + self = stack_pointer[-2]; + callable = stack_pointer[-3]; + assert(oparg == 1); + PyObject *self_o = PyStackRef_AsPyObjectBorrow(self); + if (!LOCK_OBJECT(self_o)) { + UOP_STAT_INC(uopcode, miss); + SET_CURRENT_CACHED_VALUES(0); + JUMP_TO_JUMP_TARGET(); + } + STAT_INC(CALL, hit); + int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); + UNLOCK_OBJECT(self_o); + if (err) { + SET_CURRENT_CACHED_VALUES(0); + JUMP_TO_ERROR(); + } + c = callable; + s = self; + #if TIER_ONE + + assert(next_instr->op.code == POP_TOP); + SKIP_OVER(1); + #endif + _tos_cache1 = s; + _tos_cache0 = c; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); + break; + } + + case _CALL_LIST_APPEND_r12: { + CHECK_CURRENT_CACHED_VALUES(1); + assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); + _PyStackRef arg; + _PyStackRef self; + _PyStackRef callable; + _PyStackRef c; + _PyStackRef s; + _PyStackRef _stack_item_0 = _tos_cache0; + oparg = CURRENT_OPARG(); + arg = _stack_item_0; + self = stack_pointer[-1]; + callable = stack_pointer[-2]; + assert(oparg == 1); + PyObject *self_o = PyStackRef_AsPyObjectBorrow(self); + if (!LOCK_OBJECT(self_o)) { + UOP_STAT_INC(uopcode, miss); + SET_CURRENT_CACHED_VALUES(1); + JUMP_TO_JUMP_TARGET(); + } + STAT_INC(CALL, hit); + int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); + UNLOCK_OBJECT(self_o); + if (err) { + stack_pointer += 1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + SET_CURRENT_CACHED_VALUES(0); + JUMP_TO_ERROR(); + } + c = callable; + s = self; + #if TIER_ONE + + assert(next_instr->op.code == POP_TOP); + SKIP_OVER(1); + #endif + _tos_cache1 = s; + _tos_cache0 = c; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); + break; + } + + case _CALL_LIST_APPEND_r22: { + CHECK_CURRENT_CACHED_VALUES(2); + assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); + _PyStackRef arg; + _PyStackRef self; + _PyStackRef callable; + _PyStackRef c; + _PyStackRef s; + _PyStackRef _stack_item_0 = _tos_cache0; + _PyStackRef _stack_item_1 = _tos_cache1; + oparg = CURRENT_OPARG(); + arg = _stack_item_1; + self = _stack_item_0; + callable = stack_pointer[-1]; + assert(oparg == 1); + PyObject *self_o = PyStackRef_AsPyObjectBorrow(self); + if (!LOCK_OBJECT(self_o)) { + UOP_STAT_INC(uopcode, miss); + SET_CURRENT_CACHED_VALUES(2); + JUMP_TO_JUMP_TARGET(); + } + STAT_INC(CALL, hit); + int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); + UNLOCK_OBJECT(self_o); + if (err) { + stack_pointer[0] = self; + stack_pointer += 2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + SET_CURRENT_CACHED_VALUES(0); + JUMP_TO_ERROR(); + } + c = callable; + s = self; + #if TIER_ONE + + assert(next_instr->op.code == POP_TOP); + SKIP_OVER(1); + #endif + _tos_cache1 = s; + _tos_cache0 = c; + SET_CURRENT_CACHED_VALUES(2); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); + break; + } + + case _CALL_LIST_APPEND_r32: { CHECK_CURRENT_CACHED_VALUES(3); assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); _PyStackRef arg; _PyStackRef self; _PyStackRef callable; + _PyStackRef c; + _PyStackRef s; _PyStackRef _stack_item_0 = _tos_cache0; _PyStackRef _stack_item_1 = _tos_cache1; _PyStackRef _stack_item_2 = _tos_cache2; @@ -13676,30 +13816,24 @@ STAT_INC(CALL, hit); int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); - stack_pointer[0] = callable; - stack_pointer += 1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(self); - stack_pointer = _PyFrame_GetStackPointer(frame); - stack_pointer += -1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(callable); - stack_pointer = _PyFrame_GetStackPointer(frame); if (err) { + stack_pointer[0] = callable; + stack_pointer[1] = self; + stack_pointer += 3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); SET_CURRENT_CACHED_VALUES(0); JUMP_TO_ERROR(); } + c = callable; + s = self; #if TIER_ONE assert(next_instr->op.code == POP_TOP); SKIP_OVER(1); #endif - _tos_cache0 = PyStackRef_ZERO_BITS; - _tos_cache1 = PyStackRef_ZERO_BITS; - _tos_cache2 = PyStackRef_ZERO_BITS; - SET_CURRENT_CACHED_VALUES(0); + _tos_cache1 = s; + _tos_cache0 = c; + SET_CURRENT_CACHED_VALUES(2); assert(WITHIN_STACK_BOUNDS_WITH_CACHE()); break; } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 7ba94d5381b72e..04b9b8f3f85603 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -290,7 +290,7 @@ frame_disable_deferred_refcounting(_PyInterpreterFrame *frame) frame->f_funcobj = PyStackRef_AsStrongReference(frame->f_funcobj); for (_PyStackRef *ref = frame->localsplus; ref < frame->stackpointer; ref++) { - if (!PyStackRef_IsNullOrInt(*ref) && PyStackRef_IsDeferred(*ref)) { + if (!PyStackRef_IsNullOrInt(*ref) && !PyStackRef_RefcountOnObject(*ref)) { *ref = PyStackRef_AsStrongReference(*ref); } } @@ -458,7 +458,7 @@ gc_visit_heaps(PyInterpreterState *interp, mi_block_visit_fun *visitor, static inline void gc_visit_stackref(_PyStackRef stackref) { - if (PyStackRef_IsDeferred(stackref) && !PyStackRef_IsNullOrInt(stackref)) { + if (!PyStackRef_IsNullOrInt(stackref) && !PyStackRef_RefcountOnObject(stackref)) { PyObject *obj = PyStackRef_AsPyObjectBorrow(stackref); if (_PyObject_GC_IS_TRACKED(obj) && !gc_is_frozen(obj)) { gc_add_refs(obj, 1); @@ -1828,7 +1828,7 @@ _PyGC_VisitStackRef(_PyStackRef *ref, visitproc visit, void *arg) // computing the incoming references, but otherwise treat them like // regular references. assert(!PyStackRef_IsTaggedInt(*ref)); - if (!PyStackRef_IsDeferred(*ref) || + if (PyStackRef_RefcountOnObject(*ref) || (visit != visit_decref && visit != visit_decref_unreachable)) { Py_VISIT(PyStackRef_AsPyObjectBorrow(*ref)); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index ab9373e0af5afc..9c828cba877ad4 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3241,6 +3241,9 @@ _PyStackRef nos; _PyStackRef self; _PyStackRef arg; + _PyStackRef c; + _PyStackRef s; + _PyStackRef value; /* Skip 1 cache entry */ /* Skip 2 cache entries */ // _GUARD_CALLABLE_LIST_APPEND @@ -3287,25 +3290,36 @@ STAT_INC(CALL, hit); int err = _PyList_AppendTakeRef((PyListObject *)self_o, PyStackRef_AsPyObjectSteal(arg)); UNLOCK_OBJECT(self_o); - stack_pointer += -2; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(self); - stack_pointer = _PyFrame_GetStackPointer(frame); - stack_pointer += -1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(callable); - stack_pointer = _PyFrame_GetStackPointer(frame); if (err) { JUMP_TO_LABEL(error); } + c = callable; + s = self; #if TIER_ONE assert(next_instr->op.code == POP_TOP); SKIP_OVER(1); #endif } + // _POP_TOP + { + value = s; + stack_pointer[-3] = c; + stack_pointer += -2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_XCLOSE(value); + stack_pointer = _PyFrame_GetStackPointer(frame); + } + // _POP_TOP + { + value = c; + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_XCLOSE(value); + stack_pointer = _PyFrame_GetStackPointer(frame); + } DISPATCH(); } @@ -10973,6 +10987,7 @@ _PyStackRef value; _PyStackRef dict_st; _PyStackRef sub; + _PyStackRef st; // _GUARD_NOS_DICT { nos = stack_pointer[-2]; @@ -10997,14 +11012,24 @@ PyStackRef_AsPyObjectSteal(sub), PyStackRef_AsPyObjectSteal(value)); stack_pointer = _PyFrame_GetStackPointer(frame); + if (err) { + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_CLOSE(dict_st); + stack_pointer = _PyFrame_GetStackPointer(frame); + JUMP_TO_LABEL(error); + } + st = dict_st; + } + // _POP_TOP + { + value = st; stack_pointer += -3; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(dict_st); + PyStackRef_XCLOSE(value); stack_pointer = _PyFrame_GetStackPointer(frame); - if (err) { - JUMP_TO_LABEL(error); - } } DISPATCH(); } diff --git a/Python/import.c b/Python/import.c index f4decf6e5cd247..db433dbc971d76 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2383,12 +2383,12 @@ is_builtin(PyObject *name) return 0; } -static PyModInitFunction -lookup_inittab_initfunc(const struct _Py_ext_module_loader_info* info) +static struct _inittab* +lookup_inittab_entry(const struct _Py_ext_module_loader_info* info) { for (struct _inittab *p = INITTAB; p->name != NULL; p++) { if (_PyUnicode_EqualToASCIIString(info->name, p->name)) { - return (PyModInitFunction)p->initfunc; + return p; } } // not found @@ -2430,16 +2430,28 @@ create_builtin( _extensions_cache_delete(info.path, info.name); } - PyModInitFunction p0 = initfunc; - if (p0 == NULL) { - p0 = lookup_inittab_initfunc(&info); - if (p0 == NULL) { - /* Cannot re-init internal module ("sys" or "builtins") */ - assert(is_core_module(tstate->interp, info.name, info.path)); - mod = import_add_module(tstate, info.name); + PyModInitFunction p0 = NULL; + if (initfunc == NULL) { + struct _inittab *entry = lookup_inittab_entry(&info); + if (entry == NULL) { + mod = NULL; + _PyErr_SetModuleNotFoundError(name); goto finally; } + + p0 = (PyModInitFunction)entry->initfunc; } + else { + p0 = initfunc; + } + + if (p0 == NULL) { + /* Cannot re-init internal module ("sys" or "builtins") */ + assert(is_core_module(tstate->interp, info.name, info.path)); + mod = import_add_module(tstate, info.name); + goto finally; + } + #ifdef Py_GIL_DISABLED // This call (and the corresponding call to _PyImport_CheckGILForModule()) diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 66aecf7ef54355..5023f84213b359 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -109,6 +109,11 @@ dummy_func(void) { ss = sub_st; } + op(_STORE_SUBSCR_DICT, (value, dict_st, sub -- st)) { + (void)value; + st = dict_st; + } + op(_PUSH_NULL, (-- res)) { res = sym_new_null(ctx); } @@ -1005,6 +1010,12 @@ dummy_func(void) { sym_set_const(flag, Py_True); } + op(_CALL_LIST_APPEND, (callable, self, arg -- c, s)) { + (void)(arg); + c = callable; + s = self; + } + op(_GUARD_IS_FALSE_POP, (flag -- )) { if (sym_is_const(ctx, flag)) { PyObject *value = sym_get_const(ctx, flag); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 1a3d4ad50bd824..72564ea32db772 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1148,8 +1148,16 @@ } case _STORE_SUBSCR_DICT: { - CHECK_STACK_BOUNDS(-3); - stack_pointer += -3; + JitOptRef dict_st; + JitOptRef value; + JitOptRef st; + dict_st = stack_pointer[-2]; + value = stack_pointer[-3]; + (void)value; + st = dict_st; + CHECK_STACK_BOUNDS(-2); + stack_pointer[-3] = st; + stack_pointer += -2; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); break; } @@ -3109,8 +3117,21 @@ } case _CALL_LIST_APPEND: { - CHECK_STACK_BOUNDS(-3); - stack_pointer += -3; + JitOptRef arg; + JitOptRef self; + JitOptRef callable; + JitOptRef c; + JitOptRef s; + arg = stack_pointer[-1]; + self = stack_pointer[-2]; + callable = stack_pointer[-3]; + (void)(arg); + c = callable; + s = self; + CHECK_STACK_BOUNDS(-1); + stack_pointer[-3] = c; + stack_pointer[-2] = s; + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); break; } diff --git a/Tools/README b/Tools/README index 22d76dfdbcf4a4..90acb2614820ee 100644 --- a/Tools/README +++ b/Tools/README @@ -43,6 +43,8 @@ patchcheck Tools for checking and applying patches to the Python source cod peg_generator PEG-based parser generator (pegen) used for new parser. +pixi-packages Pixi package definitions for downstream from-source builds. + scripts A number of useful single-file programs, e.g. run_tests.py which runs the Python test suite. diff --git a/Tools/pixi-packages/README.md b/Tools/pixi-packages/README.md new file mode 100644 index 00000000000000..50c3315ac0e5fc --- /dev/null +++ b/Tools/pixi-packages/README.md @@ -0,0 +1,36 @@ +# CPython Pixi packages + +This directory contains definitions for [Pixi packages](https://pixi.sh/latest/reference/pixi_manifest/#the-package-section) +which can be built from the CPython source code. + +Downstream developers can make use of these packages by adding them as Git dependencies in a +[Pixi workspace](https://pixi.sh/latest/first_workspace/), like: + +```toml +[dependencies] +python = { git = "https://github.com/python/cpython", subdirectory = "Tools/pixi-packages/asan" } +``` + +This is particularly useful when developers need to build CPython from source +(for example, for an ASan-instrumented build), as it does not require any manual +clone or build steps. Instead, Pixi will automatically handle both the build +and installation of the package. + +Each package definition is contained in a subdirectory, but they share the build script +`build.sh` in this directory. Currently defined package variants: + +- `default` +- `asan`: ASan-instrumented build with `PYTHON_ASAN=1` + +## Maintenance + +- Keep the `version` fields in each `recipe.yaml` up to date with the Python version +- Keep the dependency requirements up to date in each `recipe.yaml` +- Update `build.sh` for any breaking changes in the `configure` and `make` workflow + +## Opportunities for future improvement + +- More package variants (such as TSan, UBSan) +- Support for Windows +- Using a single `pixi.toml` and `recipe.yaml` for all package variants is blocked on https://github.com/prefix-dev/pixi/issues/4599 +- A workaround can be removed from the build script once https://github.com/prefix-dev/rattler-build/issues/2012 is resolved diff --git a/Tools/pixi-packages/asan/pixi.toml b/Tools/pixi-packages/asan/pixi.toml new file mode 100644 index 00000000000000..001ff78fa5d8cb --- /dev/null +++ b/Tools/pixi-packages/asan/pixi.toml @@ -0,0 +1,8 @@ +[workspace] +channels = ["https://prefix.dev/conda-forge"] +platforms = ["osx-arm64", "linux-64"] +preview = ["pixi-build"] + +[package.build.backend] +name = "pixi-build-rattler-build" +version = "*" diff --git a/Tools/pixi-packages/asan/recipe.yaml b/Tools/pixi-packages/asan/recipe.yaml new file mode 100644 index 00000000000000..dea88394ad9fe2 --- /dev/null +++ b/Tools/pixi-packages/asan/recipe.yaml @@ -0,0 +1,63 @@ +context: + # Keep up to date + version: "3.15" + +package: + name: python + version: ${{ version }} + +source: + - path: ../../.. + +build: + files: + exclude: + - "*.o" + script: + file: ../build.sh + env: + PYTHON_VARIANT: "asan" + +# derived from https://github.com/conda-forge/python-feedstock/blob/main/recipe/meta.yaml +requirements: + build: + - ${{ compiler('c') }} + - ${{ compiler('cxx') }} + - make + - pkg-config + # configure script looks for llvm-ar for lto + - if: osx + then: + - llvm-tools + - if: linux + then: + - ld_impl_${{ target_platform }} + - binutils_impl_${{ target_platform }} + - clang-19 + - llvm-tools-19 + + host: + - bzip2 + - sqlite + - liblzma-devel + - zlib + - zstd + - openssl + - readline + - tk + # These two are just to get the headers needed for tk.h, but is unused + - xorg-libx11 + - xorg-xorgproto + - ncurses + - libffi + - if: linux + then: + - ld_impl_${{ target_platform }} + - libuuid + - libmpdec-devel + - expat + +about: + homepage: https://www.python.org/ + license: Python-2.0 + license_file: LICENSE diff --git a/Tools/pixi-packages/build.sh b/Tools/pixi-packages/build.sh new file mode 100644 index 00000000000000..120f1d6bb0088a --- /dev/null +++ b/Tools/pixi-packages/build.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +if [[ "${PYTHON_VARIANT}" == "asan" ]]; then + echo "BUILD TYPE: ASAN" + BUILD_DIR="../build_asan" + CONFIGURE_EXTRA="--with-address-sanitizer" + export PYTHON_ASAN="1" + export ASAN_OPTIONS="strict_init_order=true" +else + echo "BUILD TYPE: DEFAULT" + BUILD_DIR="../build" + CONFIGURE_EXTRA="" +fi + +mkdir -p "${BUILD_DIR}" +cd "${BUILD_DIR}" + +if [[ -f configure-done ]]; then + echo "Skipping configure step, already done." +else + "${SRC_DIR}/configure" \ + --prefix="${PREFIX}" \ + --oldincludedir="${BUILD_PREFIX}/${HOST}/sysroot/usr/include" \ + --enable-shared \ + --srcdir="${SRC_DIR}" \ + ${CONFIGURE_EXTRA} +fi + +touch configure-done + +make -j"${CPU_COUNT}" install +ln -sf "${PREFIX}/bin/python3" "${PREFIX}/bin/python" + +# https://github.com/prefix-dev/rattler-build/issues/2012 +if [[ ${OSTYPE} == "darwin"* ]]; then + cp "${BUILD_PREFIX}/lib/clang/21/lib/darwin/libclang_rt.asan_osx_dynamic.dylib" "${PREFIX}/lib/libclang_rt.asan_osx_dynamic.dylib" +fi diff --git a/Tools/pixi-packages/default/pixi.toml b/Tools/pixi-packages/default/pixi.toml new file mode 100644 index 00000000000000..001ff78fa5d8cb --- /dev/null +++ b/Tools/pixi-packages/default/pixi.toml @@ -0,0 +1,8 @@ +[workspace] +channels = ["https://prefix.dev/conda-forge"] +platforms = ["osx-arm64", "linux-64"] +preview = ["pixi-build"] + +[package.build.backend] +name = "pixi-build-rattler-build" +version = "*" diff --git a/Tools/pixi-packages/default/recipe.yaml b/Tools/pixi-packages/default/recipe.yaml new file mode 100644 index 00000000000000..eeb4052ec3859c --- /dev/null +++ b/Tools/pixi-packages/default/recipe.yaml @@ -0,0 +1,61 @@ +context: + # Keep up to date + version: "3.15" + +package: + name: python + version: ${{ version }} + +source: + - path: ../../.. + +build: + files: + exclude: + - "*.o" + script: + file: ../build.sh + +# derived from https://github.com/conda-forge/python-feedstock/blob/main/recipe/meta.yaml +requirements: + build: + - ${{ compiler('c') }} + - ${{ compiler('cxx') }} + - make + - pkg-config + # configure script looks for llvm-ar for lto + - if: osx + then: + - llvm-tools + - if: linux + then: + - ld_impl_${{ target_platform }} + - binutils_impl_${{ target_platform }} + - clang-19 + - llvm-tools-19 + + host: + - bzip2 + - sqlite + - liblzma-devel + - zlib + - zstd + - openssl + - readline + - tk + # These two are just to get the headers needed for tk.h, but is unused + - xorg-libx11 + - xorg-xorgproto + - ncurses + - libffi + - if: linux + then: + - ld_impl_${{ target_platform }} + - libuuid + - libmpdec-devel + - expat + +about: + homepage: https://www.python.org/ + license: Python-2.0 + license_file: LICENSE