diff --git a/.gitattributes b/.gitattributes index 767ec620fba..e88d6ea13e2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -83,8 +83,10 @@ Include/opcode.h generated Include/opcode_ids.h generated Include/token.h generated Lib/_opcode_metadata.py generated -Lib/keyword.py generated Lib/idlelib/help.html generated +Lib/keyword.py generated +Lib/pydoc_data/topics.py generated +Lib/pydoc_data/module_docs.py generated Lib/test/certdata/*.pem generated Lib/test/certdata/*.0 generated Lib/test/levenshtein_examples.json generated diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index fac0fa8aba3..8810730e193 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -26,6 +26,7 @@ on: - "Tools/build/update_file.py" - "Tools/build/verify_ensurepip_wheels.py" - "Tools/cases_generator/**" + - "Tools/check-c-api-docs/**" - "Tools/clinic/**" - "Tools/jit/**" - "Tools/peg_generator/**" @@ -58,6 +59,7 @@ jobs: "Lib/tomllib", "Tools/build", "Tools/cases_generator", + "Tools/check-c-api-docs", "Tools/clinic", "Tools/jit", "Tools/peg_generator", diff --git a/.github/workflows/tail-call.yml b/.github/workflows/tail-call.yml index e99e317182e..76a8c05aa52 100644 --- a/.github/workflows/tail-call.yml +++ b/.github/workflows/tail-call.yml @@ -79,19 +79,17 @@ jobs: with: python-version: '3.11' - - name: Native Windows (debug) + - name: Native Windows MSVC (release) if: runner.os == 'Windows' && matrix.architecture != 'ARM64' shell: cmd run: | - choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}.1.0 - set PlatformToolset=clangcl - set LLVMToolsVersion=${{ matrix.llvm }}.1.0 - set LLVMInstallDir=C:\Program Files\LLVM - call ./PCbuild/build.bat --tail-call-interp -d -p ${{ matrix.architecture }} - call ./PCbuild/rt.bat -d -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3 + choco install visualstudio2026buildtools --no-progress -y --force --params "--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --locale en-US --passive" + $env:PATH = "C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\MSBuild\Current\bin;$env:PATH" + ./PCbuild/build.bat --tail-call-interp -c Release -p ${{ matrix.architecture }} "/p:PlatformToolset=v145" + ./PCbuild/rt.bat -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3 # No tests (yet): - - name: Emulated Windows (release) + - name: Emulated Windows Clang (release) if: runner.os == 'Windows' && matrix.architecture == 'ARM64' shell: cmd run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c5767ee841e..ee89e18db35 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,15 +40,15 @@ repos: files: ^Apple - id: ruff-format name: Run Ruff (format) on Doc/ - args: [--check] + args: [--exit-non-zero-on-fix] files: ^Doc/ - id: ruff-format name: Run Ruff (format) on Tools/build/check_warnings.py - args: [--check, --config=Tools/build/.ruff.toml] + args: [--exit-non-zero-on-fix, --config=Tools/build/.ruff.toml] files: ^Tools/build/check_warnings.py - id: ruff-format name: Run Ruff (format) on Tools/wasm/ - args: [--check, --config=Tools/wasm/.ruff.toml] + args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml] files: ^Tools/wasm/ - repo: https://github.com/psf/black-pre-commit-mirror diff --git a/Doc/Makefile b/Doc/Makefile index f16d9cacb1b..4d605980a62 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -140,7 +140,8 @@ doctest: pydoc-topics: BUILDER = pydoc-topics pydoc-topics: build @echo "Building finished; now run this:" \ - "cp build/pydoc-topics/topics.py ../Lib/pydoc_data/topics.py" + "cp build/pydoc-topics/topics.py ../Lib/pydoc_data/topics.py" \ + "&& cp build/pydoc-topics/module_docs.py ../Lib/pydoc_data/module_docs.py" .PHONY: gettext gettext: BUILDER = gettext diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index bb94bcb86a7..5e90d9b7bc9 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -107,6 +107,46 @@ header files properly declare the entry points to be ``extern "C"``. As a result there is no need to do anything special to use the API from C++. +.. _capi-system-includes: + +System includes +--------------- + + :file:`Python.h` includes several standard header files. + C extensions should include the standard headers that they use, + and should not rely on these implicit includes. + The implicit includes are: + + * ```` + * ```` (on Windows) + * ```` + * ```` + * ```` + * ```` + * ```` + * ```` (if present) + + The following are included for backwards compatibility, unless using + :ref:`Limited API ` 3.13 or newer: + + * ```` + * ```` (on POSIX) + + The following are included for backwards compatibility, unless using + :ref:`Limited API ` 3.11 or newer: + + * ```` + * ```` + * ```` + * ```` + +.. note:: + + Since Python may define some pre-processor definitions which affect the standard + headers on some systems, you *must* include :file:`Python.h` before any standard + headers are included. + + Useful macros ============= diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 185f20fbc6d..4e4b2e1d5f8 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -1,9 +1,9 @@ Pending removal in Python 3.20 ------------------------------ -* The ``__version__`` attribute has been deprecated in these standard library - modules and will be removed in Python 3.20. - Use :py:data:`sys.version_info` instead. +* The ``__version__``, ``version`` and ``VERSION`` attributes have been + deprecated in these standard library modules and will be removed in + Python 3.20. Use :py:data:`sys.version_info` instead. - :mod:`argparse` - :mod:`csv` @@ -24,6 +24,9 @@ Pending removal in Python 3.20 - :mod:`tkinter.font` - :mod:`tkinter.ttk` - :mod:`wsgiref.simple_server` + - :mod:`xml.etree.ElementTree` + - :mod:`!xml.sax.expatreader` + - :mod:`xml.sax.handler` - :mod:`zlib` (Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index f9b65643dfe..c0066d315d0 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -3,154 +3,20 @@ .. _extending-intro: -****************************** -Extending Python with C or C++ -****************************** +******************************** +Using the C API: Assorted topics +******************************** -It is quite easy to add new built-in modules to Python, if you know how to -program in C. Such :dfn:`extension modules` can do two things that can't be -done directly in Python: they can implement new built-in object types, and they -can call C library functions and system calls. - -To support extensions, the Python API (Application Programmers Interface) -defines a set of functions, macros and variables that provide access to most -aspects of the Python run-time system. The Python API is incorporated in a C -source file by including the header ``"Python.h"``. - -The compilation of an extension module depends on its intended use as well as on -your system setup; details are given in later chapters. - -.. note:: - - The C extension interface is specific to CPython, and extension modules do - not work on other Python implementations. In many cases, it is possible to - avoid writing C extensions and preserve portability to other implementations. - For example, if your use case is calling C library functions or system calls, - you should consider using the :mod:`ctypes` module or the `cffi - `_ library rather than writing - custom C code. - These modules let you write Python code to interface with C code and are more - portable between implementations of Python than writing and compiling a C - extension module. - - -.. _extending-simpleexample: - -A Simple Example -================ - -Let's create an extension module called ``spam`` (the favorite food of Monty -Python fans...) and let's say we want to create a Python interface to the C -library function :c:func:`system` [#]_. This function takes a null-terminated -character string as argument and returns an integer. We want this function to -be callable from Python as follows: - -.. code-block:: pycon - - >>> import spam - >>> status = spam.system("ls -l") - -Begin by creating a file :file:`spammodule.c`. (Historically, if a module is -called ``spam``, the C file containing its implementation is called -:file:`spammodule.c`; if the module name is very long, like ``spammify``, the -module name can be just :file:`spammify.c`.) - -The first two lines of our file can be:: - - #define PY_SSIZE_T_CLEAN - #include - -which pulls in the Python API (you can add a comment describing the purpose of -the module and a copyright notice if you like). - -.. note:: - - Since Python may define some pre-processor definitions which affect the standard - headers on some systems, you *must* include :file:`Python.h` before any standard - headers are included. - - ``#define PY_SSIZE_T_CLEAN`` was used to indicate that ``Py_ssize_t`` should be - used in some APIs instead of ``int``. - It is not necessary since Python 3.13, but we keep it here for backward compatibility. - See :ref:`arg-parsing-string-and-buffers` for a description of this macro. - -All user-visible symbols defined by :file:`Python.h` have a prefix of ``Py`` or -``PY``, except those defined in standard header files. - -.. tip:: - - For backward compatibility, :file:`Python.h` includes several standard header files. - C extensions should include the standard headers that they use, - and should not rely on these implicit includes. - If using the limited C API version 3.13 or newer, the implicit includes are: - - * ```` - * ```` (on Windows) - * ```` - * ```` - * ```` - * ```` - * ```` - * ```` (if present) - - If :c:macro:`Py_LIMITED_API` is not defined, or is set to version 3.12 or older, - the headers below are also included: - - * ```` - * ```` (on POSIX) - - If :c:macro:`Py_LIMITED_API` is not defined, or is set to version 3.10 or older, - the headers below are also included: - - * ```` - * ```` - * ```` - * ```` - -The next thing we add to our module file is the C function that will be called -when the Python expression ``spam.system(string)`` is evaluated (we'll see -shortly how it ends up being called):: - - static PyObject * - spam_system(PyObject *self, PyObject *args) - { - const char *command; - int sts; - - if (!PyArg_ParseTuple(args, "s", &command)) - return NULL; - sts = system(command); - return PyLong_FromLong(sts); - } - -There is a straightforward translation from the argument list in Python (for -example, the single expression ``"ls -l"``) to the arguments passed to the C -function. The C function always has two arguments, conventionally named *self* -and *args*. - -The *self* argument points to the module object for module-level functions; -for a method it would point to the object instance. - -The *args* argument will be a pointer to a Python tuple object containing the -arguments. Each item of the tuple corresponds to an argument in the call's -argument list. The arguments are Python objects --- in order to do anything -with them in our C function we have to convert them to C values. The function -:c:func:`PyArg_ParseTuple` in the Python API checks the argument types and -converts them to C values. It uses a template string to determine the required -types of the arguments as well as the types of the C variables into which to -store the converted values. More about this later. - -:c:func:`PyArg_ParseTuple` returns true (nonzero) if all arguments have the right -type and its components have been stored in the variables whose addresses are -passed. It returns false (zero) if an invalid argument list was passed. In the -latter case it also raises an appropriate exception so the calling function can -return ``NULL`` immediately (as we saw in the example). +The :ref:`tutorial ` walked you through +creating a C API extension module, but left many areas unexplained. +This document looks at several concepts that you'll need to learn +in order to write more complex extensions. .. _extending-errors: -Intermezzo: Errors and Exceptions -================================= +Errors and Exceptions +===================== An important convention throughout the Python interpreter is the following: when a function fails, it should set an exception condition and return an error value @@ -321,194 +187,14 @@ call to :c:func:`PyErr_SetString` as shown below:: } -.. _backtoexample: - -Back to the Example -=================== - -Going back to our example function, you should now be able to understand this -statement:: - - if (!PyArg_ParseTuple(args, "s", &command)) - return NULL; - -It returns ``NULL`` (the error indicator for functions returning object pointers) -if an error is detected in the argument list, relying on the exception set by -:c:func:`PyArg_ParseTuple`. Otherwise the string value of the argument has been -copied to the local variable :c:data:`!command`. This is a pointer assignment and -you are not supposed to modify the string to which it points (so in Standard C, -the variable :c:data:`!command` should properly be declared as ``const char -*command``). - -The next statement is a call to the Unix function :c:func:`system`, passing it -the string we just got from :c:func:`PyArg_ParseTuple`:: - - sts = system(command); - -Our :func:`!spam.system` function must return the value of :c:data:`!sts` as a -Python object. This is done using the function :c:func:`PyLong_FromLong`. :: - - return PyLong_FromLong(sts); - -In this case, it will return an integer object. (Yes, even integers are objects -on the heap in Python!) - -If you have a C function that returns no useful argument (a function returning -:c:expr:`void`), the corresponding Python function must return ``None``. You -need this idiom to do so (which is implemented by the :c:macro:`Py_RETURN_NONE` -macro):: - - Py_INCREF(Py_None); - return Py_None; - -:c:data:`Py_None` is the C name for the special Python object ``None``. It is a -genuine Python object rather than a ``NULL`` pointer, which means "error" in most -contexts, as we have seen. - - -.. _methodtable: - -The Module's Method Table and Initialization Function -===================================================== - -I promised to show how :c:func:`!spam_system` is called from Python programs. -First, we need to list its name and address in a "method table":: - - static PyMethodDef spam_methods[] = { - ... - {"system", spam_system, METH_VARARGS, - "Execute a shell command."}, - ... - {NULL, NULL, 0, NULL} /* Sentinel */ - }; - -Note the third entry (``METH_VARARGS``). This is a flag telling the interpreter -the calling convention to be used for the C function. It should normally always -be ``METH_VARARGS`` or ``METH_VARARGS | METH_KEYWORDS``; a value of ``0`` means -that an obsolete variant of :c:func:`PyArg_ParseTuple` is used. - -When using only ``METH_VARARGS``, the function should expect the Python-level -parameters to be passed in as a tuple acceptable for parsing via -:c:func:`PyArg_ParseTuple`; more information on this function is provided below. - -The :c:macro:`METH_KEYWORDS` bit may be set in the third field if keyword -arguments should be passed to the function. In this case, the C function should -accept a third ``PyObject *`` parameter which will be a dictionary of keywords. -Use :c:func:`PyArg_ParseTupleAndKeywords` to parse the arguments to such a -function. - -The method table must be referenced in the module definition structure:: - - static struct PyModuleDef spam_module = { - ... - .m_methods = spam_methods, - ... - }; - -This structure, in turn, must be passed to the interpreter in the module's -initialization function. The initialization function must be named -:c:func:`!PyInit_name`, where *name* is the name of the module, and should be the -only non-\ ``static`` item defined in the module file:: - - PyMODINIT_FUNC - PyInit_spam(void) - { - return PyModuleDef_Init(&spam_module); - } - -Note that :c:macro:`PyMODINIT_FUNC` declares the function as ``PyObject *`` return type, -declares any special linkage declarations required by the platform, and for C++ -declares the function as ``extern "C"``. - -:c:func:`!PyInit_spam` is called when each interpreter imports its module -:mod:`!spam` for the first time. (See below for comments about embedding Python.) -A pointer to the module definition must be returned via :c:func:`PyModuleDef_Init`, -so that the import machinery can create the module and store it in ``sys.modules``. - -When embedding Python, the :c:func:`!PyInit_spam` function is not called -automatically unless there's an entry in the :c:data:`PyImport_Inittab` table. -To add the module to the initialization table, use :c:func:`PyImport_AppendInittab`, -optionally followed by an import of the module:: - - #define PY_SSIZE_T_CLEAN - #include - - int - main(int argc, char *argv[]) - { - PyStatus status; - PyConfig config; - PyConfig_InitPythonConfig(&config); - - /* Add a built-in module, before Py_Initialize */ - if (PyImport_AppendInittab("spam", PyInit_spam) == -1) { - fprintf(stderr, "Error: could not extend in-built modules table\n"); - exit(1); - } - - /* Pass argv[0] to the Python interpreter */ - status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]); - if (PyStatus_Exception(status)) { - goto exception; - } - - /* Initialize the Python interpreter. Required. - If this step fails, it will be a fatal error. */ - status = Py_InitializeFromConfig(&config); - if (PyStatus_Exception(status)) { - goto exception; - } - PyConfig_Clear(&config); - - /* Optionally import the module; alternatively, - import can be deferred until the embedded script - imports it. */ - PyObject *pmodule = PyImport_ImportModule("spam"); - if (!pmodule) { - PyErr_Print(); - fprintf(stderr, "Error: could not import module 'spam'\n"); - } - - // ... use Python C API here ... - - return 0; - - exception: - PyConfig_Clear(&config); - Py_ExitStatusException(status); - } - -.. note:: - - If you declare a global variable or a local static one, the module may - experience unintended side-effects on re-initialisation, for example when - removing entries from ``sys.modules`` or importing compiled modules into - multiple interpreters within a process - (or following a :c:func:`fork` without an intervening :c:func:`exec`). - If module state is not yet fully :ref:`isolated `, - authors should consider marking the module as having no support for subinterpreters - (via :c:macro:`Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED`). - -A more substantial example module is included in the Python source distribution -as :file:`Modules/xxlimited.c`. This file may be used as a template or simply -read as an example. - - .. _compilation: -Compilation and Linkage -======================= +Embedding an extension +====================== -There are two more things to do before you can use your new extension: compiling -and linking it with the Python system. If you use dynamic loading, the details -may depend on the style of dynamic loading your system uses; see the chapters -about building extension modules (chapter :ref:`building`) and additional -information that pertains only to building on Windows (chapter -:ref:`building-on-windows`) for more information about this. - -If you can't use dynamic loading, or if you want to make your module a permanent +If you want to make your module a permanent part of the Python interpreter, you will have to change the configuration setup -and rebuild the interpreter. Luckily, this is very simple on Unix: just place +and rebuild the interpreter. On Unix, place your file (:file:`spammodule.c` for example) in the :file:`Modules/` directory of an unpacked source distribution, add a line to the file :file:`Modules/Setup.local` describing your file: @@ -536,7 +222,7 @@ on the line in the configuration file as well, for instance: Calling Python Functions from C =============================== -So far we have concentrated on making C functions callable from Python. The +The tutorial concentrated on making C functions callable from Python. The reverse is also useful: calling Python functions from C. This is especially the case for libraries that support so-called "callback" functions. If a C interface makes use of callbacks, the equivalent Python often needs to provide a @@ -581,7 +267,7 @@ be part of a module definition:: } This function must be registered with the interpreter using the -:c:macro:`METH_VARARGS` flag; this is described in section :ref:`methodtable`. The +:c:macro:`METH_VARARGS` flag in :c:type:`PyMethodDef.ml_flags`. The :c:func:`PyArg_ParseTuple` function and its arguments are documented in section :ref:`parsetuple`. @@ -676,14 +362,21 @@ the above example, we use :c:func:`Py_BuildValue` to construct the dictionary. : Py_DECREF(result); +.. index:: single: PyArg_ParseTuple (C function) + .. _parsetuple: Extracting Parameters in Extension Functions ============================================ -.. index:: single: PyArg_ParseTuple (C function) +The :ref:`tutorial ` uses a ":c:data:`METH_O`" +function, which is limited to a single Python argument. +If you want more, you can use :c:data:`METH_VARARGS` instead. +With this flag, the C function will receive a :py:class:`tuple` of arguments +instead of a single object. -The :c:func:`PyArg_ParseTuple` function is declared as follows:: +For unpacking the tuple, CPython provides the :c:func:`PyArg_ParseTuple` +function, declared as follows:: int PyArg_ParseTuple(PyObject *arg, const char *format, ...); @@ -693,6 +386,19 @@ whose syntax is explained in :ref:`arg-parsing` in the Python/C API Reference Manual. The remaining arguments must be addresses of variables whose type is determined by the format string. +For example, to receive a single Python :py:class:`str` object and turn it +into a C buffer, you would use ``"s"`` as the format string:: + + const char *command; + if (!PyArg_ParseTuple(args, "s", &command)) { + return NULL; + } + +If an error is detected in the argument list, :c:func:`!PyArg_ParseTuple` +returns ``NULL`` (the error indicator for functions returning object pointers); +your function may return ``NULL``, relying on the exception set by +:c:func:`PyArg_ParseTuple`. + Note that while :c:func:`PyArg_ParseTuple` checks that the Python arguments have the required types, it cannot check the validity of the addresses of C variables passed to the call: if you make mistakes there, your code will probably crash or @@ -703,7 +409,6 @@ Note that any Python object references which are provided to the caller are Some example calls:: - #define PY_SSIZE_T_CLEAN #include :: @@ -773,6 +478,17 @@ Some example calls:: Keyword Parameters for Extension Functions ========================================== +If you also want your function to accept +:term:`keyword arguments `, use the :c:data:`METH_KEYWORDS` +flag in combination with :c:data:`METH_VARARGS`. +(:c:data:`!METH_KEYWORDS` can also be used with other flags; see its +documentation for the allowed combinations.) + +In this case, the C function should accept a third ``PyObject *`` parameter +which will be a dictionary of keywords. +Use :c:func:`PyArg_ParseTupleAndKeywords` to parse the arguments to such a +function. + .. index:: single: PyArg_ParseTupleAndKeywords (C function) The :c:func:`PyArg_ParseTupleAndKeywords` function is declared as follows:: @@ -833,19 +549,6 @@ Philbrick (philbrick@hks.com):: {NULL, NULL, 0, NULL} /* sentinel */ }; - static struct PyModuleDef keywdarg_module = { - .m_base = PyModuleDef_HEAD_INIT, - .m_name = "keywdarg", - .m_size = 0, - .m_methods = keywdarg_methods, - }; - - PyMODINIT_FUNC - PyInit_keywdarg(void) - { - return PyModuleDef_Init(&keywdarg_module); - } - .. _buildvalue: @@ -986,11 +689,11 @@ needed. Ownership of a reference can be transferred. There are three ways to dispose of an owned reference: pass it on, store it, or call :c:func:`Py_DECREF`. Forgetting to dispose of an owned reference creates a memory leak. -It is also possible to :dfn:`borrow` [#]_ a reference to an object. The +It is also possible to :dfn:`borrow` [#borrow]_ a reference to an object. The borrower of a reference should not call :c:func:`Py_DECREF`. The borrower must not hold on to the object longer than the owner from which it was borrowed. Using a borrowed reference after the owner has disposed of it risks using freed -memory and should be avoided completely [#]_. +memory and should be avoided completely [#dont-check-refcount]_. The advantage of borrowing over owning a reference is that you don't need to take care of disposing of the reference on all possible paths through the code @@ -1169,7 +872,7 @@ checking. The C function calling mechanism guarantees that the argument list passed to C functions (``args`` in the examples) is never ``NULL`` --- in fact it guarantees -that it is always a tuple [#]_. +that it is always a tuple [#old-calling-convention]_. It is a severe error to ever let a ``NULL`` pointer "escape" to the Python user. @@ -1226,8 +929,8 @@ the module whose functions one wishes to call might not have been loaded yet! Portability therefore requires not to make any assumptions about symbol visibility. This means that all symbols in extension modules should be declared ``static``, except for the module's initialization function, in order to -avoid name clashes with other extension modules (as discussed in section -:ref:`methodtable`). And it means that symbols that *should* be accessible from +avoid name clashes with other extension modules. And it means that symbols +that *should* be accessible from other extension modules must be exported in a different way. Python provides a special mechanism to pass C-level information (pointers) from @@ -1269,8 +972,9 @@ file corresponding to the module provides a macro that takes care of importing the module and retrieving its C API pointers; client modules only have to call this macro before accessing the C API. -The exporting module is a modification of the :mod:`!spam` module from section -:ref:`extending-simpleexample`. The function :func:`!spam.system` does not call +The exporting module is a modification of the :mod:`!spam` module from the +:ref:`tutorial `. +The function :func:`!spam.system` does not call the C library function :c:func:`system` directly, but a function :c:func:`!PySpam_System`, which would of course do something more complicated in reality (such as adding "spam" to every command). This function @@ -1412,15 +1116,14 @@ code distribution). .. rubric:: Footnotes -.. [#] An interface for this function already exists in the standard module :mod:`os` - --- it was chosen as a simple and straightforward example. +.. [#borrow] The metaphor of "borrowing" a reference is not completely correct: + the owner still has a copy of the reference. -.. [#] The metaphor of "borrowing" a reference is not completely correct: the owner - still has a copy of the reference. - -.. [#] Checking that the reference count is at least 1 **does not work** --- the +.. [#dont-check-refcount] Checking that the reference count is at least 1 + **does not work** --- the reference count itself could be in freed memory and may thus be reused for another object! -.. [#] These guarantees don't hold when you use the "old" style calling convention --- +.. [#old-calling-convention] These guarantees don't hold when you use the + "old" style calling convention --- this is still found in much existing code. diff --git a/Doc/extending/first-extension-module.rst b/Doc/extending/first-extension-module.rst new file mode 100644 index 00000000000..5bde785c49e --- /dev/null +++ b/Doc/extending/first-extension-module.rst @@ -0,0 +1,667 @@ +.. highlight:: c + + +.. _extending-simpleexample: +.. _first-extension-module: + +********************************* +Your first C API extension module +********************************* + +This tutorial will take you through creating a simple +Python extension module written in C or C++. + +We will use the low-level Python C API directly. +For easier ways to create extension modules, see +the :ref:`recommended third party tools `. + +The tutorial assumes basic knowledge about Python: you should be able to +define functions in Python code before starting to write them in C. +See :ref:`tutorial-index` for an introduction to Python itself. + +The tutorial should be approachable for anyone who can write a basic C library. +While we will mention several concepts that a C beginner would not be expected +to know, like ``static`` functions or linkage declarations, understanding these +is not necessary for success. + +We will focus on giving you a "feel" of what Python's C API is like. +It will not teach you important concepts, like error handling +and reference counting, which are covered in later chapters. + +We will assume that you use a Unix-like system (including macOS and +Linux), or Windows. +On other systems, you might need to adjust some details -- for example, +a system command name. + +You need to have a suitable C compiler and Python development headers installed. +On Linux, headers are often in a package like ``python3-dev`` +or ``python3-devel``. + +You need to be able to install Python packages. +This tutorial uses `pip `__ (``pip install``), but you +can substitute any tool that can build and install ``pyproject.toml``-based +projects, like `uv `_ (``uv pip install``). +Preferably, have a :ref:`virtual environment ` activated. + + +.. note:: + + This tutorial uses APIs that were added in CPython 3.15. + To create an extension that's compatible with earlier versions of CPython, + please follow an earlier version of this documentation. + + This tutorial uses C syntax added in C11 and C++20. + If your extension needs to be compatible with earlier standards, + please follow tutorials in documentation for Python 3.14 or below. + + +What we'll do +============= + +Let's create an extension module called ``spam`` [#why-spam]_, +which will include a Python interface to the C +standard library function :c:func:`system`. +This function is defined in ``stdlib.h``. +It takes a C string as argument, runs the argument as a system +command, and returns a result value as an integer. +A manual page for :c:func:`system` might summarize it this way:: + + #include + int system(const char *command); + +Note that like many functions in the C standard library, +this function is already exposed in Python. +In production, use :py:func:`os.system` or :py:func:`subprocess.run` +rather than the module you'll write here. + +We want this function to be callable from Python as follows: + +.. code-block:: pycon + + >>> import spam + >>> status = spam.system("whoami") + User Name + >>> status + 0 + +.. note:: + + The system command ``whoami`` prints out your username. + It's useful in tutorials like this one because it has the same name on + both Unix and Windows. + + +Start with the headers +====================== + +Begin by creating a directory for this tutorial, and switching to it +on the command line. +Then, create a file named :file:`spammodule.c` in your directory. +[#why-spammodule]_ + +In this file, we'll include two headers: :file:`Python.h` to pull in +all declarations of the Python C API, and :file:`stdlib.h` for the +:c:func:`system` function. [#stdlib-h]_ + +Add the following lines to :file:`spammodule.c`: + +.. literalinclude:: ../includes/capi-extension/spammodule-01.c + :start-at: + :end-at: + +Be sure to put :file:`stdlib.h`, and any other standard library includes, +*after* :file:`Python.h`. +On some systems, Python may define some pre-processor definitions +that affect the standard headers. + + +Running your build tool +======================= + +With only the includes in place, your extension won't do anything. +Still, it's a good time to compile it and try to import it. +This will ensure that your build tool works, so that you can make +and test incremental changes as you follow the rest of the text. + +CPython itself does not come with a tool to build extension modules; +it is recommended to use a third-party project for this. +In this tutorial, we'll use `meson-python`_. +(If you want to use another one, see :ref:`first-extension-other-tools`.) + +.. at the time of writing, meson-python has the least overhead for a + simple extension using PyModExport. + Change this if another tool makes things easier. + +``meson-python`` requires defining a "project" using two extra files. + +First, add ``pyproject.toml`` with these contents: + +.. code-block:: toml + + [build-system] + build-backend = 'mesonpy' + requires = ['meson-python'] + + [project] + # Placeholder project information + # (change this before distributing the module) + name = 'sampleproject' + version = '0' + +Then, create ``meson.build`` containing the following: + +.. code-block:: meson + + project('sampleproject', 'c') + + py = import('python').find_installation(pure: false) + + py.extension_module( + 'spam', # name of the importable Python module + 'spammodule.c', # the C source file + install: true, + ) + +.. note:: + + See `meson-python documentation `_ for details on + configuration. + +Now, build install the *project in the current directory* (``.``) via ``pip``: + +.. code-block:: sh + + python -m pip install . + +.. tip:: + + If you don't have ``pip`` installed, run ``python -m ensurepip``, + preferably in a :ref:`virtual environment `. + (Or, if you prefer another tool that can build and install + ``pyproject.toml``-based projects, use that.) + +.. _meson-python: https://mesonbuild.com/meson-python/ +.. _virtual environment: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#create-and-use-virtual-environments + +Note that you will need to run this command again every time you change your +extension. +Unlike Python, C has an explicit compilation step. + +When your extension is compiled and installed, start Python and try to +import it. +This should fail with the following exception: + +.. code-block:: pycon + + >>> import spam + Traceback (most recent call last): + ... + ImportError: dynamic module does not define module export function (PyModExport_spam or PyInit_spam) + + +Module export hook +================== + +The exception you got when you tried to import the module told you that Python +is looking for a "module export function", also known as a +:ref:`module export hook `. +Let's define one. + +First, add a prototype below the ``#include`` lines: + +.. literalinclude:: ../includes/capi-extension/spammodule-01.c + :start-after: /// Export hook prototype + :end-before: /// + +.. tip:: + The prototype is not strictly necessary, but some modern compilers emit + warnings without it. + It's generally better to add the prototype than to disable the warning. + +The :c:macro:`PyMODEXPORT_FUNC` macro declares the function's +return type, and adds any special linkage declarations needed +to make the function visible and usable when CPython loads it. + +After the prototype, add the function itself. +For now, make it return ``NULL``: + +.. code-block:: c + + PyMODEXPORT_FUNC + PyModExport_spam(void) + { + return NULL; + } + +Compile and load the module again. +You should get a different error this time. + +.. code-block:: pycon + + >>> import spam + Traceback (most recent call last): + ... + SystemError: module export hook for module 'spam' failed without setting an exception + +Simply returning ``NULL`` is *not* correct behavior for an export hook, +and CPython complains about it. +That's good -- it means that CPython found the function! +Let's now make it do something useful. + + +The slot table +============== + +Rather than ``NULL``, the export hook should return the information needed to +create a module. +Let's start with the basics: the name and docstring. + +The information should be defined in a ``static`` array of +:c:type:`PyModuleDef_Slot` entries, which are essentially key-value pairs. +Define this array just before your export hook: + +.. code-block:: c + + static PyModuleDef_Slot spam_slots[] = { + {Py_mod_name, "spam"}, + {Py_mod_doc, "A wonderful module with an example function"}, + {0, NULL} + }; + +For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C +strings -- that is, NUL-terminated, UTF-8 encoded byte arrays. + +Note the zero-filled sentinel entry at the end. +If you forget it, you'll trigger undefined behavior. + +The array is defined as ``static`` -- that is, not visible outside this ``.c`` file. +This will be a common theme. +CPython only needs to access the export hook; all global variables +and all other functions should generally be ``static``, so that they don't +clash with other extensions. + +Return this array from your export hook instead of ``NULL``: + +.. code-block:: c + :emphasize-lines: 4 + + PyMODEXPORT_FUNC + PyModExport_spam(void) + { + return spam_slots; + } + +Now, recompile and try it out: + +.. code-block:: pycon + + >>> import spam + >>> print(spam) + + +You have an extension module! +Try ``help(spam)`` to see the docstring. + +The next step will be adding a function. + + +.. _backtoexample: + +Exposing a function +=================== + +To expose the :c:func:`system` C function directly to Python, +we'll need to write a layer of glue code to convert arguments from Python +objects to C values, and the C return value back to Python. + +One of the simplest ways to write glue code is a ":c:data:`METH_O`" function, +which takes two Python objects and returns one. +All Python objects -- regardless of the Python type -- are represented in C +as pointers to the :c:type:`PyObject` structure. + +Add such a function above the slots array:: + + static PyObject * + spam_system(PyObject *self, PyObject *arg) + { + Py_RETURN_NONE; + } + +For now, we ignore the arguments, and use the :c:macro:`Py_RETURN_NONE` +macro, which expands to a ``return`` statement that properly returns +a Python :py:data:`None` object. + +Recompile your extension to make sure you don't have syntax errors. +We haven't yet added ``spam_system`` to the module, so you might get a +warning that ``spam_system`` is unused. + +.. _methodtable: + +Method definitions +------------------ + +To expose the C function to Python, you will need to provide several pieces of +information in a structure called +:c:type:`PyMethodDef` [#why-pymethoddef]_: + +* ``ml_name``: the name of the Python function; +* ``ml_doc``: a docstring; +* ``ml_meth``: the C function to be called; and +* ``ml_flags``: a set of flags describing details like how Python arguments are + passed to the C function. + We'll use :c:data:`METH_O` here -- the flag that matches our + ``spam_system`` function's signature. + +Because modules typically create several functions, these definitions +need to be collected in an array, with a zero-filled sentinel at the end. +Add this array just below the ``spam_system`` function: + +.. literalinclude:: ../includes/capi-extension/spammodule-01.c + :start-after: /// Module method table + :end-before: /// + +As with module slots, a zero-filled sentinel marks the end of the array. + +Next, we'll add the method to the module. +Add a :c:data:`Py_mod_methods` slot to your :c:type:`PyMethodDef` array: + +.. literalinclude:: ../includes/capi-extension/spammodule-01.c + :start-after: /// Module slot table + :end-before: /// + :emphasize-lines: 5 + +Recompile your extension again, and test it. +Be sure to restart the Python interpreter, so that ``import spam`` picks +up the new version of the module. + +You should now be able to call the function: + +.. code-block:: pycon + + >>> import spam + >>> print(spam.system) + + >>> print(spam.system('whoami')) + None + +Note that our ``spam.system`` does not yet run the ``whoami`` command; +it only returns ``None``. + +Check that the function accepts exactly one argument, as specified by +the :c:data:`METH_O` flag: + +.. code-block:: pycon + + >>> print(spam.system('too', 'many', 'arguments')) + Traceback (most recent call last): + ... + TypeError: spam.system() takes exactly one argument (3 given) + + +Returning an integer +==================== + +Now, let's take a look at the return value. +Instead of ``None``, we'll want ``spam.system`` to return a number -- that is, +a Python :py:type:`int` object. +Eventually this will be the exit code of a system command, +but let's start with a fixed value, say, ``3``. + +The Python C API provides a function to create a Python :py:type:`int` object +from a C ``int`` value: :c:func:`PyLong_FromLong`. [#why-pylongfromlong]_ + +To call it, replace the ``Py_RETURN_NONE`` with the following 3 lines: + +.. this could be a one-liner, but we want to show the data types here + +.. code-block:: c + :emphasize-lines: 4-6 + + static PyObject * + spam_system(PyObject *self, PyObject *arg) + { + int status = 3; + PyObject *result = PyLong_FromLong(status); + return result; + } + + +Recompile, restart the Python interpreter again, +and check that the function now returns 3: + +.. code-block:: pycon + + >>> import spam + >>> spam.system('whoami') + 3 + + +Accepting a string +================== + +Finally, let's handle the function argument. + +Our C function, :c:func:`!spam_system`, takes two arguments. +The first one, ``PyObject *self``, will be set to the ``spam`` module +object. +This isn't useful in our case, so we'll ignore it. + +The other one, ``PyObject *arg``, will be set to the object that the user +passed from Python. +We expect that it should be a Python string. +In order to use the information in it, we will need +to convert it to a C value -- in this case, a C string (``const char *``). + +There's a slight type mismatch here: Python's :py:class:`str` objects store +Unicode text, but C strings are arrays of bytes. +So, we'll need to *encode* the data, and we'll use the UTF-8 encoding for it. +(UTF-8 might not always be correct for system commands, but it's what +:py:meth:`str.encode` uses by default, +and the C API has special support for it.) + +The function to encode a Python string into a UTF-8 buffer is named +:c:func:`PyUnicode_AsUTF8` [#why-pyunicodeasutf8]_. +Call it like this: + +.. code-block:: c + :emphasize-lines: 4 + + static PyObject * + spam_system(PyObject *self, PyObject *arg) + { + const char *command = PyUnicode_AsUTF8(arg); + int status = 3; + PyObject *result = PyLong_FromLong(status); + return result; + } + +If :c:func:`PyUnicode_AsUTF8` is successful, *command* will point to the +resulting array of bytes. +This buffer is managed by the *arg* object, which means we don't need to free +it, but we must follow some rules: + +* We should only use the buffer inside the ``spam_system`` function. + When ``spam_system`` returns, *arg* and the buffer it manages might be + garbage-collected. +* We must not modify it. This is why we use ``const``. + +If :c:func:`PyUnicode_AsUTF8` was *not* successful, it returns a ``NULL`` +pointer. +When calling *any* Python C API, we always need to handle such error cases. +The way to do this in general is left for later chapters of this documentation. +For now, be assured that we are already handling errors from +:c:func:`PyLong_FromLong` correctly. + +For the :c:func:`PyUnicode_AsUTF8` call, the correct way to handle errors is +returning ``NULL`` from ``spam_system``. +Add an ``if`` block for this: + + +.. code-block:: c + :emphasize-lines: 5-7 + + static PyObject * + spam_system(PyObject *self, PyObject *arg) + { + const char *command = PyUnicode_AsUTF8(arg); + if (command == NULL) { + return NULL; + } + int status = 3; + PyObject *result = PyLong_FromLong(status); + return result; + } + +That's it for the setup. +Now, all that is left is calling the C library function :c:func:`system` with +the ``char *`` buffer, and using its result instead of the ``3``: + +.. code-block:: c + :emphasize-lines: 8 + + static PyObject * + spam_system(PyObject *self, PyObject *arg) + { + const char *command = PyUnicode_AsUTF8(arg); + if (command == NULL) { + return NULL; + } + int status = system(command); + PyObject *result = PyLong_FromLong(status); + return result; + } + +Compile your module, restart Python, and test. +This time, you should see your username -- the output of the ``whoami`` +system command: + +.. code-block:: pycon + + >>> import spam + >>> result = spam.system('whoami') + User Name + >>> result + 0 + +You might also want to test error cases: + +.. code-block:: pycon + + >>> import spam + >>> result = spam.system('nonexistent-command') + sh: line 1: nonexistent-command: command not found + >>> result + 32512 + + >>> spam.system(3) + Traceback (most recent call last): + ... + TypeError: bad argument type for built-in operation + + +The result +========== + + +Congratulations! +You have written a complete Python C API extension module, +and completed this tutorial! + +Here is the entire source file, for your convenience: + +.. _extending-spammodule-source: + +.. literalinclude:: ../includes/capi-extension/spammodule-01.c + :start-at: /// + + +.. _first-extension-other-tools: + +Appendix: Other build tools +=========================== + +You should be able to follow this tutorial -- except the +*Running your build tool* section itself -- with a build tool other +than ``meson-python``. + +The Python Packaging User Guide has a `list of recommended tools `_; +be sure to choose one for the C language. + + +Workaround for missing PyInit function +-------------------------------------- + +If your build tool output complains about missing ``PyInit_spam``, +add the following function to your module for now: + +.. code-block:: c + + // A workaround + void *PyInit_spam(void) { return NULL; } + +This is a shim for an old-style :ref:`initialization function `, +which was required in extension modules for CPython 3.14 and below. +Current CPython does not need it, but some build tools may still assume that +all extension modules need to define it. + +If you use this workaround, you will get the exception +``SystemError: initialization of spam failed without raising an exception`` +instead of +``ImportError: dynamic module does not define module export function``. + + +Compiling directly +------------------ + +Using a third-party build tool is heavily recommended, +as it will take care of various details of your platform and Python +installation, of naming the resulting extension, and, later, of distributing +your work. + +If you are building an extension for as *specific* system, or for yourself +only, you might instead want to run your compiler directly. +The way to do this is system-specific; be prepared for issues you will need +to solve yourself. + +Linux +^^^^^ + +On Linux, the Python development package may include a ``python3-config`` +command that prints out the required compiler flags. +If you use it, check that it corresponds to the CPython interpreter you'll use +to load the module. +Then, start with the following command: + +.. code-block:: sh + + gcc --shared $(python3-config --cflags --ldflags) spammodule.c -o spam.so + +This should generate a ``spam.so`` file that you need to put in a directory +on :py:attr:`sys.path`. + + +.. rubric:: Footnotes + +.. [#why-spam] ``spam`` is the favorite food of Monty Python fans... +.. [#why-spammodule] The source file name is entirely up to you, + though some tools can be picky about the ``.c`` extension. + This tutorial uses the traditional ``*module.c`` suffix. + Some people would just use :file:`spam.c` to implement a module + named ``spam``, + projects where Python isn't the primary language might use ``py_spam.c``, + and so on. +.. [#stdlib-h] Including :file:`stdlib.h` is technically not necessary, + since :file:`Python.h` includes it and + :ref:`several other standard headers ` for its own use + or for backwards compatibility. + However, it is good practice to explicitly include what you need. +.. [#why-pymethoddef] The :c:type:`!PyMethodDef` structure is also used + to create methods of classes, so there's no separate + ":c:type:`!PyFunctionDef`". +.. [#why-pylongfromlong] The name :c:func:`PyLong_FromLong` + might not seem obvious. + ``PyLong`` refers to a the Python :py:class:`int`, which was originally + called ``long``; the ``FromLong`` refers to the C ``long`` (or ``long int``) + type. +.. [#why-pyunicodeasutf8] Here, ``PyUnicode`` refers to the original name of + the Python :py:class:`str` class: ``unicode``. diff --git a/Doc/extending/index.rst b/Doc/extending/index.rst index 4cc2c96d8d5..c0c494c3059 100644 --- a/Doc/extending/index.rst +++ b/Doc/extending/index.rst @@ -5,15 +5,17 @@ ################################################## This document describes how to write modules in C or C++ to extend the Python -interpreter with new modules. Those modules can not only define new functions -but also new object types and their methods. The document also describes how +interpreter with new modules. Those modules can do what Python code does -- +define functions, object types and methods -- but also interact with native +libraries or achieve better performance by avoiding the overhead of an +interpreter. The document also describes how to embed the Python interpreter in another application, for use as an extension language. Finally, it shows how to compile and link extension modules so that they can be loaded dynamically (at run time) into the interpreter, if the underlying operating system supports this feature. -This document assumes basic knowledge about Python. For an informal -introduction to the language, see :ref:`tutorial-index`. :ref:`reference-index` +This document assumes basic knowledge about C and Python. For an informal +introduction to Python, see :ref:`tutorial-index`. :ref:`reference-index` gives a more formal definition of the language. :ref:`library-index` documents the existing object types, functions and modules (both built-in and written in Python) that give the language its wide application range. @@ -21,37 +23,75 @@ Python) that give the language its wide application range. For a detailed description of the whole Python/C API, see the separate :ref:`c-api-index`. +To support extensions, Python's C API (Application Programmers Interface) +defines a set of functions, macros and variables that provide access to most +aspects of the Python run-time system. The Python API is incorporated in a C +source file by including the header ``"Python.h"``. + +.. note:: + + The C extension interface is specific to CPython, and extension modules do + not work on other Python implementations. In many cases, it is possible to + avoid writing C extensions and preserve portability to other implementations. + For example, if your use case is calling C library functions or system calls, + you should consider using the :mod:`ctypes` module or the `cffi + `_ library rather than writing + custom C code. + These modules let you write Python code to interface with C code and are more + portable between implementations of Python than writing and compiling a C + extension module. + + +.. toctree:: + :hidden: + + first-extension-module.rst + extending.rst + newtypes_tutorial.rst + newtypes.rst + building.rst + windows.rst + embedding.rst + Recommended third party tools ============================= -This guide only covers the basic tools for creating extensions provided +This document only covers the basic tools for creating extensions provided as part of this version of CPython. Some :ref:`third party tools ` offer both simpler and more sophisticated approaches to creating C and C++ extensions for Python. +While this document is aimed at extension authors, it should also be helpful to +the authors of such tools. +For example, the tutorial module can serve as a simple test case for a build +tool or sample expected output of a code generator. -Creating extensions without third party tools -============================================= + +C API Tutorial +============== + +This tutorial describes how to write a simple module in C or C++, +using the Python C API -- that is, using the basic tools provided +as part of this version of CPython. + + +#. :ref:`first-extension-module` + + +Guides for intermediate topics +============================== This section of the guide covers creating C and C++ extensions without assistance from third party tools. It is intended primarily for creators of those tools, rather than being a recommended way to create your own C extensions. -.. seealso:: - - :pep:`489` -- Multi-phase extension module initialization - -.. toctree:: - :maxdepth: 2 - :numbered: - - extending.rst - newtypes_tutorial.rst - newtypes.rst - building.rst - windows.rst +* :ref:`extending-intro` +* :ref:`defining-new-types` +* :ref:`new-types-topics` +* :ref:`building` +* :ref:`building-on-windows` Embedding the CPython runtime in a larger application ===================================================== @@ -61,8 +101,4 @@ interpreter as the main application, it is desirable to instead embed the CPython runtime inside a larger application. This section covers some of the details involved in doing that successfully. -.. toctree:: - :maxdepth: 2 - :numbered: - - embedding.rst +* :ref:`embedding` diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 3a01df99c38..68035c2dfb5 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -134,6 +134,14 @@ Glossary iterator's :meth:`~object.__anext__` method until it raises a :exc:`StopAsyncIteration` exception. Introduced by :pep:`492`. + atomic operation + An operation that appears to execute as a single, indivisible step: no + other thread can observe it half-done, and its effects become visible all + at once. Python does not guarantee that high-level statements are atomic + (for example, ``x += 1`` performs multiple bytecode operations and is not + atomic). Atomicity is only guaranteed where explicitly documented. See + also :term:`race condition` and :term:`data race`. + attached thread state A :term:`thread state` that is active for the current OS thread. @@ -289,6 +297,22 @@ Glossary advanced mathematical feature. If you're not aware of a need for them, it's almost certain you can safely ignore them. + concurrency + The ability of a computer program to perform multiple tasks at the same + time. Python provides libraries for writing programs that make use of + different forms of concurrency. :mod:`asyncio` is a library for dealing + with asynchronous tasks and coroutines. :mod:`threading` provides + access to operating system threads and :mod:`multiprocessing` to + operating system processes. Multi-core processors can execute threads and + processes on different CPU cores at the same time (see + :term:`parallelism`). + + concurrent modification + When multiple threads modify shared data at the same time. Concurrent + modification without proper synchronization can cause + :term:`race conditions `, and might also trigger a + :term:`data race `, data corruption, or both. + context This term has different meanings depending on where and how it is used. Some common meanings: @@ -363,6 +387,28 @@ Glossary the :term:`cyclic garbage collector ` is to identify these groups and break the reference cycles so that the memory can be reclaimed. + data race + A situation where multiple threads access the same memory location + concurrently, at least one of the accesses is a write, and the threads + do not use any synchronization to control their access. Data races + lead to :term:`non-deterministic` behavior and can cause data corruption. + Proper use of :term:`locks ` and other :term:`synchronization primitives + ` prevents data races. Note that data races + can only happen in native code, but that :term:`native code` might be + exposed in a Python API. See also :term:`race condition` and + :term:`thread-safe`. + + deadlock + A situation in which two or more tasks (threads, processes, or coroutines) + wait indefinitely for each other to release resources or complete actions, + preventing any from making progress. For example, if thread A holds lock + 1 and waits for lock 2, while thread B holds lock 2 and waits for lock 1, + both threads will wait indefinitely. In Python this often arises from + acquiring multiple locks in conflicting orders or from circular + join/await dependencies. Deadlocks can be avoided by always acquiring + multiple :term:`locks ` in a consistent order. See also + :term:`lock` and :term:`reentrant`. + decorator A function returning another function, usually applied as a function transformation using the ``@wrapper`` syntax. Common examples for @@ -662,6 +708,14 @@ Glossary requires the GIL to be held in order to use it. This refers to having an :term:`attached thread state`. + global state + Data that is accessible throughout a program, such as module-level + variables, class variables, or C static variables in :term:`extension modules + `. In multi-threaded programs, global state shared + between threads typically requires synchronization to avoid + :term:`race conditions ` and + :term:`data races `. + hash-based pyc A bytecode cache file that uses the hash rather than the last-modified time of the corresponding source file to determine its validity. See @@ -706,7 +760,9 @@ Glossary tuples. Such an object cannot be altered. A new object has to be created if a different value has to be stored. They play an important role in places where a constant hash value is needed, for example as a key - in a dictionary. + in a dictionary. Immutable objects are inherently :term:`thread-safe` + because their state cannot be modified after creation, eliminating concerns + about improperly synchronized :term:`concurrent modification`. import path A list of locations (or :term:`path entries `) that are @@ -796,8 +852,9 @@ Glossary CPython does not consistently apply the requirement that an iterator define :meth:`~iterator.__iter__`. - And also please note that the free-threading CPython does not guarantee - the thread-safety of iterator operations. + And also please note that :term:`free-threaded ` + CPython does not guarantee :term:`thread-safe` behavior of iterator + operations. key function @@ -835,10 +892,11 @@ Glossary :keyword:`if` statements. In a multi-threaded environment, the LBYL approach can risk introducing a - race condition between "the looking" and "the leaping". For example, the - code, ``if key in mapping: return mapping[key]`` can fail if another + :term:`race condition` between "the looking" and "the leaping". For example, + the code, ``if key in mapping: return mapping[key]`` can fail if another thread removes *key* from *mapping* after the test, but before the lookup. - This issue can be solved with locks or by using the EAFP approach. + This issue can be solved with :term:`locks ` or by using the + :term:`EAFP` approach. See also :term:`thread-safe`. lexical analyzer @@ -857,6 +915,19 @@ Glossary clause is optional. If omitted, all elements in ``range(256)`` are processed. + lock + A :term:`synchronization primitive` that allows only one thread at a + time to access a shared resource. A thread must acquire a lock before + accessing the protected resource and release it afterward. If a thread + attempts to acquire a lock that is already held by another thread, it + will block until the lock becomes available. Python's :mod:`threading` + module provides :class:`~threading.Lock` (a basic lock) and + :class:`~threading.RLock` (a :term:`reentrant` lock). Locks are used + to prevent :term:`race conditions ` and ensure + :term:`thread-safe` access to shared data. Alternative design patterns + to locks exist such as queues, producer/consumer patterns, and + thread-local state. See also :term:`deadlock`, and :term:`reentrant`. + loader An object that loads a module. It must define the :meth:`!exec_module` and :meth:`!create_module` methods @@ -942,8 +1013,11 @@ Glossary See :term:`method resolution order`. mutable - Mutable objects can change their value but keep their :func:`id`. See - also :term:`immutable`. + An :term:`object` with state that is allowed to change during the course + of the program. In multi-threaded programs, mutable objects that are + shared between threads require careful synchronization to avoid + :term:`race conditions `. See also :term:`immutable`, + :term:`thread-safe`, and :term:`concurrent modification`. named tuple The term "named tuple" applies to any type or class that inherits from @@ -995,6 +1069,13 @@ Glossary See also :term:`module`. + native code + Code that is compiled to machine instructions and runs directly on the + processor, as opposed to code that is interpreted or runs in a virtual + machine. In the context of Python, native code typically refers to + C, C++, Rust or Fortran code in :term:`extension modules ` + that can be called from Python. See also :term:`extension module`. + nested scope The ability to refer to a variable in an enclosing definition. For instance, a function defined inside another function can refer to @@ -1011,6 +1092,15 @@ Glossary properties, :meth:`~object.__getattribute__`, class methods, and static methods. + non-deterministic + Behavior where the outcome of a program can vary between executions with + the same inputs. In multi-threaded programs, non-deterministic behavior + often results from :term:`race conditions ` where the + relative timing or interleaving of threads affects the result. + Proper synchronization using :term:`locks ` and other + :term:`synchronization primitives ` helps + ensure deterministic behavior. + object Any data with state (attributes or value) and defined behavior (methods). Also the ultimate base class of any :term:`new-style @@ -1041,6 +1131,16 @@ Glossary See also :term:`regular package` and :term:`namespace package`. + parallelism + Executing multiple operations at the same time (e.g. on multiple CPU + cores). In Python builds with the + :term:`global interpreter lock (GIL) `, only one + thread runs Python bytecode at a time, so taking advantage of multiple + CPU cores typically involves multiple processes + (e.g. :mod:`multiprocessing`) or native extensions that release the GIL. + In :term:`free-threaded ` Python, multiple Python threads + can run Python code simultaneously on different cores. + parameter A named entity in a :term:`function` (or method) definition that specifies an :term:`argument` (or in some cases, arguments) that the @@ -1215,6 +1315,18 @@ Glossary >>> email.mime.text.__name__ 'email.mime.text' + race condition + A condition of a program where the its behavior + depends on the relative timing or ordering of events, particularly in + multi-threaded programs. Race conditions can lead to + :term:`non-deterministic` behavior and bugs that are difficult to + reproduce. A :term:`data race` is a specific type of race condition + involving unsynchronized access to shared memory. The :term:`LBYL` + coding style is particularly susceptible to race conditions in + multi-threaded code. Using :term:`locks ` and other + :term:`synchronization primitives ` + helps prevent race conditions. + reference count The number of references to an object. When the reference count of an object drops to zero, it is deallocated. Some objects are @@ -1236,6 +1348,25 @@ Glossary See also :term:`namespace package`. + reentrant + A property of a function or :term:`lock` that allows it to be called or + acquired multiple times by the same thread without causing errors or a + :term:`deadlock`. + + For functions, reentrancy means the function can be safely called again + before a previous invocation has completed, which is important when + functions may be called recursively or from signal handlers. Thread-unsafe + functions may be :term:`non-deterministic` if they're called reentrantly in a + multithreaded program. + + For locks, Python's :class:`threading.RLock` (reentrant lock) is + reentrant, meaning a thread that already holds the lock can acquire it + again without blocking. In contrast, :class:`threading.Lock` is not + reentrant - attempting to acquire it twice from the same thread will cause + a deadlock. + + See also :term:`lock` and :term:`deadlock`. + REPL An acronym for the "read–eval–print loop", another name for the :term:`interactive` interpreter shell. @@ -1340,6 +1471,18 @@ Glossary See also :term:`borrowed reference`. + synchronization primitive + A basic building block for coordinating (synchronizing) the execution of + multiple threads to ensure :term:`thread-safe` access to shared resources. + Python's :mod:`threading` module provides several synchronization primitives + including :class:`~threading.Lock`, :class:`~threading.RLock`, + :class:`~threading.Semaphore`, :class:`~threading.Condition`, + :class:`~threading.Event`, and :class:`~threading.Barrier`. Additionally, + the :mod:`queue` module provides multi-producer, multi-consumer queues + that are especially useful in multithreaded programs. These + primitives help prevent :term:`race conditions ` and + coordinate thread execution. See also :term:`lock`. + t-string t-strings String literals prefixed with ``t`` or ``T`` are commonly called @@ -1392,6 +1535,19 @@ Glossary See :ref:`Thread State and the Global Interpreter Lock ` for more information. + thread-safe + A module, function, or class that behaves correctly when used by multiple + threads concurrently. Thread-safe code uses appropriate + :term:`synchronization primitives ` like + :term:`locks ` to protect shared mutable state, or is designed + to avoid shared mutable state entirely. In the + :term:`free-threaded ` build, built-in types like + :class:`dict`, :class:`list`, and :class:`set` use internal locking + to make many operations thread-safe, although thread safety is not + necessarily guaranteed. Code that is not thread-safe may experience + :term:`race conditions ` and :term:`data races ` + when used in multi-threaded programs. + token A small unit of source code, generated by the diff --git a/Doc/includes/capi-extension/spammodule-01.c b/Doc/includes/capi-extension/spammodule-01.c new file mode 100644 index 00000000000..86c9840359d --- /dev/null +++ b/Doc/includes/capi-extension/spammodule-01.c @@ -0,0 +1,55 @@ +/* This file needs to be kept in sync with the tutorial + * at Doc/extending/first-extension-module.rst + */ + +/// Includes + +#include +#include // for system() + +/// Implementation of spam.system + +static PyObject * +spam_system(PyObject *self, PyObject *arg) +{ + const char *command = PyUnicode_AsUTF8(arg); + if (command == NULL) { + return NULL; + } + int status = system(command); + PyObject *result = PyLong_FromLong(status); + return result; +} + +/// Module method table + +static PyMethodDef spam_methods[] = { + { + .ml_name="system", + .ml_meth=spam_system, + .ml_flags=METH_O, + .ml_doc="Execute a shell command.", + }, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +/// Module slot table + +static PyModuleDef_Slot spam_slots[] = { + {Py_mod_name, "spam"}, + {Py_mod_doc, "A wonderful module with an example function"}, + {Py_mod_methods, spam_methods}, + {0, NULL} +}; + +/// Export hook prototype + +PyMODEXPORT_FUNC PyModExport_spam(void); + +/// Module export hook + +PyMODEXPORT_FUNC +PyModExport_spam(void) +{ + return spam_slots; +} diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 2e7d0dbc26e..bf37540e5fa 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -139,12 +139,13 @@ Node classes The :meth:`~object.__repr__` output of :class:`~ast.AST` nodes includes the values of the node fields. -.. deprecated:: 3.8 +.. deprecated-removed:: 3.8 3.14 - Old classes :class:`!ast.Num`, :class:`!ast.Str`, :class:`!ast.Bytes`, - :class:`!ast.NameConstant` and :class:`!ast.Ellipsis` are still available, - but they will be removed in future Python releases. In the meantime, - instantiating them will return an instance of a different class. + Previous versions of Python provided the AST classes :class:`!ast.Num`, + :class:`!ast.Str`, :class:`!ast.Bytes`, :class:`!ast.NameConstant` and + :class:`!ast.Ellipsis`, which were deprecated in Python 3.8. These classes + were removed in Python 3.14, and their functionality has been replaced with + :class:`ast.Constant`. .. deprecated:: 3.9 @@ -2419,12 +2420,12 @@ and classes for traversing abstract syntax trees: during traversal. For this a special visitor exists (:class:`NodeTransformer`) that allows modifications. - .. deprecated:: 3.8 + .. deprecated-removed:: 3.8 3.14 Methods :meth:`!visit_Num`, :meth:`!visit_Str`, :meth:`!visit_Bytes`, - :meth:`!visit_NameConstant` and :meth:`!visit_Ellipsis` are deprecated - now and will not be called in future Python versions. Add the - :meth:`visit_Constant` method to handle all constant nodes. + :meth:`!visit_NameConstant` and :meth:`!visit_Ellipsis` will not be called + in Python 3.14+. Add the :meth:`visit_Constant` method instead to handle + all constant nodes. .. class:: NodeTransformer() diff --git a/Doc/library/asyncio-queue.rst b/Doc/library/asyncio-queue.rst index d481a1921d5..a9735ae8065 100644 --- a/Doc/library/asyncio-queue.rst +++ b/Doc/library/asyncio-queue.rst @@ -107,7 +107,7 @@ Queue The queue can no longer grow. Future calls to :meth:`~Queue.put` raise :exc:`QueueShutDown`. Currently blocked callers of :meth:`~Queue.put` will be unblocked - and will raise :exc:`QueueShutDown` in the formerly blocked thread. + and will raise :exc:`QueueShutDown` in the formerly awaiting task. If *immediate* is false (the default), the queue can be wound down normally with :meth:`~Queue.get` calls to extract tasks diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 8ae1c1fb9e4..48e7080da6c 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -2651,9 +2651,42 @@ Broadly speaking, ``d.strftime(fmt)`` acts like the :mod:`time` module's ``time.strftime(fmt, d.timetuple())`` although not all objects support a :meth:`~date.timetuple` method. -For the :meth:`.datetime.strptime` class method, the default value is -``1900-01-01T00:00:00.000``: any components not specified in the format string -will be pulled from the default value. [#]_ +For the :meth:`.datetime.strptime` and :meth:`.date.strptime` class methods, +the default value is ``1900-01-01T00:00:00.000``: any components not specified +in the format string will be pulled from the default value. + +.. note:: + When used to parse partial dates lacking a year, :meth:`.datetime.strptime` + and :meth:`.date.strptime` will raise when encountering February 29 because + the default year of 1900 is *not* a leap year. Always add a default leap + year to partial date strings before parsing. + + +.. testsetup:: + + # doctest seems to turn the warning into an error which makes it + # show up and require matching and prevents the actual interesting + # exception from being raised. + # Manually apply the catch_warnings context manager + import warnings + catch_warnings = warnings.catch_warnings() + catch_warnings.__enter__() + warnings.simplefilter("ignore") + +.. testcleanup:: + + catch_warnings.__exit__() + +.. doctest:: + + >>> from datetime import datetime + >>> value = "2/29" + >>> datetime.strptime(value, "%m/%d") + Traceback (most recent call last): + ... + ValueError: day 29 must be in range 1..28 for month 2 in year 1900 + >>> datetime.strptime(f"1904 {value}", "%Y %m/%d") + datetime.datetime(1904, 2, 29, 0, 0) Using ``datetime.strptime(date_string, format)`` is equivalent to:: @@ -2790,7 +2823,7 @@ Notes: include a year in the format. If the value you need to parse lacks a year, append an explicit dummy leap year. Otherwise your code will raise an exception when it encounters leap day because the default year used by the - parser is not a leap year. Users run into this bug every four years... + parser (1900) is not a leap year. Users run into that bug every leap year. .. doctest:: @@ -2817,5 +2850,3 @@ Notes: .. [#] See R. H. van Gent's `guide to the mathematics of the ISO 8601 calendar `_ for a good explanation. - -.. [#] Passing ``datetime.strptime('Feb 29', '%b %d')`` will fail since 1900 is not a leap year. diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index a8a7e671aad..0da27ba8e78 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -947,12 +947,13 @@ Utilities and Decorators the member's name. Care must be taken if mixing *auto()* with manually specified values. - *auto* instances are only resolved when at the top level of an assignment: + *auto* instances are only resolved when at the top level of an assignment, either by + itself or as part of a tuple: * ``FIRST = auto()`` will work (auto() is replaced with ``1``); * ``SECOND = auto(), -2`` will work (auto is replaced with ``2``, so ``2, -2`` is used to create the ``SECOND`` enum member; - * ``THREE = [auto(), -3]`` will *not* work (``, -3`` is used to + * ``THREE = [auto(), -3]`` will *not* work (``[, -3]`` is used to create the ``THREE`` enum member) .. versionchanged:: 3.11.1 diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index f32aa322c40..41b90f2c3b3 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -328,6 +328,17 @@ To map anonymous memory, -1 should be passed as the fileno along with the length .. versionadded:: 3.13 + .. method:: set_name(name, /) + + Annotate the memory mapping with the given *name* for easier identification + in ``/proc//maps`` if the kernel supports the feature and :option:`-X dev <-X>` is passed + to Python or if Python is built in :ref:`debug mode `. + The length of *name* must not exceed 67 bytes including the ``'\0'`` terminator. + + .. availability:: Linux >= 5.17 (kernel built with ``CONFIG_ANON_VMA_NAME`` option) + + .. versionadded:: next + .. method:: size() Return the length of the file, which can be larger than the size of the diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 0bbdc425352..8ab3e7ec9ef 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -520,7 +520,8 @@ can be overridden by the local file. To remove all commands from a breakpoint, type ``commands`` and follow it immediately with ``end``; that is, give no commands. - With no *bpnumber* argument, ``commands`` refers to the last breakpoint set. + With no *bpnumber* argument, ``commands`` refers to the most recently set + breakpoint that still exists. You can use breakpoint commands to start your program up again. Simply use the :pdbcmd:`continue` command, or :pdbcmd:`step`, diff --git a/Doc/library/profiling.sampling.rst b/Doc/library/profiling.sampling.rst index 1f60e2cb578..41cb254174d 100644 --- a/Doc/library/profiling.sampling.rst +++ b/Doc/library/profiling.sampling.rst @@ -200,6 +200,36 @@ On most systems, attaching to another process requires appropriate permissions. See :ref:`profiling-permissions` for platform-specific requirements. +.. _replay-command: + +The ``replay`` command +---------------------- + +The ``replay`` command converts binary profile files to other output formats:: + + python -m profiling.sampling replay profile.bin + python -m profiling.sampling replay --flamegraph -o profile.html profile.bin + +This command is useful when you have captured profiling data in binary format +and want to analyze it later or convert it to a visualization format. Binary +profiles can be replayed multiple times to different formats without +re-profiling. + +:: + + # Convert binary to pstats (default, prints to stdout) + python -m profiling.sampling replay profile.bin + + # Convert binary to flame graph + python -m profiling.sampling replay --flamegraph -o output.html profile.bin + + # Convert binary to gecko format for Firefox Profiler + python -m profiling.sampling replay --gecko -o profile.json profile.bin + + # Convert binary to heatmap + python -m profiling.sampling replay --heatmap -o my_heatmap profile.bin + + Profiling in production ----------------------- @@ -1041,6 +1071,59 @@ intuitive view that shows exactly where time is spent without requiring interpretation of hierarchical visualizations. +Binary format +------------- + +Binary format (:option:`--binary`) produces a compact binary file for efficient +storage of profiling data:: + + python -m profiling.sampling run --binary -o profile.bin script.py + python -m profiling.sampling attach --binary -o profile.bin 12345 + +The :option:`--compression` option controls data compression: + +- ``auto`` (default): Use zstd compression if available, otherwise no + compression +- ``zstd``: Force zstd compression (requires :mod:`compression.zstd` support) +- ``none``: Disable compression + +:: + + python -m profiling.sampling run --binary --compression=zstd -o profile.bin script.py + +To analyze binary profiles, use the :ref:`replay-command` to convert them to +other formats like flame graphs or pstats output. + + +Record and replay workflow +========================== + +The binary format combined with the replay command enables a record-and-replay +workflow that separates data capture from analysis. Rather than generating +visualizations during profiling, you capture raw data to a compact binary file +and convert it to different formats later. + +This approach has three main benefits: + +- Sampling runs faster because the work of building data structures for + visualization is deferred until replay. +- A single binary capture can be converted to multiple output formats + without re-profiling: pstats for a quick overview, flame graph for visual + exploration, heatmap for line-level detail. +- Binary files are compact and easy to share with colleagues who can convert + them to their preferred format. + +A typical workflow:: + + # Capture profile in production or during tests + python -m profiling.sampling attach --binary -o profile.bin 12345 + + # Later, analyze with different formats + python -m profiling.sampling replay profile.bin + python -m profiling.sampling replay --flamegraph -o profile.html profile.bin + python -m profiling.sampling replay --heatmap -o heatmap profile.bin + + Live mode ========= @@ -1252,6 +1335,10 @@ Global options Attach to and profile a running process by PID. +.. option:: replay + + Convert a binary profile file to another output format. + Sampling options ---------------- @@ -1335,12 +1422,22 @@ Output options Generate HTML heatmap with line-level sample counts. +.. option:: --binary + + Generate high-performance binary format for later conversion with the + ``replay`` command. + +.. option:: --compression + + Compression for binary format: ``auto`` (use zstd if available, default), + ``zstd``, or ``none``. + .. option:: -o , --output Output file or directory path. Default behavior varies by format: - ``--pstats`` writes to stdout, ``--flamegraph`` and ``--gecko`` generate - files like ``flamegraph.PID.html``, and ``--heatmap`` creates a directory - named ``heatmap_PID``. + :option:`--pstats` writes to stdout, while other formats generate a file + named ``_.`` (for example, ``flamegraph_12345.html``). + :option:`--heatmap` creates a directory named ``heatmap_``. pstats display options diff --git a/Doc/library/random.rst b/Doc/library/random.rst index 4e55e301b89..6bddf575a80 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -78,7 +78,7 @@ Bookkeeping functions instead of the system time (see the :func:`os.urandom` function for details on availability). - If *a* is an int, it is used directly. + If *a* is an int, its absolute value is used directly. With version 2 (the default), a :class:`str`, :class:`bytes`, or :class:`bytearray` object gets converted to an :class:`int` and all of its bits are used. diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index f33b73238ec..7eaa9f48ab5 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -46,8 +46,10 @@ Any object can be tested for truth value, for use in an :keyword:`if` or By default, an object is considered true unless its class defines either a :meth:`~object.__bool__` method that returns ``False`` or a :meth:`~object.__len__` method that -returns zero, when called with the object. [1]_ Here are most of the built-in -objects considered false: +returns zero, when called with the object. [1]_ If one of the methods raises an +exception when called, the exception is propagated and the object does +not have a truth value (for example, :data:`NotImplemented`). +Here are most of the built-in objects considered false: .. index:: single: None (Built-in object) diff --git a/Doc/tools/extensions/pydoc_topics.py b/Doc/tools/extensions/pydoc_topics.py index 01efbba6283..a65d77433b2 100644 --- a/Doc/tools/extensions/pydoc_topics.py +++ b/Doc/tools/extensions/pydoc_topics.py @@ -109,6 +109,7 @@ class PydocTopicsBuilder(TextBuilder): def init(self) -> None: super().init() self.topics: dict[str, str] = {} + self.module_docs: dict[str, str] = {} def get_outdated_docs(self) -> str: # Return a string describing what an update build will build. @@ -130,6 +131,15 @@ class PydocTopicsBuilder(TextBuilder): continue doc_labels.setdefault(docname, []).append((topic_label, label_id)) + py_domain = env.domains['py'] + for module_name, module_info in py_domain.data['modules'].items(): + docname = module_info[0] + if docname.startswith('library/'): + doc_file = docname.replace('library/', '') + self.module_docs[module_name] = ( + f"{doc_file}#module-{module_name}" + ) + for docname, label_ids in status_iterator( doc_labels.items(), "building topics... ", @@ -161,6 +171,22 @@ topics = {{ """ self.outdir.joinpath("topics.py").write_text(topics, encoding="utf-8") + module_docs_repr = "\n".join( + f" '{module}': '{doc_file}'," + for module, doc_file in sorted(self.module_docs.items()) + ) + module_docs = f"""\ +# Autogenerated by Sphinx on {asctime()} +# as part of the release process. + +module_docs = {{ +{module_docs_repr} +}} +""" + self.outdir.joinpath("module_docs.py").write_text( + module_docs, encoding="utf-8" + ) + def _display_labels(item: tuple[str, Sequence[tuple[str, str]]]) -> str: _docname, label_ids = item diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 24a51f87c0f..aa138c9cacb 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -73,6 +73,7 @@ Summary -- Release highlights ` * :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object ` +* :ref:`The JIT compiler has been significantly upgraded ` * :ref:`Improved error messages ` @@ -591,6 +592,11 @@ mmap not be duplicated. (Contributed by Serhiy Storchaka in :gh:`78502`.) +* Added the :meth:`mmap.mmap.set_name` method + to annotate an anonymous memory mapping + if Linux kernel supports :manpage:`PR_SET_VMA_ANON_NAME ` (Linux 5.17 or newer). + (Contributed by Donghee Na in :gh:`142419`.) + os -- @@ -843,6 +849,16 @@ zlib Optimizations ============= +* Builds using Visual Studio 2026 (MSVC 18) may now use the new + :ref:`tail-calling interpreter `. + Results on an early experimental MSVC compiler reported roughly 15% speedup + on the geometric mean of pyperformance on Windows x86-64 over + the switch-case interpreter. We have + observed speedups ranging from 15% for large pure-Python libraries + to 40% for long-running small pure-Python scripts on Windows. + (Contributed by Chris Eibl, Ken Jin, and Brandt Bucher in :gh:`143068`. + Special thanks to the MSVC team including Hulon Jenkins.) + csv --- @@ -850,6 +866,91 @@ csv (Contributed by Maurycy Pawłowski-Wieroński in :gh:`137628`.) +.. _whatsnew315-jit: + +Upgraded JIT compiler +===================== + +Results from the `pyperformance `__ +benchmark suite report +`3-4% `__ +geometric mean performance improvement for the JIT over the standard CPython +interpreter built with all optimizations enabled. The speedups for JIT +builds versus no JIT builds range from roughly 20% slowdown to over +100% speedup (ignoring the ``unpack_sequence`` microbenchmark) on +x86-64 Linux and AArch64 macOS systems. + +.. attention:: + These results are not yet final. + +The major upgrades to the JIT are: + +* LLVM 21 build-time dependency +* New tracing frontend +* Basic register allocation in the JIT +* More JIT optimizations +* Better machine code generation + +.. rubric:: LLVM 21 build-time dependency + +The JIT compiler now uses LLVM 21 for build-time stencil generation. As +always, LLVM is only needed when building CPython with the JIT enabled; +end users running Python do not need LLVM installed. Instructions for +installing LLVM can be found in the `JIT compiler documentation +`__ +for all supported platforms. + +(Contributed by Savannah Ostrowski in :gh:`140973`.) + +.. rubric:: A new tracing frontend + +The JIT compiler now supports significantly more bytecode operations and +control flow than in Python 3.14, enabling speedups on a wider variety of +code. For example, simple Python object creation is now understood by the +3.15 JIT compiler. Overloaded operations and generators are also partially +supported. This was made possible by an overhauled JIT tracing frontend +that records actual execution paths through code, rather than estimating +them as the previous implementation did. + +(Contributed by Ken Jin in :gh:`139109`. Support for Windows added by +Mark Shannon in :gh:`141703`.) + +.. rubric:: Basic register allocation in the JIT + +A basic form of register allocation has been added to the JIT compiler's +optimizer. This allows the JIT compiler to avoid certain stack operations +altogether and instead operate on registers. This allows the JIT to produce +more efficient traces by avoiding reads and writes to memory. + +(Contributed by Mark Shannon in :gh:`135379`.) + +.. rubric:: More JIT optimizations + +More `constant-propagation `__ +is now performed. This means when the JIT compiler detects that certain user +code results in constants, the code can be simplified by the JIT. + +(Contributed by Ken Jin and Savannah Ostrowski in :gh:`132732`.) + +The JIT avoids :term:`reference count`\ s where possible. This generally +reduces the cost of most operations in Python. + +(Contributed by Ken Jin, Donghee Na, Zheao Li, Savannah Ostrowski, +Noam Cohen, Tomas Roun, PuQing in :gh:`134584`.) + +.. rubric:: Better machine code generation + +The JIT compiler's machine code generator now produces better machine code +for x86-64 and AArch64 macOS and Linux targets. In general, users should +experience lower memory usage for generated machine code and more efficient +machine code versus the old JIT. + +(Contributed by Brandt Bucher in :gh:`136528` and :gh:`136528`. +Implementation for AArch64 contributed by Mark Shannon in :gh:`139855`. +Additional optimizations for AArch64 contributed by Mark Shannon and +Diego Russo in :gh:`140683` and :gh:`142305`.) + + Removed ======= @@ -1018,9 +1119,9 @@ New deprecations * ``__version__`` - * The ``__version__`` attribute has been deprecated in these standard library - modules and will be removed in Python 3.20. - Use :py:data:`sys.version_info` instead. + * The ``__version__``, ``version`` and ``VERSION`` attributes have been + deprecated in these standard library modules and will be removed in + Python 3.20. Use :py:data:`sys.version_info` instead. - :mod:`argparse` - :mod:`csv` @@ -1041,6 +1142,9 @@ New deprecations - :mod:`tkinter.font` - :mod:`tkinter.ttk` - :mod:`wsgiref.simple_server` + - :mod:`xml.etree.ElementTree` + - :mod:`!xml.sax.expatreader` + - :mod:`xml.sax.handler` - :mod:`zlib` (Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.) diff --git a/Include/cpython/pyatomic.h b/Include/cpython/pyatomic.h index 790640309f1..ce907fd6a4c 100644 --- a/Include/cpython/pyatomic.h +++ b/Include/cpython/pyatomic.h @@ -523,6 +523,9 @@ _Py_atomic_store_uintptr_release(uintptr_t *obj, uintptr_t value); static inline void _Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value); +static inline void +_Py_atomic_store_int8_release(int8_t *obj, int8_t value); + static inline void _Py_atomic_store_int_release(int *obj, int value); diff --git a/Include/cpython/pyatomic_gcc.h b/Include/cpython/pyatomic_gcc.h index 1566b83b9f6..c045213c898 100644 --- a/Include/cpython/pyatomic_gcc.h +++ b/Include/cpython/pyatomic_gcc.h @@ -572,6 +572,10 @@ static inline void _Py_atomic_store_int_release(int *obj, int value) { __atomic_store_n(obj, value, __ATOMIC_RELEASE); } +static inline void +_Py_atomic_store_int8_release(int8_t *obj, int8_t value) +{ __atomic_store_n(obj, value, __ATOMIC_RELEASE); } + static inline void _Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value) { __atomic_store_n(obj, value, __ATOMIC_RELEASE); } diff --git a/Include/cpython/pyatomic_msc.h b/Include/cpython/pyatomic_msc.h index d155955df0c..8b9dd3eb0f8 100644 --- a/Include/cpython/pyatomic_msc.h +++ b/Include/cpython/pyatomic_msc.h @@ -1066,6 +1066,19 @@ _Py_atomic_store_int_release(int *obj, int value) #endif } +static inline void +_Py_atomic_store_int8_release(int8_t *obj, int8_t value) +{ +#if defined(_M_X64) || defined(_M_IX86) + *(int8_t volatile *)obj = value; +#elif defined(_M_ARM64) + _Py_atomic_ASSERT_ARG_TYPE(unsigned __int8); + __stlr8((unsigned __int8 volatile *)obj, (unsigned __int8)value); +#else +# error "no implementation of _Py_atomic_store_int8_release" +#endif +} + static inline void _Py_atomic_store_ssize_release(Py_ssize_t *obj, Py_ssize_t value) { diff --git a/Include/cpython/pyatomic_std.h b/Include/cpython/pyatomic_std.h index 7176f667a40..cfc8dbefc63 100644 --- a/Include/cpython/pyatomic_std.h +++ b/Include/cpython/pyatomic_std.h @@ -1023,6 +1023,14 @@ _Py_atomic_store_int_release(int *obj, int value) memory_order_release); } +static inline void +_Py_atomic_store_int8_release(int8_t *obj, int8_t value) +{ + _Py_USING_STD; + atomic_store_explicit((_Atomic(int8_t)*)obj, value, + memory_order_release); +} + static inline void _Py_atomic_store_uint_release(unsigned int *obj, unsigned int value) { diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index af53f2e7d6f..c6c82038d7c 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -123,7 +123,7 @@ _PyEval_EvalFrame(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwfl #ifdef _Py_TIER2 #ifdef _Py_JIT -_Py_CODEUNIT *_Py_LazyJitTrampoline( +_Py_CODEUNIT *_Py_LazyJitShim( struct _PyExecutorObject *current_executor, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate ); @@ -415,6 +415,17 @@ _Py_VectorCall_StackRefSteal( int total_args, _PyStackRef kwnames); +PyAPI_FUNC(PyObject*) +_Py_VectorCallInstrumentation_StackRefSteal( + _PyStackRef callable, + _PyStackRef* arguments, + int total_args, + _PyStackRef kwnames, + bool call_instrumentation, + _PyInterpreterFrame* frame, + _Py_CODEUNIT* this_instr, + PyThreadState* tstate); + PyAPI_FUNC(PyObject *) _Py_BuiltinCallFast_StackRefSteal( _PyStackRef callable, @@ -464,6 +475,11 @@ _Py_assert_within_stack_bounds( _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, const char *filename, int lineno); +// Like PyMapping_GetOptionalItem, but returns the PyObject* instead of taking +// it as an out parameter. This helps MSVC's escape analysis when used with +// tail calling. +PyAPI_FUNC(PyObject*) _PyMapping_GetOptionalItem2(PyObject* obj, PyObject* key, int* err); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 1193f496da1..a7005a3b8e2 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -272,8 +272,7 @@ _PyDict_SendEvent(int watcher_bits, PyObject *value); static inline void -_PyDict_NotifyEvent(PyInterpreterState *interp, - PyDict_WatchEvent event, +_PyDict_NotifyEvent(PyDict_WatchEvent event, PyDictObject *mp, PyObject *key, PyObject *value) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 56bc003ac3e..e625bf2fef1 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1653,9 +1653,11 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(co_varnames)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(code)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(col_offset)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(collector)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(command)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(comment_factory)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(compile_mode)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(compression)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(config)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(consts)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(context)); @@ -1718,7 +1720,9 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(event)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(eventmask)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_tb)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_type)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_val)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_value)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(excepthook)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exception)); @@ -1974,6 +1978,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(print_file_and_line)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(priority)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress_callback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(progress_routine)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(proto)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(protocol)); @@ -2014,6 +2019,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(reversed)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(rounding)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(salt)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sample_interval_us)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sched_priority)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(scheduler)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(script)); @@ -2053,8 +2059,10 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(spam)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(src)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(src_dir_fd)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stack_frames)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stacklevel)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(start)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(start_time_us)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(statement)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(stats)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(status)); @@ -2095,6 +2103,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(times)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timespec)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timestamp)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timestamp_us)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timetuple)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timeunit)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(top)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 8be948b92ec..771f0f8cb4a 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -376,9 +376,11 @@ struct _Py_global_strings { STRUCT_FOR_ID(co_varnames) STRUCT_FOR_ID(code) STRUCT_FOR_ID(col_offset) + STRUCT_FOR_ID(collector) STRUCT_FOR_ID(command) STRUCT_FOR_ID(comment_factory) STRUCT_FOR_ID(compile_mode) + STRUCT_FOR_ID(compression) STRUCT_FOR_ID(config) STRUCT_FOR_ID(consts) STRUCT_FOR_ID(context) @@ -441,7 +443,9 @@ struct _Py_global_strings { STRUCT_FOR_ID(event) STRUCT_FOR_ID(eventmask) STRUCT_FOR_ID(exc) + STRUCT_FOR_ID(exc_tb) STRUCT_FOR_ID(exc_type) + STRUCT_FOR_ID(exc_val) STRUCT_FOR_ID(exc_value) STRUCT_FOR_ID(excepthook) STRUCT_FOR_ID(exception) @@ -697,6 +701,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(print_file_and_line) STRUCT_FOR_ID(priority) STRUCT_FOR_ID(progress) + STRUCT_FOR_ID(progress_callback) STRUCT_FOR_ID(progress_routine) STRUCT_FOR_ID(proto) STRUCT_FOR_ID(protocol) @@ -737,6 +742,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(reversed) STRUCT_FOR_ID(rounding) STRUCT_FOR_ID(salt) + STRUCT_FOR_ID(sample_interval_us) STRUCT_FOR_ID(sched_priority) STRUCT_FOR_ID(scheduler) STRUCT_FOR_ID(script) @@ -776,8 +782,10 @@ struct _Py_global_strings { STRUCT_FOR_ID(spam) STRUCT_FOR_ID(src) STRUCT_FOR_ID(src_dir_fd) + STRUCT_FOR_ID(stack_frames) STRUCT_FOR_ID(stacklevel) STRUCT_FOR_ID(start) + STRUCT_FOR_ID(start_time_us) STRUCT_FOR_ID(statement) STRUCT_FOR_ID(stats) STRUCT_FOR_ID(status) @@ -818,6 +826,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(times) STRUCT_FOR_ID(timespec) STRUCT_FOR_ID(timestamp) + STRUCT_FOR_ID(timestamp_us) STRUCT_FOR_ID(timetuple) STRUCT_FOR_ID(timeunit) STRUCT_FOR_ID(top) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 6b3d5711b92..818c4f15959 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -947,7 +947,6 @@ struct _is { struct _PyExecutorObject *executor_deletion_list_head; struct _PyExecutorObject *cold_executor; struct _PyExecutorObject *cold_dynamic_executor; - int executor_deletion_list_remaining_capacity; size_t executor_creation_counter; _rare_events rare_events; PyDict_WatchCallback builtins_dict_watcher; diff --git a/Include/internal/pycore_mmap.h b/Include/internal/pycore_mmap.h index 214fd4362a5..897816db010 100644 --- a/Include/internal/pycore_mmap.h +++ b/Include/internal/pycore_mmap.h @@ -17,25 +17,27 @@ extern "C" { #endif #if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__) -static inline void +static inline int _PyAnnotateMemoryMap(void *addr, size_t size, const char *name) { #ifndef Py_DEBUG if (!_Py_GetConfig()->dev_mode) { - return; + return 0; } #endif + // The name length cannot exceed 80 (including the '\0'). assert(strlen(name) < 80); - int old_errno = errno; - prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name); - /* Ignore errno from prctl */ - /* See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746 */ - errno = old_errno; + int res = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name); + if (res < 0) { + return -1; + } + return 0; } #else -static inline void +static inline int _PyAnnotateMemoryMap(void *Py_UNUSED(addr), size_t Py_UNUSED(size), const char *Py_UNUSED(name)) { + return 0; } #endif diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index ce8a26c551b..e0d2e2a3c43 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1081,7 +1081,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [BINARY_OP] = { true, INSTR_FMT_IBC0000, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG }, [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG }, - [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG }, [BINARY_OP_EXTEND] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG }, @@ -1331,16 +1331,16 @@ _PyOpcode_macro_expansion[256] = { [BINARY_OP] = { .nuops = 1, .uops = { { _BINARY_OP, OPARG_SIMPLE, 4 } } }, [BINARY_OP_ADD_FLOAT] = { .nuops = 5, .uops = { { _GUARD_TOS_FLOAT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_FLOAT, OPARG_SIMPLE, 0 }, { _BINARY_OP_ADD_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 } } }, [BINARY_OP_ADD_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_INT, OPARG_SIMPLE, 0 }, { _BINARY_OP_ADD_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 } } }, - [BINARY_OP_ADD_UNICODE] = { .nuops = 3, .uops = { { _GUARD_TOS_UNICODE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_ADD_UNICODE, OPARG_SIMPLE, 5 } } }, + [BINARY_OP_ADD_UNICODE] = { .nuops = 5, .uops = { { _GUARD_TOS_UNICODE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_ADD_UNICODE, OPARG_SIMPLE, 5 }, { _POP_TOP_UNICODE, OPARG_SIMPLE, 5 }, { _POP_TOP_UNICODE, OPARG_SIMPLE, 5 } } }, [BINARY_OP_EXTEND] = { .nuops = 2, .uops = { { _GUARD_BINARY_OP_EXTEND, 4, 1 }, { _BINARY_OP_EXTEND, 4, 1 } } }, [BINARY_OP_INPLACE_ADD_UNICODE] = { .nuops = 3, .uops = { { _GUARD_TOS_UNICODE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_INPLACE_ADD_UNICODE, OPARG_SIMPLE, 5 } } }, [BINARY_OP_MULTIPLY_FLOAT] = { .nuops = 5, .uops = { { _GUARD_TOS_FLOAT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_FLOAT, OPARG_SIMPLE, 0 }, { _BINARY_OP_MULTIPLY_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 } } }, [BINARY_OP_MULTIPLY_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_INT, OPARG_SIMPLE, 0 }, { _BINARY_OP_MULTIPLY_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBSCR_DICT] = { .nuops = 2, .uops = { { _GUARD_NOS_DICT, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_DICT, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBSCR_GETITEM] = { .nuops = 4, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 5 }, { _BINARY_OP_SUBSCR_CHECK_FUNC, OPARG_SIMPLE, 5 }, { _BINARY_OP_SUBSCR_INIT_CALL, OPARG_SIMPLE, 5 }, { _PUSH_FRAME, OPARG_SIMPLE, 5 } } }, - [BINARY_OP_SUBSCR_LIST_INT] = { .nuops = 3, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_LIST_INT, OPARG_SIMPLE, 5 } } }, + [BINARY_OP_SUBSCR_LIST_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_LIST_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBSCR_LIST_SLICE] = { .nuops = 3, .uops = { { _GUARD_TOS_SLICE, OPARG_SIMPLE, 0 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_LIST_SLICE, OPARG_SIMPLE, 5 } } }, - [BINARY_OP_SUBSCR_STR_INT] = { .nuops = 3, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_STR_INT, OPARG_SIMPLE, 5 } } }, + [BINARY_OP_SUBSCR_STR_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_UNICODE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_STR_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBSCR_TUPLE_INT] = { .nuops = 3, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_TUPLE, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBSCR_TUPLE_INT, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBTRACT_FLOAT] = { .nuops = 5, .uops = { { _GUARD_TOS_FLOAT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_FLOAT, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBTRACT_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 }, { _POP_TOP_FLOAT, OPARG_SIMPLE, 5 } } }, [BINARY_OP_SUBTRACT_INT] = { .nuops = 5, .uops = { { _GUARD_TOS_INT, OPARG_SIMPLE, 0 }, { _GUARD_NOS_INT, OPARG_SIMPLE, 0 }, { _BINARY_OP_SUBTRACT_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 }, { _POP_TOP_INT, OPARG_SIMPLE, 5 } } }, @@ -1425,7 +1425,7 @@ _PyOpcode_macro_expansion[256] = { [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, OPARG_SIMPLE, 8 } } }, [LOAD_ATTR_CLASS] = { .nuops = 3, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 4, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _GUARD_TYPE_VERSION, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, - [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, + [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 5, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 1, 3 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } }, [LOAD_ATTR_METHOD_NO_DICT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_METHOD_NO_DICT, 4, 5 } } }, [LOAD_ATTR_METHOD_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, OPARG_SIMPLE, 3 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_METHOD_WITH_VALUES, 4, 5 } } }, @@ -1434,7 +1434,7 @@ _PyOpcode_macro_expansion[256] = { [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, OPARG_SIMPLE, 3 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } }, [LOAD_ATTR_PROPERTY] = { .nuops = 5, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_PROPERTY_FRAME, 4, 5 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 9 }, { _PUSH_FRAME, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_SLOT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_SLOT, 1, 3 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, - [LOAD_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_WITH_HINT, 1, 3 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, + [LOAD_ATTR_WITH_HINT] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_WITH_HINT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { _LOAD_BUILD_CLASS, OPARG_SIMPLE, 0 } } }, [LOAD_COMMON_CONSTANT] = { .nuops = 1, .uops = { { _LOAD_COMMON_CONSTANT, OPARG_SIMPLE, 0 } } }, [LOAD_CONST] = { .nuops = 1, .uops = { { _LOAD_CONST, OPARG_SIMPLE, 0 } } }, @@ -1484,7 +1484,7 @@ _PyOpcode_macro_expansion[256] = { [STORE_ATTR] = { .nuops = 1, .uops = { { _STORE_ATTR, OPARG_SIMPLE, 3 } } }, [STORE_ATTR_INSTANCE_VALUE] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION_AND_LOCK, 2, 1 }, { _GUARD_DORV_NO_DICT, OPARG_SIMPLE, 3 }, { _STORE_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 } } }, [STORE_ATTR_SLOT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_SLOT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 } } }, - [STORE_ATTR_WITH_HINT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_WITH_HINT, 1, 3 } } }, + [STORE_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_WITH_HINT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 } } }, [STORE_DEREF] = { .nuops = 1, .uops = { { _STORE_DEREF, OPARG_SIMPLE, 0 } } }, [STORE_FAST] = { .nuops = 1, .uops = { { _STORE_FAST, OPARG_SIMPLE, 0 } } }, [STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { _STORE_FAST, OPARG_TOP, 0 }, { _LOAD_FAST, OPARG_BOTTOM, 0 } } }, diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 295d4909e14..3ee62f17283 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -25,7 +25,6 @@ typedef struct { uint8_t opcode; uint8_t oparg; uint8_t valid; - uint8_t linked; uint8_t chain_depth; // Must be big enough for MAX_CHAIN_DEPTH - 1. bool warm; int32_t index; // Index of ENTER_EXECUTOR (if code isn't NULL, below). @@ -55,11 +54,6 @@ typedef struct _PyExecutorObject { _PyExitData exits[1]; } _PyExecutorObject; -/* If pending deletion list gets large enough, then scan, - * and free any executors that aren't executing - * i.e. any that aren't a thread's current_executor. */ -#define EXECUTOR_DELETE_LIST_MAX 100 - // Export for '_opcode' shared extension (JIT compiler). PyAPI_FUNC(_PyExecutorObject*) _Py_GetExecutor(PyCodeObject *code, int offset); @@ -80,7 +74,6 @@ PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyInterpreterState *interp); #else # define _Py_Executors_InvalidateDependency(A, B, C) ((void)0) # define _Py_Executors_InvalidateAll(A, B) ((void)0) -# define _Py_Executors_InvalidateCold(A) ((void)0) #endif diff --git a/Include/internal/pycore_parser.h b/Include/internal/pycore_parser.h index 2c46f59ab7d..b89d02035db 100644 --- a/Include/internal/pycore_parser.h +++ b/Include/internal/pycore_parser.h @@ -14,21 +14,6 @@ extern "C" { #include "pycore_pyarena.h" // PyArena _Py_DECLARE_STR(empty, "") -#if defined(Py_DEBUG) && defined(Py_GIL_DISABLED) -#define _parser_runtime_state_INIT \ - { \ - .mutex = {0}, \ - .dummy_name = { \ - .kind = Name_kind, \ - .v.Name.id = &_Py_STR(empty), \ - .v.Name.ctx = Load, \ - .lineno = 1, \ - .col_offset = 0, \ - .end_lineno = 1, \ - .end_col_offset = 0, \ - }, \ - } -#else #define _parser_runtime_state_INIT \ { \ .dummy_name = { \ @@ -41,7 +26,6 @@ _Py_DECLARE_STR(empty, "") .end_col_offset = 0, \ }, \ } -#endif extern struct _mod* _PyParser_ASTFromString( const char *str, diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h index 1a6d5075361..70a32db663b 100644 --- a/Include/internal/pycore_pyatomic_ft_wrappers.h +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -41,6 +41,8 @@ extern "C" { _Py_atomic_load_uint8(&value) #define FT_ATOMIC_STORE_UINT8(value, new_value) \ _Py_atomic_store_uint8(&value, new_value) +#define FT_ATOMIC_LOAD_INT8_RELAXED(value) \ + _Py_atomic_load_int8_relaxed(&value) #define FT_ATOMIC_LOAD_UINT8_RELAXED(value) \ _Py_atomic_load_uint8_relaxed(&value) #define FT_ATOMIC_LOAD_UINT16_RELAXED(value) \ @@ -55,6 +57,10 @@ extern "C" { _Py_atomic_store_ptr_release(&value, new_value) #define FT_ATOMIC_STORE_UINTPTR_RELEASE(value, new_value) \ _Py_atomic_store_uintptr_release(&value, new_value) +#define FT_ATOMIC_STORE_INT8_RELAXED(value, new_value) \ + _Py_atomic_store_int8_relaxed(&value, new_value) +#define FT_ATOMIC_STORE_INT8_RELEASE(value, new_value) \ + _Py_atomic_store_int8_release(&value, new_value) #define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) \ _Py_atomic_store_ssize_relaxed(&value, new_value) #define FT_ATOMIC_STORE_SSIZE_RELEASE(value, new_value) \ @@ -134,6 +140,7 @@ extern "C" { #define FT_ATOMIC_LOAD_PTR_RELAXED(value) value #define FT_ATOMIC_LOAD_UINT8(value) value #define FT_ATOMIC_STORE_UINT8(value, new_value) value = new_value +#define FT_ATOMIC_LOAD_INT8_RELAXED(value) value #define FT_ATOMIC_LOAD_UINT8_RELAXED(value) value #define FT_ATOMIC_LOAD_UINT16_RELAXED(value) value #define FT_ATOMIC_LOAD_UINT32_RELAXED(value) value @@ -141,6 +148,8 @@ extern "C" { #define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value #define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value #define FT_ATOMIC_STORE_UINTPTR_RELEASE(value, new_value) value = new_value +#define FT_ATOMIC_STORE_INT8_RELAXED(value, new_value) value = new_value +#define FT_ATOMIC_STORE_INT8_RELEASE(value, new_value) value = new_value #define FT_ATOMIC_STORE_SSIZE_RELAXED(value, new_value) value = new_value #define FT_ATOMIC_STORE_SSIZE_RELEASE(value, new_value) value = new_value #define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) value = new_value diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index d381fb9d2d4..499a2569b9a 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1651,9 +1651,11 @@ extern "C" { INIT_ID(co_varnames), \ INIT_ID(code), \ INIT_ID(col_offset), \ + INIT_ID(collector), \ INIT_ID(command), \ INIT_ID(comment_factory), \ INIT_ID(compile_mode), \ + INIT_ID(compression), \ INIT_ID(config), \ INIT_ID(consts), \ INIT_ID(context), \ @@ -1716,7 +1718,9 @@ extern "C" { INIT_ID(event), \ INIT_ID(eventmask), \ INIT_ID(exc), \ + INIT_ID(exc_tb), \ INIT_ID(exc_type), \ + INIT_ID(exc_val), \ INIT_ID(exc_value), \ INIT_ID(excepthook), \ INIT_ID(exception), \ @@ -1972,6 +1976,7 @@ extern "C" { INIT_ID(print_file_and_line), \ INIT_ID(priority), \ INIT_ID(progress), \ + INIT_ID(progress_callback), \ INIT_ID(progress_routine), \ INIT_ID(proto), \ INIT_ID(protocol), \ @@ -2012,6 +2017,7 @@ extern "C" { INIT_ID(reversed), \ INIT_ID(rounding), \ INIT_ID(salt), \ + INIT_ID(sample_interval_us), \ INIT_ID(sched_priority), \ INIT_ID(scheduler), \ INIT_ID(script), \ @@ -2051,8 +2057,10 @@ extern "C" { INIT_ID(spam), \ INIT_ID(src), \ INIT_ID(src_dir_fd), \ + INIT_ID(stack_frames), \ INIT_ID(stacklevel), \ INIT_ID(start), \ + INIT_ID(start_time_us), \ INIT_ID(statement), \ INIT_ID(stats), \ INIT_ID(status), \ @@ -2093,6 +2101,7 @@ extern "C" { INIT_ID(times), \ INIT_ID(timespec), \ INIT_ID(timestamp), \ + INIT_ID(timestamp_us), \ INIT_ID(timetuple), \ INIT_ID(timeunit), \ INIT_ID(top), \ diff --git a/Include/internal/pycore_runtime_structs.h b/Include/internal/pycore_runtime_structs.h index 995f49e78dc..92387031ad7 100644 --- a/Include/internal/pycore_runtime_structs.h +++ b/Include/internal/pycore_runtime_structs.h @@ -77,9 +77,7 @@ struct _fileutils_state { struct _parser_runtime_state { #ifdef Py_DEBUG long memo_statistics[_PYPEGEN_NSTATISTICS]; -#ifdef Py_GIL_DISABLED PyMutex mutex; -#endif #else int _not_used; #endif diff --git a/Include/internal/pycore_tracemalloc.h b/Include/internal/pycore_tracemalloc.h index 693385f9a46..9974ea3c414 100644 --- a/Include/internal/pycore_tracemalloc.h +++ b/Include/internal/pycore_tracemalloc.h @@ -21,7 +21,10 @@ struct _PyTraceMalloc_Config { } initialized; /* Is tracemalloc tracing memory allocations? - Variable protected by the TABLES_LOCK(). */ + Variable protected by the TABLES_LOCK() and stored atomically. + Atomic store is used so that it can read without locking for the + general case of checking if tracemalloc is enabled. + */ int tracing; /* limit of the number of frames in a traceback, 1 by default. diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index c4f723ac8ab..a57f1f45c13 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -82,6 +82,13 @@ typedef struct _PyThreadStateImpl { PyObject *asyncio_running_loop; // Strong reference PyObject *asyncio_running_task; // Strong reference + // Distinguishes between yield and return from PyEval_EvalFrame(). + // See gen_send_ex2() in Objects/genobject.c + enum { + GENERATOR_RETURN = 0, + GENERATOR_YIELD = 1, + } generator_return_kind; + /* Head of circular linked-list of all tasks which are instances of `asyncio.Task` or subclasses of it used in `asyncio.all_tasks`. */ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 24e50828935..1375f46018f 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1284,6 +1284,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(collector); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(command); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -1296,6 +1300,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(compression); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(config); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -1544,10 +1552,18 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(exc_tb); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(exc_type); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(exc_val); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(exc_value); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2568,6 +2584,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(progress_callback); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(progress_routine); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2728,6 +2748,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(sample_interval_us); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(sched_priority); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2884,6 +2908,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(stack_frames); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(stacklevel); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2892,6 +2920,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(start_time_us); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(statement); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -3052,6 +3084,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(timestamp_us); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(timetuple); _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 df623f49b0d..c8aa2765a34 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -314,61 +314,61 @@ extern "C" { #define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE #define _SET_UPDATE SET_UPDATE -#define _SPILL_OR_RELOAD 524 -#define _START_EXECUTOR 525 -#define _STORE_ATTR 526 -#define _STORE_ATTR_INSTANCE_VALUE 527 -#define _STORE_ATTR_SLOT 528 -#define _STORE_ATTR_WITH_HINT 529 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW 524 +#define _SPILL_OR_RELOAD 525 +#define _START_EXECUTOR 526 +#define _STORE_ATTR 527 +#define _STORE_ATTR_INSTANCE_VALUE 528 +#define _STORE_ATTR_SLOT 529 +#define _STORE_ATTR_WITH_HINT 530 #define _STORE_DEREF STORE_DEREF -#define _STORE_FAST 530 -#define _STORE_FAST_0 531 -#define _STORE_FAST_1 532 -#define _STORE_FAST_2 533 -#define _STORE_FAST_3 534 -#define _STORE_FAST_4 535 -#define _STORE_FAST_5 536 -#define _STORE_FAST_6 537 -#define _STORE_FAST_7 538 +#define _STORE_FAST 531 +#define _STORE_FAST_0 532 +#define _STORE_FAST_1 533 +#define _STORE_FAST_2 534 +#define _STORE_FAST_3 535 +#define _STORE_FAST_4 536 +#define _STORE_FAST_5 537 +#define _STORE_FAST_6 538 +#define _STORE_FAST_7 539 #define _STORE_GLOBAL STORE_GLOBAL #define _STORE_NAME STORE_NAME -#define _STORE_SLICE 539 -#define _STORE_SUBSCR 540 -#define _STORE_SUBSCR_DICT 541 -#define _STORE_SUBSCR_LIST_INT 542 -#define _SWAP 543 -#define _SWAP_2 544 -#define _SWAP_3 545 -#define _TIER2_RESUME_CHECK 546 -#define _TO_BOOL 547 +#define _STORE_SLICE 540 +#define _STORE_SUBSCR 541 +#define _STORE_SUBSCR_DICT 542 +#define _STORE_SUBSCR_LIST_INT 543 +#define _SWAP 544 +#define _SWAP_2 545 +#define _SWAP_3 546 +#define _TIER2_RESUME_CHECK 547 +#define _TO_BOOL 548 #define _TO_BOOL_BOOL TO_BOOL_BOOL #define _TO_BOOL_INT TO_BOOL_INT -#define _TO_BOOL_LIST 548 +#define _TO_BOOL_LIST 549 #define _TO_BOOL_NONE TO_BOOL_NONE -#define _TO_BOOL_STR 549 +#define _TO_BOOL_STR 550 #define _TRACE_RECORD TRACE_RECORD #define _UNARY_INVERT UNARY_INVERT #define _UNARY_NEGATIVE UNARY_NEGATIVE #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 550 -#define _UNPACK_SEQUENCE_LIST 551 -#define _UNPACK_SEQUENCE_TUPLE 552 -#define _UNPACK_SEQUENCE_TWO_TUPLE 553 +#define _UNPACK_SEQUENCE 551 +#define _UNPACK_SEQUENCE_LIST 552 +#define _UNPACK_SEQUENCE_TUPLE 553 +#define _UNPACK_SEQUENCE_TWO_TUPLE 554 #define _WITH_EXCEPT_START WITH_EXCEPT_START #define _YIELD_VALUE YIELD_VALUE -#define MAX_UOP_ID 553 -#define _BINARY_OP_r21 554 -#define _BINARY_OP_ADD_FLOAT_r03 555 -#define _BINARY_OP_ADD_FLOAT_r13 556 -#define _BINARY_OP_ADD_FLOAT_r23 557 -#define _BINARY_OP_ADD_INT_r03 558 -#define _BINARY_OP_ADD_INT_r13 559 -#define _BINARY_OP_ADD_INT_r23 560 -#define _BINARY_OP_ADD_UNICODE_r01 561 -#define _BINARY_OP_ADD_UNICODE_r11 562 -#define _BINARY_OP_ADD_UNICODE_r21 563 -#define _BINARY_OP_ADD_UNICODE_r32 564 +#define MAX_UOP_ID 554 +#define _BINARY_OP_r21 555 +#define _BINARY_OP_ADD_FLOAT_r03 556 +#define _BINARY_OP_ADD_FLOAT_r13 557 +#define _BINARY_OP_ADD_FLOAT_r23 558 +#define _BINARY_OP_ADD_INT_r03 559 +#define _BINARY_OP_ADD_INT_r13 560 +#define _BINARY_OP_ADD_INT_r23 561 +#define _BINARY_OP_ADD_UNICODE_r03 562 +#define _BINARY_OP_ADD_UNICODE_r13 563 +#define _BINARY_OP_ADD_UNICODE_r23 564 #define _BINARY_OP_EXTEND_r21 565 #define _BINARY_OP_INPLACE_ADD_UNICODE_r20 566 #define _BINARY_OP_MULTIPLY_FLOAT_r03 567 @@ -383,9 +383,9 @@ extern "C" { #define _BINARY_OP_SUBSCR_INIT_CALL_r11 576 #define _BINARY_OP_SUBSCR_INIT_CALL_r21 577 #define _BINARY_OP_SUBSCR_INIT_CALL_r31 578 -#define _BINARY_OP_SUBSCR_LIST_INT_r21 579 +#define _BINARY_OP_SUBSCR_LIST_INT_r23 579 #define _BINARY_OP_SUBSCR_LIST_SLICE_r21 580 -#define _BINARY_OP_SUBSCR_STR_INT_r21 581 +#define _BINARY_OP_SUBSCR_STR_INT_r23 581 #define _BINARY_OP_SUBSCR_TUPLE_INT_r21 582 #define _BINARY_OP_SUBTRACT_FLOAT_r03 583 #define _BINARY_OP_SUBTRACT_FLOAT_r13 584 @@ -535,566 +535,574 @@ extern "C" { #define _FORMAT_SIMPLE_r11 728 #define _FORMAT_WITH_SPEC_r21 729 #define _FOR_ITER_r23 730 -#define _FOR_ITER_GEN_FRAME_r23 731 -#define _FOR_ITER_TIER_TWO_r23 732 -#define _GET_AITER_r11 733 -#define _GET_ANEXT_r12 734 -#define _GET_AWAITABLE_r11 735 -#define _GET_ITER_r12 736 -#define _GET_LEN_r12 737 -#define _GET_YIELD_FROM_ITER_r11 738 -#define _GUARD_BINARY_OP_EXTEND_r22 739 -#define _GUARD_CALLABLE_ISINSTANCE_r03 740 -#define _GUARD_CALLABLE_ISINSTANCE_r13 741 -#define _GUARD_CALLABLE_ISINSTANCE_r23 742 -#define _GUARD_CALLABLE_ISINSTANCE_r33 743 -#define _GUARD_CALLABLE_LEN_r03 744 -#define _GUARD_CALLABLE_LEN_r13 745 -#define _GUARD_CALLABLE_LEN_r23 746 -#define _GUARD_CALLABLE_LEN_r33 747 -#define _GUARD_CALLABLE_LIST_APPEND_r03 748 -#define _GUARD_CALLABLE_LIST_APPEND_r13 749 -#define _GUARD_CALLABLE_LIST_APPEND_r23 750 -#define _GUARD_CALLABLE_LIST_APPEND_r33 751 -#define _GUARD_CALLABLE_STR_1_r03 752 -#define _GUARD_CALLABLE_STR_1_r13 753 -#define _GUARD_CALLABLE_STR_1_r23 754 -#define _GUARD_CALLABLE_STR_1_r33 755 -#define _GUARD_CALLABLE_TUPLE_1_r03 756 -#define _GUARD_CALLABLE_TUPLE_1_r13 757 -#define _GUARD_CALLABLE_TUPLE_1_r23 758 -#define _GUARD_CALLABLE_TUPLE_1_r33 759 -#define _GUARD_CALLABLE_TYPE_1_r03 760 -#define _GUARD_CALLABLE_TYPE_1_r13 761 -#define _GUARD_CALLABLE_TYPE_1_r23 762 -#define _GUARD_CALLABLE_TYPE_1_r33 763 -#define _GUARD_DORV_NO_DICT_r01 764 -#define _GUARD_DORV_NO_DICT_r11 765 -#define _GUARD_DORV_NO_DICT_r22 766 -#define _GUARD_DORV_NO_DICT_r33 767 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 768 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 769 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 770 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 771 -#define _GUARD_GLOBALS_VERSION_r00 772 -#define _GUARD_GLOBALS_VERSION_r11 773 -#define _GUARD_GLOBALS_VERSION_r22 774 -#define _GUARD_GLOBALS_VERSION_r33 775 -#define _GUARD_IP_RETURN_GENERATOR_r00 776 -#define _GUARD_IP_RETURN_GENERATOR_r11 777 -#define _GUARD_IP_RETURN_GENERATOR_r22 778 -#define _GUARD_IP_RETURN_GENERATOR_r33 779 -#define _GUARD_IP_RETURN_VALUE_r00 780 -#define _GUARD_IP_RETURN_VALUE_r11 781 -#define _GUARD_IP_RETURN_VALUE_r22 782 -#define _GUARD_IP_RETURN_VALUE_r33 783 -#define _GUARD_IP_YIELD_VALUE_r00 784 -#define _GUARD_IP_YIELD_VALUE_r11 785 -#define _GUARD_IP_YIELD_VALUE_r22 786 -#define _GUARD_IP_YIELD_VALUE_r33 787 -#define _GUARD_IP__PUSH_FRAME_r00 788 -#define _GUARD_IP__PUSH_FRAME_r11 789 -#define _GUARD_IP__PUSH_FRAME_r22 790 -#define _GUARD_IP__PUSH_FRAME_r33 791 -#define _GUARD_IS_FALSE_POP_r00 792 -#define _GUARD_IS_FALSE_POP_r10 793 -#define _GUARD_IS_FALSE_POP_r21 794 -#define _GUARD_IS_FALSE_POP_r32 795 -#define _GUARD_IS_NONE_POP_r00 796 -#define _GUARD_IS_NONE_POP_r10 797 -#define _GUARD_IS_NONE_POP_r21 798 -#define _GUARD_IS_NONE_POP_r32 799 -#define _GUARD_IS_NOT_NONE_POP_r10 800 -#define _GUARD_IS_TRUE_POP_r00 801 -#define _GUARD_IS_TRUE_POP_r10 802 -#define _GUARD_IS_TRUE_POP_r21 803 -#define _GUARD_IS_TRUE_POP_r32 804 -#define _GUARD_KEYS_VERSION_r01 805 -#define _GUARD_KEYS_VERSION_r11 806 -#define _GUARD_KEYS_VERSION_r22 807 -#define _GUARD_KEYS_VERSION_r33 808 -#define _GUARD_NOS_DICT_r02 809 -#define _GUARD_NOS_DICT_r12 810 -#define _GUARD_NOS_DICT_r22 811 -#define _GUARD_NOS_DICT_r33 812 -#define _GUARD_NOS_FLOAT_r02 813 -#define _GUARD_NOS_FLOAT_r12 814 -#define _GUARD_NOS_FLOAT_r22 815 -#define _GUARD_NOS_FLOAT_r33 816 -#define _GUARD_NOS_INT_r02 817 -#define _GUARD_NOS_INT_r12 818 -#define _GUARD_NOS_INT_r22 819 -#define _GUARD_NOS_INT_r33 820 -#define _GUARD_NOS_LIST_r02 821 -#define _GUARD_NOS_LIST_r12 822 -#define _GUARD_NOS_LIST_r22 823 -#define _GUARD_NOS_LIST_r33 824 -#define _GUARD_NOS_NOT_NULL_r02 825 -#define _GUARD_NOS_NOT_NULL_r12 826 -#define _GUARD_NOS_NOT_NULL_r22 827 -#define _GUARD_NOS_NOT_NULL_r33 828 -#define _GUARD_NOS_NULL_r02 829 -#define _GUARD_NOS_NULL_r12 830 -#define _GUARD_NOS_NULL_r22 831 -#define _GUARD_NOS_NULL_r33 832 -#define _GUARD_NOS_OVERFLOWED_r02 833 -#define _GUARD_NOS_OVERFLOWED_r12 834 -#define _GUARD_NOS_OVERFLOWED_r22 835 -#define _GUARD_NOS_OVERFLOWED_r33 836 -#define _GUARD_NOS_TUPLE_r02 837 -#define _GUARD_NOS_TUPLE_r12 838 -#define _GUARD_NOS_TUPLE_r22 839 -#define _GUARD_NOS_TUPLE_r33 840 -#define _GUARD_NOS_UNICODE_r02 841 -#define _GUARD_NOS_UNICODE_r12 842 -#define _GUARD_NOS_UNICODE_r22 843 -#define _GUARD_NOS_UNICODE_r33 844 -#define _GUARD_NOT_EXHAUSTED_LIST_r02 845 -#define _GUARD_NOT_EXHAUSTED_LIST_r12 846 -#define _GUARD_NOT_EXHAUSTED_LIST_r22 847 -#define _GUARD_NOT_EXHAUSTED_LIST_r33 848 -#define _GUARD_NOT_EXHAUSTED_RANGE_r02 849 -#define _GUARD_NOT_EXHAUSTED_RANGE_r12 850 -#define _GUARD_NOT_EXHAUSTED_RANGE_r22 851 -#define _GUARD_NOT_EXHAUSTED_RANGE_r33 852 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 853 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 854 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 855 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 856 -#define _GUARD_THIRD_NULL_r03 857 -#define _GUARD_THIRD_NULL_r13 858 -#define _GUARD_THIRD_NULL_r23 859 -#define _GUARD_THIRD_NULL_r33 860 -#define _GUARD_TOS_ANY_SET_r01 861 -#define _GUARD_TOS_ANY_SET_r11 862 -#define _GUARD_TOS_ANY_SET_r22 863 -#define _GUARD_TOS_ANY_SET_r33 864 -#define _GUARD_TOS_DICT_r01 865 -#define _GUARD_TOS_DICT_r11 866 -#define _GUARD_TOS_DICT_r22 867 -#define _GUARD_TOS_DICT_r33 868 -#define _GUARD_TOS_FLOAT_r01 869 -#define _GUARD_TOS_FLOAT_r11 870 -#define _GUARD_TOS_FLOAT_r22 871 -#define _GUARD_TOS_FLOAT_r33 872 -#define _GUARD_TOS_INT_r01 873 -#define _GUARD_TOS_INT_r11 874 -#define _GUARD_TOS_INT_r22 875 -#define _GUARD_TOS_INT_r33 876 -#define _GUARD_TOS_LIST_r01 877 -#define _GUARD_TOS_LIST_r11 878 -#define _GUARD_TOS_LIST_r22 879 -#define _GUARD_TOS_LIST_r33 880 -#define _GUARD_TOS_OVERFLOWED_r01 881 -#define _GUARD_TOS_OVERFLOWED_r11 882 -#define _GUARD_TOS_OVERFLOWED_r22 883 -#define _GUARD_TOS_OVERFLOWED_r33 884 -#define _GUARD_TOS_SLICE_r01 885 -#define _GUARD_TOS_SLICE_r11 886 -#define _GUARD_TOS_SLICE_r22 887 -#define _GUARD_TOS_SLICE_r33 888 -#define _GUARD_TOS_TUPLE_r01 889 -#define _GUARD_TOS_TUPLE_r11 890 -#define _GUARD_TOS_TUPLE_r22 891 -#define _GUARD_TOS_TUPLE_r33 892 -#define _GUARD_TOS_UNICODE_r01 893 -#define _GUARD_TOS_UNICODE_r11 894 -#define _GUARD_TOS_UNICODE_r22 895 -#define _GUARD_TOS_UNICODE_r33 896 -#define _GUARD_TYPE_VERSION_r01 897 -#define _GUARD_TYPE_VERSION_r11 898 -#define _GUARD_TYPE_VERSION_r22 899 -#define _GUARD_TYPE_VERSION_r33 900 -#define _GUARD_TYPE_VERSION_AND_LOCK_r01 901 -#define _GUARD_TYPE_VERSION_AND_LOCK_r11 902 -#define _GUARD_TYPE_VERSION_AND_LOCK_r22 903 -#define _GUARD_TYPE_VERSION_AND_LOCK_r33 904 -#define _HANDLE_PENDING_AND_DEOPT_r00 905 -#define _HANDLE_PENDING_AND_DEOPT_r10 906 -#define _HANDLE_PENDING_AND_DEOPT_r20 907 -#define _HANDLE_PENDING_AND_DEOPT_r30 908 -#define _IMPORT_FROM_r12 909 -#define _IMPORT_NAME_r21 910 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 911 -#define _INIT_CALL_PY_EXACT_ARGS_r01 912 -#define _INIT_CALL_PY_EXACT_ARGS_0_r01 913 -#define _INIT_CALL_PY_EXACT_ARGS_1_r01 914 -#define _INIT_CALL_PY_EXACT_ARGS_2_r01 915 -#define _INIT_CALL_PY_EXACT_ARGS_3_r01 916 -#define _INIT_CALL_PY_EXACT_ARGS_4_r01 917 -#define _INSERT_NULL_r10 918 -#define _INSTRUMENTED_FOR_ITER_r23 919 -#define _INSTRUMENTED_INSTRUCTION_r00 920 -#define _INSTRUMENTED_JUMP_FORWARD_r00 921 -#define _INSTRUMENTED_JUMP_FORWARD_r11 922 -#define _INSTRUMENTED_JUMP_FORWARD_r22 923 -#define _INSTRUMENTED_JUMP_FORWARD_r33 924 -#define _INSTRUMENTED_LINE_r00 925 -#define _INSTRUMENTED_NOT_TAKEN_r00 926 -#define _INSTRUMENTED_NOT_TAKEN_r11 927 -#define _INSTRUMENTED_NOT_TAKEN_r22 928 -#define _INSTRUMENTED_NOT_TAKEN_r33 929 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 930 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 931 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 932 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 933 -#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 934 -#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 935 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 936 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 937 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 938 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 939 -#define _IS_NONE_r11 940 -#define _IS_OP_r21 941 -#define _ITER_CHECK_LIST_r02 942 -#define _ITER_CHECK_LIST_r12 943 -#define _ITER_CHECK_LIST_r22 944 -#define _ITER_CHECK_LIST_r33 945 -#define _ITER_CHECK_RANGE_r02 946 -#define _ITER_CHECK_RANGE_r12 947 -#define _ITER_CHECK_RANGE_r22 948 -#define _ITER_CHECK_RANGE_r33 949 -#define _ITER_CHECK_TUPLE_r02 950 -#define _ITER_CHECK_TUPLE_r12 951 -#define _ITER_CHECK_TUPLE_r22 952 -#define _ITER_CHECK_TUPLE_r33 953 -#define _ITER_JUMP_LIST_r02 954 -#define _ITER_JUMP_LIST_r12 955 -#define _ITER_JUMP_LIST_r22 956 -#define _ITER_JUMP_LIST_r33 957 -#define _ITER_JUMP_RANGE_r02 958 -#define _ITER_JUMP_RANGE_r12 959 -#define _ITER_JUMP_RANGE_r22 960 -#define _ITER_JUMP_RANGE_r33 961 -#define _ITER_JUMP_TUPLE_r02 962 -#define _ITER_JUMP_TUPLE_r12 963 -#define _ITER_JUMP_TUPLE_r22 964 -#define _ITER_JUMP_TUPLE_r33 965 -#define _ITER_NEXT_LIST_r23 966 -#define _ITER_NEXT_LIST_TIER_TWO_r23 967 -#define _ITER_NEXT_RANGE_r03 968 -#define _ITER_NEXT_RANGE_r13 969 -#define _ITER_NEXT_RANGE_r23 970 -#define _ITER_NEXT_TUPLE_r03 971 -#define _ITER_NEXT_TUPLE_r13 972 -#define _ITER_NEXT_TUPLE_r23 973 -#define _JUMP_BACKWARD_NO_INTERRUPT_r00 974 -#define _JUMP_BACKWARD_NO_INTERRUPT_r11 975 -#define _JUMP_BACKWARD_NO_INTERRUPT_r22 976 -#define _JUMP_BACKWARD_NO_INTERRUPT_r33 977 -#define _JUMP_TO_TOP_r00 978 -#define _LIST_APPEND_r10 979 -#define _LIST_EXTEND_r10 980 -#define _LOAD_ATTR_r10 981 -#define _LOAD_ATTR_CLASS_r11 982 -#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_r11 983 -#define _LOAD_ATTR_INSTANCE_VALUE_r11 984 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 985 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 986 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 987 -#define _LOAD_ATTR_METHOD_NO_DICT_r02 988 -#define _LOAD_ATTR_METHOD_NO_DICT_r12 989 -#define _LOAD_ATTR_METHOD_NO_DICT_r23 990 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 991 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 992 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 993 -#define _LOAD_ATTR_MODULE_r11 994 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 995 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 996 -#define _LOAD_ATTR_PROPERTY_FRAME_r11 997 -#define _LOAD_ATTR_SLOT_r11 998 -#define _LOAD_ATTR_WITH_HINT_r11 999 -#define _LOAD_BUILD_CLASS_r01 1000 -#define _LOAD_BYTECODE_r00 1001 -#define _LOAD_COMMON_CONSTANT_r01 1002 -#define _LOAD_COMMON_CONSTANT_r12 1003 -#define _LOAD_COMMON_CONSTANT_r23 1004 -#define _LOAD_CONST_r01 1005 -#define _LOAD_CONST_r12 1006 -#define _LOAD_CONST_r23 1007 -#define _LOAD_CONST_INLINE_r01 1008 -#define _LOAD_CONST_INLINE_r12 1009 -#define _LOAD_CONST_INLINE_r23 1010 -#define _LOAD_CONST_INLINE_BORROW_r01 1011 -#define _LOAD_CONST_INLINE_BORROW_r12 1012 -#define _LOAD_CONST_INLINE_BORROW_r23 1013 -#define _LOAD_CONST_UNDER_INLINE_r02 1014 -#define _LOAD_CONST_UNDER_INLINE_r12 1015 -#define _LOAD_CONST_UNDER_INLINE_r23 1016 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r02 1017 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r12 1018 -#define _LOAD_CONST_UNDER_INLINE_BORROW_r23 1019 -#define _LOAD_DEREF_r01 1020 -#define _LOAD_FAST_r01 1021 -#define _LOAD_FAST_r12 1022 -#define _LOAD_FAST_r23 1023 -#define _LOAD_FAST_0_r01 1024 -#define _LOAD_FAST_0_r12 1025 -#define _LOAD_FAST_0_r23 1026 -#define _LOAD_FAST_1_r01 1027 -#define _LOAD_FAST_1_r12 1028 -#define _LOAD_FAST_1_r23 1029 -#define _LOAD_FAST_2_r01 1030 -#define _LOAD_FAST_2_r12 1031 -#define _LOAD_FAST_2_r23 1032 -#define _LOAD_FAST_3_r01 1033 -#define _LOAD_FAST_3_r12 1034 -#define _LOAD_FAST_3_r23 1035 -#define _LOAD_FAST_4_r01 1036 -#define _LOAD_FAST_4_r12 1037 -#define _LOAD_FAST_4_r23 1038 -#define _LOAD_FAST_5_r01 1039 -#define _LOAD_FAST_5_r12 1040 -#define _LOAD_FAST_5_r23 1041 -#define _LOAD_FAST_6_r01 1042 -#define _LOAD_FAST_6_r12 1043 -#define _LOAD_FAST_6_r23 1044 -#define _LOAD_FAST_7_r01 1045 -#define _LOAD_FAST_7_r12 1046 -#define _LOAD_FAST_7_r23 1047 -#define _LOAD_FAST_AND_CLEAR_r01 1048 -#define _LOAD_FAST_AND_CLEAR_r12 1049 -#define _LOAD_FAST_AND_CLEAR_r23 1050 -#define _LOAD_FAST_BORROW_r01 1051 -#define _LOAD_FAST_BORROW_r12 1052 -#define _LOAD_FAST_BORROW_r23 1053 -#define _LOAD_FAST_BORROW_0_r01 1054 -#define _LOAD_FAST_BORROW_0_r12 1055 -#define _LOAD_FAST_BORROW_0_r23 1056 -#define _LOAD_FAST_BORROW_1_r01 1057 -#define _LOAD_FAST_BORROW_1_r12 1058 -#define _LOAD_FAST_BORROW_1_r23 1059 -#define _LOAD_FAST_BORROW_2_r01 1060 -#define _LOAD_FAST_BORROW_2_r12 1061 -#define _LOAD_FAST_BORROW_2_r23 1062 -#define _LOAD_FAST_BORROW_3_r01 1063 -#define _LOAD_FAST_BORROW_3_r12 1064 -#define _LOAD_FAST_BORROW_3_r23 1065 -#define _LOAD_FAST_BORROW_4_r01 1066 -#define _LOAD_FAST_BORROW_4_r12 1067 -#define _LOAD_FAST_BORROW_4_r23 1068 -#define _LOAD_FAST_BORROW_5_r01 1069 -#define _LOAD_FAST_BORROW_5_r12 1070 -#define _LOAD_FAST_BORROW_5_r23 1071 -#define _LOAD_FAST_BORROW_6_r01 1072 -#define _LOAD_FAST_BORROW_6_r12 1073 -#define _LOAD_FAST_BORROW_6_r23 1074 -#define _LOAD_FAST_BORROW_7_r01 1075 -#define _LOAD_FAST_BORROW_7_r12 1076 -#define _LOAD_FAST_BORROW_7_r23 1077 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1078 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1079 -#define _LOAD_FAST_CHECK_r01 1080 -#define _LOAD_FAST_CHECK_r12 1081 -#define _LOAD_FAST_CHECK_r23 1082 -#define _LOAD_FAST_LOAD_FAST_r02 1083 -#define _LOAD_FAST_LOAD_FAST_r13 1084 -#define _LOAD_FROM_DICT_OR_DEREF_r11 1085 -#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1086 -#define _LOAD_GLOBAL_r00 1087 -#define _LOAD_GLOBAL_BUILTINS_r01 1088 -#define _LOAD_GLOBAL_MODULE_r01 1089 -#define _LOAD_LOCALS_r01 1090 -#define _LOAD_LOCALS_r12 1091 -#define _LOAD_LOCALS_r23 1092 -#define _LOAD_NAME_r01 1093 -#define _LOAD_SMALL_INT_r01 1094 -#define _LOAD_SMALL_INT_r12 1095 -#define _LOAD_SMALL_INT_r23 1096 -#define _LOAD_SMALL_INT_0_r01 1097 -#define _LOAD_SMALL_INT_0_r12 1098 -#define _LOAD_SMALL_INT_0_r23 1099 -#define _LOAD_SMALL_INT_1_r01 1100 -#define _LOAD_SMALL_INT_1_r12 1101 -#define _LOAD_SMALL_INT_1_r23 1102 -#define _LOAD_SMALL_INT_2_r01 1103 -#define _LOAD_SMALL_INT_2_r12 1104 -#define _LOAD_SMALL_INT_2_r23 1105 -#define _LOAD_SMALL_INT_3_r01 1106 -#define _LOAD_SMALL_INT_3_r12 1107 -#define _LOAD_SMALL_INT_3_r23 1108 -#define _LOAD_SPECIAL_r00 1109 -#define _LOAD_SUPER_ATTR_ATTR_r31 1110 -#define _LOAD_SUPER_ATTR_METHOD_r32 1111 -#define _MAKE_CALLARGS_A_TUPLE_r33 1112 -#define _MAKE_CELL_r00 1113 -#define _MAKE_FUNCTION_r11 1114 -#define _MAKE_WARM_r00 1115 -#define _MAKE_WARM_r11 1116 -#define _MAKE_WARM_r22 1117 -#define _MAKE_WARM_r33 1118 -#define _MAP_ADD_r20 1119 -#define _MATCH_CLASS_r31 1120 -#define _MATCH_KEYS_r23 1121 -#define _MATCH_MAPPING_r02 1122 -#define _MATCH_MAPPING_r12 1123 -#define _MATCH_MAPPING_r23 1124 -#define _MATCH_SEQUENCE_r02 1125 -#define _MATCH_SEQUENCE_r12 1126 -#define _MATCH_SEQUENCE_r23 1127 -#define _MAYBE_EXPAND_METHOD_r00 1128 -#define _MAYBE_EXPAND_METHOD_KW_r11 1129 -#define _MONITOR_CALL_r00 1130 -#define _MONITOR_CALL_KW_r11 1131 -#define _MONITOR_JUMP_BACKWARD_r00 1132 -#define _MONITOR_JUMP_BACKWARD_r11 1133 -#define _MONITOR_JUMP_BACKWARD_r22 1134 -#define _MONITOR_JUMP_BACKWARD_r33 1135 -#define _MONITOR_RESUME_r00 1136 -#define _NOP_r00 1137 -#define _NOP_r11 1138 -#define _NOP_r22 1139 -#define _NOP_r33 1140 -#define _POP_CALL_r20 1141 -#define _POP_CALL_LOAD_CONST_INLINE_BORROW_r21 1142 -#define _POP_CALL_ONE_r30 1143 -#define _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 1144 -#define _POP_CALL_TWO_r30 1145 -#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31 1146 -#define _POP_EXCEPT_r10 1147 -#define _POP_ITER_r20 1148 -#define _POP_JUMP_IF_FALSE_r00 1149 -#define _POP_JUMP_IF_FALSE_r10 1150 -#define _POP_JUMP_IF_FALSE_r21 1151 -#define _POP_JUMP_IF_FALSE_r32 1152 -#define _POP_JUMP_IF_TRUE_r00 1153 -#define _POP_JUMP_IF_TRUE_r10 1154 -#define _POP_JUMP_IF_TRUE_r21 1155 -#define _POP_JUMP_IF_TRUE_r32 1156 -#define _POP_TOP_r10 1157 -#define _POP_TOP_FLOAT_r00 1158 -#define _POP_TOP_FLOAT_r10 1159 -#define _POP_TOP_FLOAT_r21 1160 -#define _POP_TOP_FLOAT_r32 1161 -#define _POP_TOP_INT_r00 1162 -#define _POP_TOP_INT_r10 1163 -#define _POP_TOP_INT_r21 1164 -#define _POP_TOP_INT_r32 1165 -#define _POP_TOP_LOAD_CONST_INLINE_r11 1166 -#define _POP_TOP_LOAD_CONST_INLINE_BORROW_r11 1167 -#define _POP_TOP_NOP_r00 1168 -#define _POP_TOP_NOP_r10 1169 -#define _POP_TOP_NOP_r21 1170 -#define _POP_TOP_NOP_r32 1171 -#define _POP_TOP_UNICODE_r00 1172 -#define _POP_TOP_UNICODE_r10 1173 -#define _POP_TOP_UNICODE_r21 1174 -#define _POP_TOP_UNICODE_r32 1175 -#define _POP_TWO_r20 1176 -#define _POP_TWO_LOAD_CONST_INLINE_BORROW_r21 1177 -#define _PUSH_EXC_INFO_r02 1178 -#define _PUSH_EXC_INFO_r12 1179 -#define _PUSH_EXC_INFO_r23 1180 -#define _PUSH_FRAME_r10 1181 -#define _PUSH_NULL_r01 1182 -#define _PUSH_NULL_r12 1183 -#define _PUSH_NULL_r23 1184 -#define _PUSH_NULL_CONDITIONAL_r00 1185 -#define _PY_FRAME_GENERAL_r01 1186 -#define _PY_FRAME_KW_r11 1187 -#define _QUICKEN_RESUME_r00 1188 -#define _QUICKEN_RESUME_r11 1189 -#define _QUICKEN_RESUME_r22 1190 -#define _QUICKEN_RESUME_r33 1191 -#define _REPLACE_WITH_TRUE_r11 1192 -#define _RESUME_CHECK_r00 1193 -#define _RESUME_CHECK_r11 1194 -#define _RESUME_CHECK_r22 1195 -#define _RESUME_CHECK_r33 1196 -#define _RETURN_GENERATOR_r01 1197 -#define _RETURN_VALUE_r11 1198 -#define _SAVE_RETURN_OFFSET_r00 1199 -#define _SAVE_RETURN_OFFSET_r11 1200 -#define _SAVE_RETURN_OFFSET_r22 1201 -#define _SAVE_RETURN_OFFSET_r33 1202 -#define _SEND_r22 1203 -#define _SEND_GEN_FRAME_r22 1204 -#define _SETUP_ANNOTATIONS_r00 1205 -#define _SET_ADD_r10 1206 -#define _SET_FUNCTION_ATTRIBUTE_r01 1207 -#define _SET_FUNCTION_ATTRIBUTE_r11 1208 -#define _SET_FUNCTION_ATTRIBUTE_r21 1209 -#define _SET_FUNCTION_ATTRIBUTE_r32 1210 -#define _SET_IP_r00 1211 -#define _SET_IP_r11 1212 -#define _SET_IP_r22 1213 -#define _SET_IP_r33 1214 -#define _SET_UPDATE_r10 1215 -#define _SPILL_OR_RELOAD_r01 1216 -#define _SPILL_OR_RELOAD_r02 1217 -#define _SPILL_OR_RELOAD_r03 1218 -#define _SPILL_OR_RELOAD_r10 1219 -#define _SPILL_OR_RELOAD_r12 1220 -#define _SPILL_OR_RELOAD_r13 1221 -#define _SPILL_OR_RELOAD_r20 1222 -#define _SPILL_OR_RELOAD_r21 1223 -#define _SPILL_OR_RELOAD_r23 1224 -#define _SPILL_OR_RELOAD_r30 1225 -#define _SPILL_OR_RELOAD_r31 1226 -#define _SPILL_OR_RELOAD_r32 1227 -#define _START_EXECUTOR_r00 1228 -#define _STORE_ATTR_r20 1229 -#define _STORE_ATTR_INSTANCE_VALUE_r21 1230 -#define _STORE_ATTR_SLOT_r21 1231 -#define _STORE_ATTR_WITH_HINT_r20 1232 -#define _STORE_DEREF_r10 1233 -#define _STORE_FAST_r10 1234 -#define _STORE_FAST_0_r10 1235 -#define _STORE_FAST_1_r10 1236 -#define _STORE_FAST_2_r10 1237 -#define _STORE_FAST_3_r10 1238 -#define _STORE_FAST_4_r10 1239 -#define _STORE_FAST_5_r10 1240 -#define _STORE_FAST_6_r10 1241 -#define _STORE_FAST_7_r10 1242 -#define _STORE_FAST_LOAD_FAST_r11 1243 -#define _STORE_FAST_STORE_FAST_r20 1244 -#define _STORE_GLOBAL_r10 1245 -#define _STORE_NAME_r10 1246 -#define _STORE_SLICE_r30 1247 -#define _STORE_SUBSCR_r30 1248 -#define _STORE_SUBSCR_DICT_r31 1249 -#define _STORE_SUBSCR_LIST_INT_r32 1250 -#define _SWAP_r11 1251 -#define _SWAP_2_r02 1252 -#define _SWAP_2_r12 1253 -#define _SWAP_2_r22 1254 -#define _SWAP_2_r33 1255 -#define _SWAP_3_r03 1256 -#define _SWAP_3_r13 1257 -#define _SWAP_3_r23 1258 -#define _SWAP_3_r33 1259 -#define _TIER2_RESUME_CHECK_r00 1260 -#define _TIER2_RESUME_CHECK_r11 1261 -#define _TIER2_RESUME_CHECK_r22 1262 -#define _TIER2_RESUME_CHECK_r33 1263 -#define _TO_BOOL_r11 1264 -#define _TO_BOOL_BOOL_r01 1265 -#define _TO_BOOL_BOOL_r11 1266 -#define _TO_BOOL_BOOL_r22 1267 -#define _TO_BOOL_BOOL_r33 1268 -#define _TO_BOOL_INT_r11 1269 -#define _TO_BOOL_LIST_r11 1270 -#define _TO_BOOL_NONE_r01 1271 -#define _TO_BOOL_NONE_r11 1272 -#define _TO_BOOL_NONE_r22 1273 -#define _TO_BOOL_NONE_r33 1274 -#define _TO_BOOL_STR_r11 1275 -#define _TRACE_RECORD_r00 1276 -#define _UNARY_INVERT_r11 1277 -#define _UNARY_NEGATIVE_r11 1278 -#define _UNARY_NOT_r01 1279 -#define _UNARY_NOT_r11 1280 -#define _UNARY_NOT_r22 1281 -#define _UNARY_NOT_r33 1282 -#define _UNPACK_EX_r10 1283 -#define _UNPACK_SEQUENCE_r10 1284 -#define _UNPACK_SEQUENCE_LIST_r10 1285 -#define _UNPACK_SEQUENCE_TUPLE_r10 1286 -#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1287 -#define _WITH_EXCEPT_START_r33 1288 -#define _YIELD_VALUE_r11 1289 -#define MAX_UOP_REGS_ID 1289 +#define _FOR_ITER_GEN_FRAME_r03 731 +#define _FOR_ITER_GEN_FRAME_r13 732 +#define _FOR_ITER_GEN_FRAME_r23 733 +#define _FOR_ITER_TIER_TWO_r23 734 +#define _GET_AITER_r11 735 +#define _GET_ANEXT_r12 736 +#define _GET_AWAITABLE_r11 737 +#define _GET_ITER_r12 738 +#define _GET_LEN_r12 739 +#define _GET_YIELD_FROM_ITER_r11 740 +#define _GUARD_BINARY_OP_EXTEND_r22 741 +#define _GUARD_CALLABLE_ISINSTANCE_r03 742 +#define _GUARD_CALLABLE_ISINSTANCE_r13 743 +#define _GUARD_CALLABLE_ISINSTANCE_r23 744 +#define _GUARD_CALLABLE_ISINSTANCE_r33 745 +#define _GUARD_CALLABLE_LEN_r03 746 +#define _GUARD_CALLABLE_LEN_r13 747 +#define _GUARD_CALLABLE_LEN_r23 748 +#define _GUARD_CALLABLE_LEN_r33 749 +#define _GUARD_CALLABLE_LIST_APPEND_r03 750 +#define _GUARD_CALLABLE_LIST_APPEND_r13 751 +#define _GUARD_CALLABLE_LIST_APPEND_r23 752 +#define _GUARD_CALLABLE_LIST_APPEND_r33 753 +#define _GUARD_CALLABLE_STR_1_r03 754 +#define _GUARD_CALLABLE_STR_1_r13 755 +#define _GUARD_CALLABLE_STR_1_r23 756 +#define _GUARD_CALLABLE_STR_1_r33 757 +#define _GUARD_CALLABLE_TUPLE_1_r03 758 +#define _GUARD_CALLABLE_TUPLE_1_r13 759 +#define _GUARD_CALLABLE_TUPLE_1_r23 760 +#define _GUARD_CALLABLE_TUPLE_1_r33 761 +#define _GUARD_CALLABLE_TYPE_1_r03 762 +#define _GUARD_CALLABLE_TYPE_1_r13 763 +#define _GUARD_CALLABLE_TYPE_1_r23 764 +#define _GUARD_CALLABLE_TYPE_1_r33 765 +#define _GUARD_DORV_NO_DICT_r01 766 +#define _GUARD_DORV_NO_DICT_r11 767 +#define _GUARD_DORV_NO_DICT_r22 768 +#define _GUARD_DORV_NO_DICT_r33 769 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 770 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 771 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 772 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 773 +#define _GUARD_GLOBALS_VERSION_r00 774 +#define _GUARD_GLOBALS_VERSION_r11 775 +#define _GUARD_GLOBALS_VERSION_r22 776 +#define _GUARD_GLOBALS_VERSION_r33 777 +#define _GUARD_IP_RETURN_GENERATOR_r00 778 +#define _GUARD_IP_RETURN_GENERATOR_r11 779 +#define _GUARD_IP_RETURN_GENERATOR_r22 780 +#define _GUARD_IP_RETURN_GENERATOR_r33 781 +#define _GUARD_IP_RETURN_VALUE_r00 782 +#define _GUARD_IP_RETURN_VALUE_r11 783 +#define _GUARD_IP_RETURN_VALUE_r22 784 +#define _GUARD_IP_RETURN_VALUE_r33 785 +#define _GUARD_IP_YIELD_VALUE_r00 786 +#define _GUARD_IP_YIELD_VALUE_r11 787 +#define _GUARD_IP_YIELD_VALUE_r22 788 +#define _GUARD_IP_YIELD_VALUE_r33 789 +#define _GUARD_IP__PUSH_FRAME_r00 790 +#define _GUARD_IP__PUSH_FRAME_r11 791 +#define _GUARD_IP__PUSH_FRAME_r22 792 +#define _GUARD_IP__PUSH_FRAME_r33 793 +#define _GUARD_IS_FALSE_POP_r00 794 +#define _GUARD_IS_FALSE_POP_r10 795 +#define _GUARD_IS_FALSE_POP_r21 796 +#define _GUARD_IS_FALSE_POP_r32 797 +#define _GUARD_IS_NONE_POP_r00 798 +#define _GUARD_IS_NONE_POP_r10 799 +#define _GUARD_IS_NONE_POP_r21 800 +#define _GUARD_IS_NONE_POP_r32 801 +#define _GUARD_IS_NOT_NONE_POP_r10 802 +#define _GUARD_IS_TRUE_POP_r00 803 +#define _GUARD_IS_TRUE_POP_r10 804 +#define _GUARD_IS_TRUE_POP_r21 805 +#define _GUARD_IS_TRUE_POP_r32 806 +#define _GUARD_KEYS_VERSION_r01 807 +#define _GUARD_KEYS_VERSION_r11 808 +#define _GUARD_KEYS_VERSION_r22 809 +#define _GUARD_KEYS_VERSION_r33 810 +#define _GUARD_NOS_DICT_r02 811 +#define _GUARD_NOS_DICT_r12 812 +#define _GUARD_NOS_DICT_r22 813 +#define _GUARD_NOS_DICT_r33 814 +#define _GUARD_NOS_FLOAT_r02 815 +#define _GUARD_NOS_FLOAT_r12 816 +#define _GUARD_NOS_FLOAT_r22 817 +#define _GUARD_NOS_FLOAT_r33 818 +#define _GUARD_NOS_INT_r02 819 +#define _GUARD_NOS_INT_r12 820 +#define _GUARD_NOS_INT_r22 821 +#define _GUARD_NOS_INT_r33 822 +#define _GUARD_NOS_LIST_r02 823 +#define _GUARD_NOS_LIST_r12 824 +#define _GUARD_NOS_LIST_r22 825 +#define _GUARD_NOS_LIST_r33 826 +#define _GUARD_NOS_NOT_NULL_r02 827 +#define _GUARD_NOS_NOT_NULL_r12 828 +#define _GUARD_NOS_NOT_NULL_r22 829 +#define _GUARD_NOS_NOT_NULL_r33 830 +#define _GUARD_NOS_NULL_r02 831 +#define _GUARD_NOS_NULL_r12 832 +#define _GUARD_NOS_NULL_r22 833 +#define _GUARD_NOS_NULL_r33 834 +#define _GUARD_NOS_OVERFLOWED_r02 835 +#define _GUARD_NOS_OVERFLOWED_r12 836 +#define _GUARD_NOS_OVERFLOWED_r22 837 +#define _GUARD_NOS_OVERFLOWED_r33 838 +#define _GUARD_NOS_TUPLE_r02 839 +#define _GUARD_NOS_TUPLE_r12 840 +#define _GUARD_NOS_TUPLE_r22 841 +#define _GUARD_NOS_TUPLE_r33 842 +#define _GUARD_NOS_UNICODE_r02 843 +#define _GUARD_NOS_UNICODE_r12 844 +#define _GUARD_NOS_UNICODE_r22 845 +#define _GUARD_NOS_UNICODE_r33 846 +#define _GUARD_NOT_EXHAUSTED_LIST_r02 847 +#define _GUARD_NOT_EXHAUSTED_LIST_r12 848 +#define _GUARD_NOT_EXHAUSTED_LIST_r22 849 +#define _GUARD_NOT_EXHAUSTED_LIST_r33 850 +#define _GUARD_NOT_EXHAUSTED_RANGE_r02 851 +#define _GUARD_NOT_EXHAUSTED_RANGE_r12 852 +#define _GUARD_NOT_EXHAUSTED_RANGE_r22 853 +#define _GUARD_NOT_EXHAUSTED_RANGE_r33 854 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 855 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 856 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 857 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 858 +#define _GUARD_THIRD_NULL_r03 859 +#define _GUARD_THIRD_NULL_r13 860 +#define _GUARD_THIRD_NULL_r23 861 +#define _GUARD_THIRD_NULL_r33 862 +#define _GUARD_TOS_ANY_SET_r01 863 +#define _GUARD_TOS_ANY_SET_r11 864 +#define _GUARD_TOS_ANY_SET_r22 865 +#define _GUARD_TOS_ANY_SET_r33 866 +#define _GUARD_TOS_DICT_r01 867 +#define _GUARD_TOS_DICT_r11 868 +#define _GUARD_TOS_DICT_r22 869 +#define _GUARD_TOS_DICT_r33 870 +#define _GUARD_TOS_FLOAT_r01 871 +#define _GUARD_TOS_FLOAT_r11 872 +#define _GUARD_TOS_FLOAT_r22 873 +#define _GUARD_TOS_FLOAT_r33 874 +#define _GUARD_TOS_INT_r01 875 +#define _GUARD_TOS_INT_r11 876 +#define _GUARD_TOS_INT_r22 877 +#define _GUARD_TOS_INT_r33 878 +#define _GUARD_TOS_LIST_r01 879 +#define _GUARD_TOS_LIST_r11 880 +#define _GUARD_TOS_LIST_r22 881 +#define _GUARD_TOS_LIST_r33 882 +#define _GUARD_TOS_OVERFLOWED_r01 883 +#define _GUARD_TOS_OVERFLOWED_r11 884 +#define _GUARD_TOS_OVERFLOWED_r22 885 +#define _GUARD_TOS_OVERFLOWED_r33 886 +#define _GUARD_TOS_SLICE_r01 887 +#define _GUARD_TOS_SLICE_r11 888 +#define _GUARD_TOS_SLICE_r22 889 +#define _GUARD_TOS_SLICE_r33 890 +#define _GUARD_TOS_TUPLE_r01 891 +#define _GUARD_TOS_TUPLE_r11 892 +#define _GUARD_TOS_TUPLE_r22 893 +#define _GUARD_TOS_TUPLE_r33 894 +#define _GUARD_TOS_UNICODE_r01 895 +#define _GUARD_TOS_UNICODE_r11 896 +#define _GUARD_TOS_UNICODE_r22 897 +#define _GUARD_TOS_UNICODE_r33 898 +#define _GUARD_TYPE_VERSION_r01 899 +#define _GUARD_TYPE_VERSION_r11 900 +#define _GUARD_TYPE_VERSION_r22 901 +#define _GUARD_TYPE_VERSION_r33 902 +#define _GUARD_TYPE_VERSION_AND_LOCK_r01 903 +#define _GUARD_TYPE_VERSION_AND_LOCK_r11 904 +#define _GUARD_TYPE_VERSION_AND_LOCK_r22 905 +#define _GUARD_TYPE_VERSION_AND_LOCK_r33 906 +#define _HANDLE_PENDING_AND_DEOPT_r00 907 +#define _HANDLE_PENDING_AND_DEOPT_r10 908 +#define _HANDLE_PENDING_AND_DEOPT_r20 909 +#define _HANDLE_PENDING_AND_DEOPT_r30 910 +#define _IMPORT_FROM_r12 911 +#define _IMPORT_NAME_r21 912 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 913 +#define _INIT_CALL_PY_EXACT_ARGS_r01 914 +#define _INIT_CALL_PY_EXACT_ARGS_0_r01 915 +#define _INIT_CALL_PY_EXACT_ARGS_1_r01 916 +#define _INIT_CALL_PY_EXACT_ARGS_2_r01 917 +#define _INIT_CALL_PY_EXACT_ARGS_3_r01 918 +#define _INIT_CALL_PY_EXACT_ARGS_4_r01 919 +#define _INSERT_NULL_r10 920 +#define _INSTRUMENTED_FOR_ITER_r23 921 +#define _INSTRUMENTED_INSTRUCTION_r00 922 +#define _INSTRUMENTED_JUMP_FORWARD_r00 923 +#define _INSTRUMENTED_JUMP_FORWARD_r11 924 +#define _INSTRUMENTED_JUMP_FORWARD_r22 925 +#define _INSTRUMENTED_JUMP_FORWARD_r33 926 +#define _INSTRUMENTED_LINE_r00 927 +#define _INSTRUMENTED_NOT_TAKEN_r00 928 +#define _INSTRUMENTED_NOT_TAKEN_r11 929 +#define _INSTRUMENTED_NOT_TAKEN_r22 930 +#define _INSTRUMENTED_NOT_TAKEN_r33 931 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 932 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 933 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 934 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 935 +#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 936 +#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 937 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 938 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 939 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 940 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 941 +#define _IS_NONE_r11 942 +#define _IS_OP_r21 943 +#define _ITER_CHECK_LIST_r02 944 +#define _ITER_CHECK_LIST_r12 945 +#define _ITER_CHECK_LIST_r22 946 +#define _ITER_CHECK_LIST_r33 947 +#define _ITER_CHECK_RANGE_r02 948 +#define _ITER_CHECK_RANGE_r12 949 +#define _ITER_CHECK_RANGE_r22 950 +#define _ITER_CHECK_RANGE_r33 951 +#define _ITER_CHECK_TUPLE_r02 952 +#define _ITER_CHECK_TUPLE_r12 953 +#define _ITER_CHECK_TUPLE_r22 954 +#define _ITER_CHECK_TUPLE_r33 955 +#define _ITER_JUMP_LIST_r02 956 +#define _ITER_JUMP_LIST_r12 957 +#define _ITER_JUMP_LIST_r22 958 +#define _ITER_JUMP_LIST_r33 959 +#define _ITER_JUMP_RANGE_r02 960 +#define _ITER_JUMP_RANGE_r12 961 +#define _ITER_JUMP_RANGE_r22 962 +#define _ITER_JUMP_RANGE_r33 963 +#define _ITER_JUMP_TUPLE_r02 964 +#define _ITER_JUMP_TUPLE_r12 965 +#define _ITER_JUMP_TUPLE_r22 966 +#define _ITER_JUMP_TUPLE_r33 967 +#define _ITER_NEXT_LIST_r23 968 +#define _ITER_NEXT_LIST_TIER_TWO_r23 969 +#define _ITER_NEXT_RANGE_r03 970 +#define _ITER_NEXT_RANGE_r13 971 +#define _ITER_NEXT_RANGE_r23 972 +#define _ITER_NEXT_TUPLE_r03 973 +#define _ITER_NEXT_TUPLE_r13 974 +#define _ITER_NEXT_TUPLE_r23 975 +#define _JUMP_BACKWARD_NO_INTERRUPT_r00 976 +#define _JUMP_BACKWARD_NO_INTERRUPT_r11 977 +#define _JUMP_BACKWARD_NO_INTERRUPT_r22 978 +#define _JUMP_BACKWARD_NO_INTERRUPT_r33 979 +#define _JUMP_TO_TOP_r00 980 +#define _LIST_APPEND_r10 981 +#define _LIST_EXTEND_r10 982 +#define _LOAD_ATTR_r10 983 +#define _LOAD_ATTR_CLASS_r11 984 +#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_r11 985 +#define _LOAD_ATTR_INSTANCE_VALUE_r02 986 +#define _LOAD_ATTR_INSTANCE_VALUE_r12 987 +#define _LOAD_ATTR_INSTANCE_VALUE_r23 988 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 989 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 990 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 991 +#define _LOAD_ATTR_METHOD_NO_DICT_r02 992 +#define _LOAD_ATTR_METHOD_NO_DICT_r12 993 +#define _LOAD_ATTR_METHOD_NO_DICT_r23 994 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 995 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 996 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 997 +#define _LOAD_ATTR_MODULE_r11 998 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 999 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1000 +#define _LOAD_ATTR_PROPERTY_FRAME_r11 1001 +#define _LOAD_ATTR_SLOT_r11 1002 +#define _LOAD_ATTR_WITH_HINT_r12 1003 +#define _LOAD_BUILD_CLASS_r01 1004 +#define _LOAD_BYTECODE_r00 1005 +#define _LOAD_COMMON_CONSTANT_r01 1006 +#define _LOAD_COMMON_CONSTANT_r12 1007 +#define _LOAD_COMMON_CONSTANT_r23 1008 +#define _LOAD_CONST_r01 1009 +#define _LOAD_CONST_r12 1010 +#define _LOAD_CONST_r23 1011 +#define _LOAD_CONST_INLINE_r01 1012 +#define _LOAD_CONST_INLINE_r12 1013 +#define _LOAD_CONST_INLINE_r23 1014 +#define _LOAD_CONST_INLINE_BORROW_r01 1015 +#define _LOAD_CONST_INLINE_BORROW_r12 1016 +#define _LOAD_CONST_INLINE_BORROW_r23 1017 +#define _LOAD_CONST_UNDER_INLINE_r02 1018 +#define _LOAD_CONST_UNDER_INLINE_r12 1019 +#define _LOAD_CONST_UNDER_INLINE_r23 1020 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r02 1021 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r12 1022 +#define _LOAD_CONST_UNDER_INLINE_BORROW_r23 1023 +#define _LOAD_DEREF_r01 1024 +#define _LOAD_FAST_r01 1025 +#define _LOAD_FAST_r12 1026 +#define _LOAD_FAST_r23 1027 +#define _LOAD_FAST_0_r01 1028 +#define _LOAD_FAST_0_r12 1029 +#define _LOAD_FAST_0_r23 1030 +#define _LOAD_FAST_1_r01 1031 +#define _LOAD_FAST_1_r12 1032 +#define _LOAD_FAST_1_r23 1033 +#define _LOAD_FAST_2_r01 1034 +#define _LOAD_FAST_2_r12 1035 +#define _LOAD_FAST_2_r23 1036 +#define _LOAD_FAST_3_r01 1037 +#define _LOAD_FAST_3_r12 1038 +#define _LOAD_FAST_3_r23 1039 +#define _LOAD_FAST_4_r01 1040 +#define _LOAD_FAST_4_r12 1041 +#define _LOAD_FAST_4_r23 1042 +#define _LOAD_FAST_5_r01 1043 +#define _LOAD_FAST_5_r12 1044 +#define _LOAD_FAST_5_r23 1045 +#define _LOAD_FAST_6_r01 1046 +#define _LOAD_FAST_6_r12 1047 +#define _LOAD_FAST_6_r23 1048 +#define _LOAD_FAST_7_r01 1049 +#define _LOAD_FAST_7_r12 1050 +#define _LOAD_FAST_7_r23 1051 +#define _LOAD_FAST_AND_CLEAR_r01 1052 +#define _LOAD_FAST_AND_CLEAR_r12 1053 +#define _LOAD_FAST_AND_CLEAR_r23 1054 +#define _LOAD_FAST_BORROW_r01 1055 +#define _LOAD_FAST_BORROW_r12 1056 +#define _LOAD_FAST_BORROW_r23 1057 +#define _LOAD_FAST_BORROW_0_r01 1058 +#define _LOAD_FAST_BORROW_0_r12 1059 +#define _LOAD_FAST_BORROW_0_r23 1060 +#define _LOAD_FAST_BORROW_1_r01 1061 +#define _LOAD_FAST_BORROW_1_r12 1062 +#define _LOAD_FAST_BORROW_1_r23 1063 +#define _LOAD_FAST_BORROW_2_r01 1064 +#define _LOAD_FAST_BORROW_2_r12 1065 +#define _LOAD_FAST_BORROW_2_r23 1066 +#define _LOAD_FAST_BORROW_3_r01 1067 +#define _LOAD_FAST_BORROW_3_r12 1068 +#define _LOAD_FAST_BORROW_3_r23 1069 +#define _LOAD_FAST_BORROW_4_r01 1070 +#define _LOAD_FAST_BORROW_4_r12 1071 +#define _LOAD_FAST_BORROW_4_r23 1072 +#define _LOAD_FAST_BORROW_5_r01 1073 +#define _LOAD_FAST_BORROW_5_r12 1074 +#define _LOAD_FAST_BORROW_5_r23 1075 +#define _LOAD_FAST_BORROW_6_r01 1076 +#define _LOAD_FAST_BORROW_6_r12 1077 +#define _LOAD_FAST_BORROW_6_r23 1078 +#define _LOAD_FAST_BORROW_7_r01 1079 +#define _LOAD_FAST_BORROW_7_r12 1080 +#define _LOAD_FAST_BORROW_7_r23 1081 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1082 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1083 +#define _LOAD_FAST_CHECK_r01 1084 +#define _LOAD_FAST_CHECK_r12 1085 +#define _LOAD_FAST_CHECK_r23 1086 +#define _LOAD_FAST_LOAD_FAST_r02 1087 +#define _LOAD_FAST_LOAD_FAST_r13 1088 +#define _LOAD_FROM_DICT_OR_DEREF_r11 1089 +#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1090 +#define _LOAD_GLOBAL_r00 1091 +#define _LOAD_GLOBAL_BUILTINS_r01 1092 +#define _LOAD_GLOBAL_MODULE_r01 1093 +#define _LOAD_LOCALS_r01 1094 +#define _LOAD_LOCALS_r12 1095 +#define _LOAD_LOCALS_r23 1096 +#define _LOAD_NAME_r01 1097 +#define _LOAD_SMALL_INT_r01 1098 +#define _LOAD_SMALL_INT_r12 1099 +#define _LOAD_SMALL_INT_r23 1100 +#define _LOAD_SMALL_INT_0_r01 1101 +#define _LOAD_SMALL_INT_0_r12 1102 +#define _LOAD_SMALL_INT_0_r23 1103 +#define _LOAD_SMALL_INT_1_r01 1104 +#define _LOAD_SMALL_INT_1_r12 1105 +#define _LOAD_SMALL_INT_1_r23 1106 +#define _LOAD_SMALL_INT_2_r01 1107 +#define _LOAD_SMALL_INT_2_r12 1108 +#define _LOAD_SMALL_INT_2_r23 1109 +#define _LOAD_SMALL_INT_3_r01 1110 +#define _LOAD_SMALL_INT_3_r12 1111 +#define _LOAD_SMALL_INT_3_r23 1112 +#define _LOAD_SPECIAL_r00 1113 +#define _LOAD_SUPER_ATTR_ATTR_r31 1114 +#define _LOAD_SUPER_ATTR_METHOD_r32 1115 +#define _MAKE_CALLARGS_A_TUPLE_r33 1116 +#define _MAKE_CELL_r00 1117 +#define _MAKE_FUNCTION_r11 1118 +#define _MAKE_WARM_r00 1119 +#define _MAKE_WARM_r11 1120 +#define _MAKE_WARM_r22 1121 +#define _MAKE_WARM_r33 1122 +#define _MAP_ADD_r20 1123 +#define _MATCH_CLASS_r31 1124 +#define _MATCH_KEYS_r23 1125 +#define _MATCH_MAPPING_r02 1126 +#define _MATCH_MAPPING_r12 1127 +#define _MATCH_MAPPING_r23 1128 +#define _MATCH_SEQUENCE_r02 1129 +#define _MATCH_SEQUENCE_r12 1130 +#define _MATCH_SEQUENCE_r23 1131 +#define _MAYBE_EXPAND_METHOD_r00 1132 +#define _MAYBE_EXPAND_METHOD_KW_r11 1133 +#define _MONITOR_CALL_r00 1134 +#define _MONITOR_CALL_KW_r11 1135 +#define _MONITOR_JUMP_BACKWARD_r00 1136 +#define _MONITOR_JUMP_BACKWARD_r11 1137 +#define _MONITOR_JUMP_BACKWARD_r22 1138 +#define _MONITOR_JUMP_BACKWARD_r33 1139 +#define _MONITOR_RESUME_r00 1140 +#define _NOP_r00 1141 +#define _NOP_r11 1142 +#define _NOP_r22 1143 +#define _NOP_r33 1144 +#define _POP_CALL_r20 1145 +#define _POP_CALL_LOAD_CONST_INLINE_BORROW_r21 1146 +#define _POP_CALL_ONE_r30 1147 +#define _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 1148 +#define _POP_CALL_TWO_r30 1149 +#define _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31 1150 +#define _POP_EXCEPT_r10 1151 +#define _POP_ITER_r20 1152 +#define _POP_JUMP_IF_FALSE_r00 1153 +#define _POP_JUMP_IF_FALSE_r10 1154 +#define _POP_JUMP_IF_FALSE_r21 1155 +#define _POP_JUMP_IF_FALSE_r32 1156 +#define _POP_JUMP_IF_TRUE_r00 1157 +#define _POP_JUMP_IF_TRUE_r10 1158 +#define _POP_JUMP_IF_TRUE_r21 1159 +#define _POP_JUMP_IF_TRUE_r32 1160 +#define _POP_TOP_r10 1161 +#define _POP_TOP_FLOAT_r00 1162 +#define _POP_TOP_FLOAT_r10 1163 +#define _POP_TOP_FLOAT_r21 1164 +#define _POP_TOP_FLOAT_r32 1165 +#define _POP_TOP_INT_r00 1166 +#define _POP_TOP_INT_r10 1167 +#define _POP_TOP_INT_r21 1168 +#define _POP_TOP_INT_r32 1169 +#define _POP_TOP_LOAD_CONST_INLINE_r11 1170 +#define _POP_TOP_LOAD_CONST_INLINE_BORROW_r11 1171 +#define _POP_TOP_NOP_r00 1172 +#define _POP_TOP_NOP_r10 1173 +#define _POP_TOP_NOP_r21 1174 +#define _POP_TOP_NOP_r32 1175 +#define _POP_TOP_UNICODE_r00 1176 +#define _POP_TOP_UNICODE_r10 1177 +#define _POP_TOP_UNICODE_r21 1178 +#define _POP_TOP_UNICODE_r32 1179 +#define _POP_TWO_r20 1180 +#define _POP_TWO_LOAD_CONST_INLINE_BORROW_r21 1181 +#define _PUSH_EXC_INFO_r02 1182 +#define _PUSH_EXC_INFO_r12 1183 +#define _PUSH_EXC_INFO_r23 1184 +#define _PUSH_FRAME_r10 1185 +#define _PUSH_NULL_r01 1186 +#define _PUSH_NULL_r12 1187 +#define _PUSH_NULL_r23 1188 +#define _PUSH_NULL_CONDITIONAL_r00 1189 +#define _PY_FRAME_GENERAL_r01 1190 +#define _PY_FRAME_KW_r11 1191 +#define _QUICKEN_RESUME_r00 1192 +#define _QUICKEN_RESUME_r11 1193 +#define _QUICKEN_RESUME_r22 1194 +#define _QUICKEN_RESUME_r33 1195 +#define _REPLACE_WITH_TRUE_r11 1196 +#define _RESUME_CHECK_r00 1197 +#define _RESUME_CHECK_r11 1198 +#define _RESUME_CHECK_r22 1199 +#define _RESUME_CHECK_r33 1200 +#define _RETURN_GENERATOR_r01 1201 +#define _RETURN_VALUE_r11 1202 +#define _SAVE_RETURN_OFFSET_r00 1203 +#define _SAVE_RETURN_OFFSET_r11 1204 +#define _SAVE_RETURN_OFFSET_r22 1205 +#define _SAVE_RETURN_OFFSET_r33 1206 +#define _SEND_r22 1207 +#define _SEND_GEN_FRAME_r22 1208 +#define _SETUP_ANNOTATIONS_r00 1209 +#define _SET_ADD_r10 1210 +#define _SET_FUNCTION_ATTRIBUTE_r01 1211 +#define _SET_FUNCTION_ATTRIBUTE_r11 1212 +#define _SET_FUNCTION_ATTRIBUTE_r21 1213 +#define _SET_FUNCTION_ATTRIBUTE_r32 1214 +#define _SET_IP_r00 1215 +#define _SET_IP_r11 1216 +#define _SET_IP_r22 1217 +#define _SET_IP_r33 1218 +#define _SET_UPDATE_r10 1219 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03 1220 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13 1221 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23 1222 +#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33 1223 +#define _SPILL_OR_RELOAD_r01 1224 +#define _SPILL_OR_RELOAD_r02 1225 +#define _SPILL_OR_RELOAD_r03 1226 +#define _SPILL_OR_RELOAD_r10 1227 +#define _SPILL_OR_RELOAD_r12 1228 +#define _SPILL_OR_RELOAD_r13 1229 +#define _SPILL_OR_RELOAD_r20 1230 +#define _SPILL_OR_RELOAD_r21 1231 +#define _SPILL_OR_RELOAD_r23 1232 +#define _SPILL_OR_RELOAD_r30 1233 +#define _SPILL_OR_RELOAD_r31 1234 +#define _SPILL_OR_RELOAD_r32 1235 +#define _START_EXECUTOR_r00 1236 +#define _STORE_ATTR_r20 1237 +#define _STORE_ATTR_INSTANCE_VALUE_r21 1238 +#define _STORE_ATTR_SLOT_r21 1239 +#define _STORE_ATTR_WITH_HINT_r21 1240 +#define _STORE_DEREF_r10 1241 +#define _STORE_FAST_r10 1242 +#define _STORE_FAST_0_r10 1243 +#define _STORE_FAST_1_r10 1244 +#define _STORE_FAST_2_r10 1245 +#define _STORE_FAST_3_r10 1246 +#define _STORE_FAST_4_r10 1247 +#define _STORE_FAST_5_r10 1248 +#define _STORE_FAST_6_r10 1249 +#define _STORE_FAST_7_r10 1250 +#define _STORE_FAST_LOAD_FAST_r11 1251 +#define _STORE_FAST_STORE_FAST_r20 1252 +#define _STORE_GLOBAL_r10 1253 +#define _STORE_NAME_r10 1254 +#define _STORE_SLICE_r30 1255 +#define _STORE_SUBSCR_r30 1256 +#define _STORE_SUBSCR_DICT_r31 1257 +#define _STORE_SUBSCR_LIST_INT_r32 1258 +#define _SWAP_r11 1259 +#define _SWAP_2_r02 1260 +#define _SWAP_2_r12 1261 +#define _SWAP_2_r22 1262 +#define _SWAP_2_r33 1263 +#define _SWAP_3_r03 1264 +#define _SWAP_3_r13 1265 +#define _SWAP_3_r23 1266 +#define _SWAP_3_r33 1267 +#define _TIER2_RESUME_CHECK_r00 1268 +#define _TIER2_RESUME_CHECK_r11 1269 +#define _TIER2_RESUME_CHECK_r22 1270 +#define _TIER2_RESUME_CHECK_r33 1271 +#define _TO_BOOL_r11 1272 +#define _TO_BOOL_BOOL_r01 1273 +#define _TO_BOOL_BOOL_r11 1274 +#define _TO_BOOL_BOOL_r22 1275 +#define _TO_BOOL_BOOL_r33 1276 +#define _TO_BOOL_INT_r11 1277 +#define _TO_BOOL_LIST_r11 1278 +#define _TO_BOOL_NONE_r01 1279 +#define _TO_BOOL_NONE_r11 1280 +#define _TO_BOOL_NONE_r22 1281 +#define _TO_BOOL_NONE_r33 1282 +#define _TO_BOOL_STR_r11 1283 +#define _TRACE_RECORD_r00 1284 +#define _UNARY_INVERT_r11 1285 +#define _UNARY_NEGATIVE_r11 1286 +#define _UNARY_NOT_r01 1287 +#define _UNARY_NOT_r11 1288 +#define _UNARY_NOT_r22 1289 +#define _UNARY_NOT_r33 1290 +#define _UNPACK_EX_r10 1291 +#define _UNPACK_SEQUENCE_r10 1292 +#define _UNPACK_SEQUENCE_LIST_r10 1293 +#define _UNPACK_SEQUENCE_TUPLE_r10 1294 +#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1295 +#define _WITH_EXCEPT_START_r33 1296 +#define _YIELD_VALUE_r11 1297 +#define MAX_UOP_REGS_ID 1297 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index f600468c321..d84c88c9243 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -111,7 +111,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_PURE_FLAG, [_BINARY_OP_ADD_FLOAT] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_PURE_FLAG, [_BINARY_OP_SUBTRACT_FLOAT] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_PURE_FLAG, - [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_PURE_FLAG, [_BINARY_OP_INPLACE_ADD_UNICODE] = HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_BINARY_OP_EXTEND] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG, @@ -119,7 +119,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_BINARY_OP_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, [_BINARY_OP_SUBSCR_LIST_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_BINARY_OP_SUBSCR_STR_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_BINARY_OP_SUBSCR_STR_INT] = HAS_DEOPT_FLAG, [_GUARD_NOS_TUPLE] = HAS_EXIT_FLAG, [_GUARD_TOS_TUPLE] = HAS_EXIT_FLAG, [_BINARY_OP_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, @@ -189,9 +189,9 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_GUARD_TYPE_VERSION] = HAS_EXIT_FLAG, [_GUARD_TYPE_VERSION_AND_LOCK] = HAS_EXIT_FLAG, [_CHECK_MANAGED_OBJECT_HAS_VALUES] = HAS_DEOPT_FLAG, - [_LOAD_ATTR_INSTANCE_VALUE] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR_INSTANCE_VALUE] = HAS_DEOPT_FLAG, [_LOAD_ATTR_MODULE] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, - [_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG, [_LOAD_ATTR_SLOT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, [_CHECK_ATTR_CLASS] = HAS_EXIT_FLAG, [_LOAD_ATTR_CLASS] = HAS_ESCAPES_FLAG, @@ -335,6 +335,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_POP_CALL_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = 0, [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_LOAD_CONST_UNDER_INLINE] = 0, [_LOAD_CONST_UNDER_INLINE_BORROW] = 0, @@ -1050,12 +1051,12 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { }, }, [_BINARY_OP_ADD_UNICODE] = { - .best = { 0, 1, 2, 3 }, + .best = { 0, 1, 2, 2 }, .entries = { - { 1, 0, _BINARY_OP_ADD_UNICODE_r01 }, - { 1, 1, _BINARY_OP_ADD_UNICODE_r11 }, - { 1, 2, _BINARY_OP_ADD_UNICODE_r21 }, - { 2, 3, _BINARY_OP_ADD_UNICODE_r32 }, + { 3, 0, _BINARY_OP_ADD_UNICODE_r03 }, + { 3, 1, _BINARY_OP_ADD_UNICODE_r13 }, + { 3, 2, _BINARY_OP_ADD_UNICODE_r23 }, + { -1, -1, -1 }, }, }, [_BINARY_OP_INPLACE_ADD_UNICODE] = { @@ -1108,7 +1109,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { .entries = { { -1, -1, -1 }, { -1, -1, -1 }, - { 1, 2, _BINARY_OP_SUBSCR_LIST_INT_r21 }, + { 3, 2, _BINARY_OP_SUBSCR_LIST_INT_r23 }, { -1, -1, -1 }, }, }, @@ -1126,7 +1127,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { .entries = { { -1, -1, -1 }, { -1, -1, -1 }, - { 1, 2, _BINARY_OP_SUBSCR_STR_INT_r21 }, + { 3, 2, _BINARY_OP_SUBSCR_STR_INT_r23 }, { -1, -1, -1 }, }, }, @@ -1752,11 +1753,11 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { }, }, [_LOAD_ATTR_INSTANCE_VALUE] = { - .best = { 1, 1, 1, 1 }, + .best = { 0, 1, 2, 2 }, .entries = { - { -1, -1, -1 }, - { 1, 1, _LOAD_ATTR_INSTANCE_VALUE_r11 }, - { -1, -1, -1 }, + { 2, 0, _LOAD_ATTR_INSTANCE_VALUE_r02 }, + { 2, 1, _LOAD_ATTR_INSTANCE_VALUE_r12 }, + { 3, 2, _LOAD_ATTR_INSTANCE_VALUE_r23 }, { -1, -1, -1 }, }, }, @@ -1773,7 +1774,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { .best = { 1, 1, 1, 1 }, .entries = { { -1, -1, -1 }, - { 1, 1, _LOAD_ATTR_WITH_HINT_r11 }, + { 2, 1, _LOAD_ATTR_WITH_HINT_r12 }, { -1, -1, -1 }, { -1, -1, -1 }, }, @@ -1837,7 +1838,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { .entries = { { -1, -1, -1 }, { -1, -1, -1 }, - { 0, 2, _STORE_ATTR_WITH_HINT_r20 }, + { 1, 2, _STORE_ATTR_WITH_HINT_r21 }, { -1, -1, -1 }, }, }, @@ -2130,10 +2131,10 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { }, }, [_FOR_ITER_GEN_FRAME] = { - .best = { 2, 2, 2, 2 }, + .best = { 0, 1, 2, 2 }, .entries = { - { -1, -1, -1 }, - { -1, -1, -1 }, + { 3, 0, _FOR_ITER_GEN_FRAME_r03 }, + { 3, 1, _FOR_ITER_GEN_FRAME_r13 }, { 3, 2, _FOR_ITER_GEN_FRAME_r23 }, { -1, -1, -1 }, }, @@ -3065,6 +3066,15 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { { 1, 3, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31 }, }, }, + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = { + .best = { 0, 1, 2, 3 }, + .entries = { + { 3, 0, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03 }, + { 3, 1, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13 }, + { 3, 2, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23 }, + { 3, 3, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33 }, + }, + }, [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = { .best = { 3, 3, 3, 3 }, .entries = { @@ -3414,18 +3424,17 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_BINARY_OP_SUBTRACT_FLOAT_r03] = _BINARY_OP_SUBTRACT_FLOAT, [_BINARY_OP_SUBTRACT_FLOAT_r13] = _BINARY_OP_SUBTRACT_FLOAT, [_BINARY_OP_SUBTRACT_FLOAT_r23] = _BINARY_OP_SUBTRACT_FLOAT, - [_BINARY_OP_ADD_UNICODE_r01] = _BINARY_OP_ADD_UNICODE, - [_BINARY_OP_ADD_UNICODE_r11] = _BINARY_OP_ADD_UNICODE, - [_BINARY_OP_ADD_UNICODE_r21] = _BINARY_OP_ADD_UNICODE, - [_BINARY_OP_ADD_UNICODE_r32] = _BINARY_OP_ADD_UNICODE, + [_BINARY_OP_ADD_UNICODE_r03] = _BINARY_OP_ADD_UNICODE, + [_BINARY_OP_ADD_UNICODE_r13] = _BINARY_OP_ADD_UNICODE, + [_BINARY_OP_ADD_UNICODE_r23] = _BINARY_OP_ADD_UNICODE, [_BINARY_OP_INPLACE_ADD_UNICODE_r20] = _BINARY_OP_INPLACE_ADD_UNICODE, [_GUARD_BINARY_OP_EXTEND_r22] = _GUARD_BINARY_OP_EXTEND, [_BINARY_OP_EXTEND_r21] = _BINARY_OP_EXTEND, [_BINARY_SLICE_r31] = _BINARY_SLICE, [_STORE_SLICE_r30] = _STORE_SLICE, - [_BINARY_OP_SUBSCR_LIST_INT_r21] = _BINARY_OP_SUBSCR_LIST_INT, + [_BINARY_OP_SUBSCR_LIST_INT_r23] = _BINARY_OP_SUBSCR_LIST_INT, [_BINARY_OP_SUBSCR_LIST_SLICE_r21] = _BINARY_OP_SUBSCR_LIST_SLICE, - [_BINARY_OP_SUBSCR_STR_INT_r21] = _BINARY_OP_SUBSCR_STR_INT, + [_BINARY_OP_SUBSCR_STR_INT_r23] = _BINARY_OP_SUBSCR_STR_INT, [_GUARD_NOS_TUPLE_r02] = _GUARD_NOS_TUPLE, [_GUARD_NOS_TUPLE_r12] = _GUARD_NOS_TUPLE, [_GUARD_NOS_TUPLE_r22] = _GUARD_NOS_TUPLE, @@ -3529,9 +3538,11 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_CHECK_MANAGED_OBJECT_HAS_VALUES_r11] = _CHECK_MANAGED_OBJECT_HAS_VALUES, [_CHECK_MANAGED_OBJECT_HAS_VALUES_r22] = _CHECK_MANAGED_OBJECT_HAS_VALUES, [_CHECK_MANAGED_OBJECT_HAS_VALUES_r33] = _CHECK_MANAGED_OBJECT_HAS_VALUES, - [_LOAD_ATTR_INSTANCE_VALUE_r11] = _LOAD_ATTR_INSTANCE_VALUE, + [_LOAD_ATTR_INSTANCE_VALUE_r02] = _LOAD_ATTR_INSTANCE_VALUE, + [_LOAD_ATTR_INSTANCE_VALUE_r12] = _LOAD_ATTR_INSTANCE_VALUE, + [_LOAD_ATTR_INSTANCE_VALUE_r23] = _LOAD_ATTR_INSTANCE_VALUE, [_LOAD_ATTR_MODULE_r11] = _LOAD_ATTR_MODULE, - [_LOAD_ATTR_WITH_HINT_r11] = _LOAD_ATTR_WITH_HINT, + [_LOAD_ATTR_WITH_HINT_r12] = _LOAD_ATTR_WITH_HINT, [_LOAD_ATTR_SLOT_r11] = _LOAD_ATTR_SLOT, [_CHECK_ATTR_CLASS_r01] = _CHECK_ATTR_CLASS, [_CHECK_ATTR_CLASS_r11] = _CHECK_ATTR_CLASS, @@ -3544,7 +3555,7 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_GUARD_DORV_NO_DICT_r22] = _GUARD_DORV_NO_DICT, [_GUARD_DORV_NO_DICT_r33] = _GUARD_DORV_NO_DICT, [_STORE_ATTR_INSTANCE_VALUE_r21] = _STORE_ATTR_INSTANCE_VALUE, - [_STORE_ATTR_WITH_HINT_r20] = _STORE_ATTR_WITH_HINT, + [_STORE_ATTR_WITH_HINT_r21] = _STORE_ATTR_WITH_HINT, [_STORE_ATTR_SLOT_r21] = _STORE_ATTR_SLOT, [_COMPARE_OP_r21] = _COMPARE_OP, [_COMPARE_OP_FLOAT_r01] = _COMPARE_OP_FLOAT, @@ -3609,6 +3620,8 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_ITER_NEXT_RANGE_r03] = _ITER_NEXT_RANGE, [_ITER_NEXT_RANGE_r13] = _ITER_NEXT_RANGE, [_ITER_NEXT_RANGE_r23] = _ITER_NEXT_RANGE, + [_FOR_ITER_GEN_FRAME_r03] = _FOR_ITER_GEN_FRAME, + [_FOR_ITER_GEN_FRAME_r13] = _FOR_ITER_GEN_FRAME, [_FOR_ITER_GEN_FRAME_r23] = _FOR_ITER_GEN_FRAME, [_INSERT_NULL_r10] = _INSERT_NULL, [_LOAD_SPECIAL_r00] = _LOAD_SPECIAL, @@ -3816,6 +3829,10 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_POP_TWO_LOAD_CONST_INLINE_BORROW_r21] = _POP_TWO_LOAD_CONST_INLINE_BORROW, [_POP_CALL_LOAD_CONST_INLINE_BORROW_r21] = _POP_CALL_LOAD_CONST_INLINE_BORROW, [_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW_r31] = _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31] = _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, [_LOAD_CONST_UNDER_INLINE_r02] = _LOAD_CONST_UNDER_INLINE, [_LOAD_CONST_UNDER_INLINE_r12] = _LOAD_CONST_UNDER_INLINE, @@ -3904,10 +3921,9 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_BINARY_OP_ADD_INT_r13] = "_BINARY_OP_ADD_INT_r13", [_BINARY_OP_ADD_INT_r23] = "_BINARY_OP_ADD_INT_r23", [_BINARY_OP_ADD_UNICODE] = "_BINARY_OP_ADD_UNICODE", - [_BINARY_OP_ADD_UNICODE_r01] = "_BINARY_OP_ADD_UNICODE_r01", - [_BINARY_OP_ADD_UNICODE_r11] = "_BINARY_OP_ADD_UNICODE_r11", - [_BINARY_OP_ADD_UNICODE_r21] = "_BINARY_OP_ADD_UNICODE_r21", - [_BINARY_OP_ADD_UNICODE_r32] = "_BINARY_OP_ADD_UNICODE_r32", + [_BINARY_OP_ADD_UNICODE_r03] = "_BINARY_OP_ADD_UNICODE_r03", + [_BINARY_OP_ADD_UNICODE_r13] = "_BINARY_OP_ADD_UNICODE_r13", + [_BINARY_OP_ADD_UNICODE_r23] = "_BINARY_OP_ADD_UNICODE_r23", [_BINARY_OP_EXTEND] = "_BINARY_OP_EXTEND", [_BINARY_OP_EXTEND_r21] = "_BINARY_OP_EXTEND_r21", [_BINARY_OP_INPLACE_ADD_UNICODE] = "_BINARY_OP_INPLACE_ADD_UNICODE", @@ -3930,11 +3946,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_BINARY_OP_SUBSCR_INIT_CALL_r21] = "_BINARY_OP_SUBSCR_INIT_CALL_r21", [_BINARY_OP_SUBSCR_INIT_CALL_r31] = "_BINARY_OP_SUBSCR_INIT_CALL_r31", [_BINARY_OP_SUBSCR_LIST_INT] = "_BINARY_OP_SUBSCR_LIST_INT", - [_BINARY_OP_SUBSCR_LIST_INT_r21] = "_BINARY_OP_SUBSCR_LIST_INT_r21", + [_BINARY_OP_SUBSCR_LIST_INT_r23] = "_BINARY_OP_SUBSCR_LIST_INT_r23", [_BINARY_OP_SUBSCR_LIST_SLICE] = "_BINARY_OP_SUBSCR_LIST_SLICE", [_BINARY_OP_SUBSCR_LIST_SLICE_r21] = "_BINARY_OP_SUBSCR_LIST_SLICE_r21", [_BINARY_OP_SUBSCR_STR_INT] = "_BINARY_OP_SUBSCR_STR_INT", - [_BINARY_OP_SUBSCR_STR_INT_r21] = "_BINARY_OP_SUBSCR_STR_INT_r21", + [_BINARY_OP_SUBSCR_STR_INT_r23] = "_BINARY_OP_SUBSCR_STR_INT_r23", [_BINARY_OP_SUBSCR_TUPLE_INT] = "_BINARY_OP_SUBSCR_TUPLE_INT", [_BINARY_OP_SUBSCR_TUPLE_INT_r21] = "_BINARY_OP_SUBSCR_TUPLE_INT_r21", [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", @@ -4168,6 +4184,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_FORMAT_WITH_SPEC] = "_FORMAT_WITH_SPEC", [_FORMAT_WITH_SPEC_r21] = "_FORMAT_WITH_SPEC_r21", [_FOR_ITER_GEN_FRAME] = "_FOR_ITER_GEN_FRAME", + [_FOR_ITER_GEN_FRAME_r03] = "_FOR_ITER_GEN_FRAME_r03", + [_FOR_ITER_GEN_FRAME_r13] = "_FOR_ITER_GEN_FRAME_r13", [_FOR_ITER_GEN_FRAME_r23] = "_FOR_ITER_GEN_FRAME_r23", [_FOR_ITER_TIER_TWO] = "_FOR_ITER_TIER_TWO", [_FOR_ITER_TIER_TWO_r23] = "_FOR_ITER_TIER_TWO_r23", @@ -4457,7 +4475,9 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS", [_LOAD_ATTR_CLASS_r11] = "_LOAD_ATTR_CLASS_r11", [_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE", - [_LOAD_ATTR_INSTANCE_VALUE_r11] = "_LOAD_ATTR_INSTANCE_VALUE_r11", + [_LOAD_ATTR_INSTANCE_VALUE_r02] = "_LOAD_ATTR_INSTANCE_VALUE_r02", + [_LOAD_ATTR_INSTANCE_VALUE_r12] = "_LOAD_ATTR_INSTANCE_VALUE_r12", + [_LOAD_ATTR_INSTANCE_VALUE_r23] = "_LOAD_ATTR_INSTANCE_VALUE_r23", [_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT", [_LOAD_ATTR_METHOD_LAZY_DICT_r02] = "_LOAD_ATTR_METHOD_LAZY_DICT_r02", [_LOAD_ATTR_METHOD_LAZY_DICT_r12] = "_LOAD_ATTR_METHOD_LAZY_DICT_r12", @@ -4481,7 +4501,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT", [_LOAD_ATTR_SLOT_r11] = "_LOAD_ATTR_SLOT_r11", [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", - [_LOAD_ATTR_WITH_HINT_r11] = "_LOAD_ATTR_WITH_HINT_r11", + [_LOAD_ATTR_WITH_HINT_r12] = "_LOAD_ATTR_WITH_HINT_r12", [_LOAD_BUILD_CLASS] = "_LOAD_BUILD_CLASS", [_LOAD_BUILD_CLASS_r01] = "_LOAD_BUILD_CLASS_r01", [_LOAD_COMMON_CONSTANT] = "_LOAD_COMMON_CONSTANT", @@ -4760,6 +4780,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_SET_IP_r33] = "_SET_IP_r33", [_SET_UPDATE] = "_SET_UPDATE", [_SET_UPDATE_r10] = "_SET_UPDATE_r10", + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW", + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03", + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13", + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23", + [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33", [_SPILL_OR_RELOAD] = "_SPILL_OR_RELOAD", [_SPILL_OR_RELOAD_r01] = "_SPILL_OR_RELOAD_r01", [_SPILL_OR_RELOAD_r02] = "_SPILL_OR_RELOAD_r02", @@ -4782,7 +4807,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", [_STORE_ATTR_SLOT_r21] = "_STORE_ATTR_SLOT_r21", [_STORE_ATTR_WITH_HINT] = "_STORE_ATTR_WITH_HINT", - [_STORE_ATTR_WITH_HINT_r20] = "_STORE_ATTR_WITH_HINT_r20", + [_STORE_ATTR_WITH_HINT_r21] = "_STORE_ATTR_WITH_HINT_r21", [_STORE_DEREF] = "_STORE_DEREF", [_STORE_DEREF_r10] = "_STORE_DEREF_r10", [_STORE_FAST] = "_STORE_FAST", @@ -5477,6 +5502,8 @@ int _PyUop_num_popped(int opcode, int oparg) return 2; case _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW: return 3; + case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW: + return 3; case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW: return 4; case _LOAD_CONST_UNDER_INLINE: diff --git a/Include/patchlevel.h b/Include/patchlevel.h index 610d5bb4a12..90a73c8f2b1 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -27,7 +27,7 @@ #define PY_RELEASE_SERIAL 3 /* Version as a string */ -#define PY_VERSION "3.15.0a3" +#define PY_VERSION "3.15.0a3+" /*--end constants--*/ diff --git a/InternalDocs/profiling_binary_format.md b/InternalDocs/profiling_binary_format.md new file mode 100644 index 00000000000..b3ebdfd22ed --- /dev/null +++ b/InternalDocs/profiling_binary_format.md @@ -0,0 +1,489 @@ +# Profiling Binary Format + +The profiling module includes a binary file format for storing sampling +profiler data. This document describes the format's structure and the +design decisions behind it. + +The implementation is in +[`Modules/_remote_debugging/binary_io_writer.c`](../Modules/_remote_debugging/binary_io_writer.c) +and [`Modules/_remote_debugging/binary_io_reader.c`](../Modules/_remote_debugging/binary_io_reader.c), +with declarations in +[`Modules/_remote_debugging/binary_io.h`](../Modules/_remote_debugging/binary_io.h). + +## Overview + +The sampling profiler can generate enormous amounts of data. A typical +profiling session sampling at 1000 Hz for 60 seconds produces 60,000 samples. +Each sample contains a full call stack, often 20-50 frames deep, and each +frame includes a filename, function name, and line number. In a text-based +format like collapsed stacks, this would mean repeating the same long file +paths and function names thousands of times. + +The binary format addresses this through two key strategies: + +1. **Deduplication**: Strings and frames are stored once in lookup tables, + then referenced by small integer indices. A 100-character file path that + appears in 50,000 samples is stored once, not 50,000 times. + +2. **Compact encoding**: Variable-length integers (varints) encode small + values in fewer bytes. Since most indices are small (under 128), they + typically need only one byte instead of four. + +Together with optional zstd compression, these techniques reduce file sizes +by 10-50x compared to text formats while also enabling faster I/O. + +## File Layout + +The file consists of five sections: + +``` ++------------------+ Offset 0 +| Header | 64 bytes (fixed) ++------------------+ Offset 64 +| | +| Sample Data | Variable size (optionally compressed) +| | ++------------------+ string_table_offset +| String Table | Variable size ++------------------+ frame_table_offset +| Frame Table | Variable size ++------------------+ file_size - 32 +| Footer | 32 bytes (fixed) ++------------------+ file_size +``` + +The layout is designed for streaming writes during profiling. The profiler +cannot know in advance how many unique strings or frames will be encountered, +so these tables must be built incrementally and written at the end. + +The header comes first so readers can quickly validate the file and locate +the metadata tables. The sample data follows immediately, allowing the writer +to stream samples directly to disk (or through a compression stream) without +buffering the entire dataset in memory. + +The string and frame tables are placed after sample data because they grow +as new unique entries are discovered during profiling. By deferring their +output until finalization, the writer avoids the complexity of reserving +space or rewriting portions of the file. + +The footer at the end contains counts needed to allocate arrays before +parsing the tables. Placing it at a fixed offset from the end (rather than +at a variable offset recorded in the header) means readers can locate it +with a single seek to `file_size - 32`, without first reading the header. + +## Header + +``` + Offset Size Type Description ++--------+------+---------+----------------------------------------+ +| 0 | 4 | uint32 | Magic number (0x54414348 = "TACH") | +| 4 | 4 | uint32 | Format version | +| 8 | 4 | bytes | Python version (major, minor, micro, | +| | | | reserved) | +| 12 | 8 | uint64 | Start timestamp (microseconds) | +| 20 | 8 | uint64 | Sample interval (microseconds) | +| 28 | 4 | uint32 | Total sample count | +| 32 | 4 | uint32 | Thread count | +| 36 | 8 | uint64 | String table offset | +| 44 | 8 | uint64 | Frame table offset | +| 52 | 4 | uint32 | Compression type (0=none, 1=zstd) | +| 56 | 8 | bytes | Reserved (zero-filled) | ++--------+------+---------+----------------------------------------+ +``` + +The magic number `0x54414348` ("TACH" for Tachyon) identifies the file format +and also serves as an **endianness marker**. When read on a system with +different byte order than the writer, it appears as `0x48434154`. The reader +uses this to detect cross-endian files and automatically byte-swap all +multi-byte integer fields. + +The Python version field records the major, minor, and micro version numbers +of the Python interpreter that generated the file. This allows analysis tools +to detect version mismatches when replaying data collected on a different +Python version, which may have different internal structures or behaviors. + +The header is written as zeros initially, then overwritten with actual values +during finalization. This requires the output stream to be seekable, which +is acceptable since the format targets regular files rather than pipes or +network streams. + +## Sample Data + +Sample data begins at offset 64 and extends to `string_table_offset`. Samples +use delta compression to minimize redundancy when consecutive samples from the +same thread have identical or similar call stacks. + +### Stack Encoding Types + +Each sample record begins with thread identification, then an encoding byte: + +| Code | Name | Description | +|------|------|-------------| +| 0x00 | REPEAT | RLE: identical stack repeated N times | +| 0x01 | FULL | Complete stack (first sample or no match) | +| 0x02 | SUFFIX | Shares N frames from bottom of previous stack | +| 0x03 | POP_PUSH | Remove M frames from top, add N new frames | + +### Record Formats + +**REPEAT (0x00) - Run-Length Encoded Identical Stacks:** +``` ++-----------------+-----------+----------------------------------------+ +| thread_id | 8 bytes | Thread identifier (uint64, fixed) | +| interpreter_id | 4 bytes | Interpreter ID (uint32, fixed) | +| encoding | 1 byte | 0x00 (REPEAT) | +| count | varint | Number of samples in this RLE group | +| samples | varies | Interleaved: [delta: varint, status: 1]| +| | | repeated count times | ++-----------------+-----------+----------------------------------------+ +``` +The stack is inherited from this thread's previous sample. Each sample in the +group gets its own timestamp delta and status byte, stored as interleaved pairs +(delta1, status1, delta2, status2, ...) rather than separate arrays. + +**FULL (0x01) - Complete Stack:** +``` ++-----------------+-----------+----------------------------------------+ +| thread_id | 8 bytes | Thread identifier (uint64, fixed) | +| interpreter_id | 4 bytes | Interpreter ID (uint32, fixed) | +| encoding | 1 byte | 0x01 (FULL) | +| timestamp_delta | varint | Microseconds since thread's last sample| +| status | 1 byte | Thread state flags | +| stack_depth | varint | Number of frames in call stack | +| frame_indices | varint[] | Array of frame table indices | ++-----------------+-----------+----------------------------------------+ +``` +Used for the first sample from a thread, or when delta encoding would not +provide savings. + +**SUFFIX (0x02) - Shared Suffix Match:** +``` ++-----------------+-----------+----------------------------------------+ +| thread_id | 8 bytes | Thread identifier (uint64, fixed) | +| interpreter_id | 4 bytes | Interpreter ID (uint32, fixed) | +| encoding | 1 byte | 0x02 (SUFFIX) | +| timestamp_delta | varint | Microseconds since thread's last sample| +| status | 1 byte | Thread state flags | +| shared_count | varint | Frames shared from bottom of prev stack| +| new_count | varint | New frames at top of stack | +| new_frames | varint[] | Array of new_count frame indices | ++-----------------+-----------+----------------------------------------+ +``` +Used when a function call added frames to the top of the stack. The shared +frames from the previous stack are kept, and new frames are prepended. + +**POP_PUSH (0x03) - Pop and Push:** +``` ++-----------------+-----------+----------------------------------------+ +| thread_id | 8 bytes | Thread identifier (uint64, fixed) | +| interpreter_id | 4 bytes | Interpreter ID (uint32, fixed) | +| encoding | 1 byte | 0x03 (POP_PUSH) | +| timestamp_delta | varint | Microseconds since thread's last sample| +| status | 1 byte | Thread state flags | +| pop_count | varint | Frames to remove from top of prev stack| +| push_count | varint | New frames to add at top | +| new_frames | varint[] | Array of push_count frame indices | ++-----------------+-----------+----------------------------------------+ +``` +Used when the code path changed: some frames were popped (function returns) +and new frames were pushed (different function calls). + +### Thread and Interpreter Identification + +Thread IDs are 64-bit values that can be large (memory addresses on some +platforms) and vary unpredictably. Using a fixed 8-byte encoding avoids +the overhead of varint encoding for large values and simplifies parsing +since the reader knows exactly where each field begins. + +The interpreter ID identifies which Python sub-interpreter the thread +belongs to, allowing analysis tools to separate activity across interpreters +in processes using multiple sub-interpreters. + +### Status Byte + +The status byte is a bitfield encoding thread state at sample time: + +| Bit | Flag | Meaning | +|-----|-----------------------|--------------------------------------------| +| 0 | THREAD_STATUS_HAS_GIL | Thread holds the GIL (Global Interpreter Lock) | +| 1 | THREAD_STATUS_ON_CPU | Thread is actively running on a CPU core | +| 2 | THREAD_STATUS_UNKNOWN | Thread state could not be determined | +| 3 | THREAD_STATUS_GIL_REQUESTED | Thread is waiting to acquire the GIL | +| 4 | THREAD_STATUS_HAS_EXCEPTION | Thread has a pending exception | + +Multiple flags can be set simultaneously (e.g., a thread can hold the GIL +while also running on CPU). Analysis tools use these to filter samples or +visualize thread states over time. + +### Timestamp Delta Encoding + +Timestamps use delta encoding rather than absolute values. Absolute +timestamps in microseconds require 8 bytes each, but consecutive samples +from the same thread are typically separated by the sampling interval +(e.g., 1000 microseconds), so the delta between them is small and fits +in 1-2 varint bytes. The writer tracks the previous timestamp for each +thread separately. The first sample from a thread encodes its delta from +the profiling start time; subsequent samples encode the delta from that +thread's previous sample. This per-thread tracking is necessary because +samples are interleaved across threads in arrival order, not grouped by +thread. + +For REPEAT (RLE) records, timestamp deltas and status bytes are stored as +interleaved pairs (delta, status, delta, status, ...) - one pair per +repeated sample - allowing efficient batching while preserving the exact +timing and state of each sample. + +### Frame Indexing + +Each frame in a call stack is represented by an index into the frame table +rather than inline data. This provides massive space savings because call +stacks are highly repetitive: the same function appears in many samples +(hot functions), call stacks often share common prefixes (main -> app -> +handler -> ...), and recursive functions create repeated frame sequences. +A frame index is typically 1-2 varint bytes. Inline frame data would be +20-200+ bytes (two strings plus a line number). For a profile with 100,000 +samples averaging 30 frames each, this reduces frame data from potentially +gigabytes to tens of megabytes. + +Frame indices are written innermost-first (the currently executing frame +has index 0 in the array). This ordering works well with delta compression: +function calls typically add frames at the top (index 0), while shared +frames remain at the bottom. + +## String Table + +The string table stores deduplicated UTF-8 strings (filenames and function +names). It begins at `string_table_offset` and contains entries in order of +their assignment during writing: + +``` ++----------------+ +| length: varint | +| data: bytes | ++----------------+ (repeated for each string) +``` + +Strings are stored in the order they were first encountered during writing. +The first unique filename gets index 0, the second gets index 1, and so on. +Length-prefixing (rather than null-termination) allows strings containing +null bytes and enables readers to allocate exact-sized buffers. The varint +length encoding means short strings (under 128 bytes) need only one length +byte. + +## Frame Table + +The frame table stores deduplicated frame entries: + +``` ++----------------------+ +| filename_idx: varint | +| funcname_idx: varint | +| lineno: svarint | ++----------------------+ (repeated for each frame) +``` + +Each unique (filename, funcname, lineno) combination gets one entry. Two +calls to the same function at different line numbers produce different +frame entries; two calls at the same line number share one entry. + +Strings and frames are deduplicated separately because they have different +cardinalities and reference patterns. A codebase might have hundreds of +unique source files but thousands of unique functions. Many functions share +the same filename, so storing the filename index in each frame entry (rather +than the full string) provides an additional layer of deduplication. A frame +entry is just three varints (typically 3-6 bytes) rather than two full +strings plus a line number. + +Line numbers use signed varint (zigzag encoding) rather than unsigned to +handle edge cases. Synthetic frames—generated frames that don't correspond +directly to Python source code, such as C extension boundaries or internal +interpreter frames—use line number 0 or -1 to indicate the absence of a +source location. Zigzag encoding ensures these small negative values encode +efficiently (−1 becomes 1, which is one byte) rather than requiring the +maximum varint length. + +## Footer + +``` + Offset Size Type Description ++--------+------+---------+----------------------------------------+ +| 0 | 4 | uint32 | String count | +| 4 | 4 | uint32 | Frame count | +| 8 | 8 | uint64 | Total file size | +| 16 | 16 | bytes | Checksum (reserved, currently zeros) | ++--------+------+---------+----------------------------------------+ +``` + +The string and frame counts allow readers to pre-allocate arrays of the +correct size before parsing the tables. Without these counts, readers would +need to either scan the tables twice (once to count, once to parse) or use +dynamically-growing arrays. + +The file size field provides a consistency check: if the actual file size +does not match, the file may be truncated or corrupted. + +The checksum field is reserved for future use. A checksum would allow +detection of corruption but adds complexity and computation cost. The +current implementation leaves this as zeros. + +## Variable-Length Integer Encoding + +The format uses LEB128 (Little Endian Base 128) for unsigned integers and +zigzag + LEB128 for signed integers. These encodings are widely used +(Protocol Buffers, DWARF debug info, WebAssembly) and well-understood. + +### Unsigned Varint (LEB128) + +Each byte stores 7 bits of data. The high bit indicates whether more bytes +follow: + +``` +Value Encoded bytes +0-127 [0xxxxxxx] (1 byte) +128-16383 [1xxxxxxx] [0xxxxxxx] (2 bytes) +16384+ [1xxxxxxx] [1xxxxxxx] ... (3+ bytes) +``` + +Most indices in profiling data are small. A profile with 1000 unique frames +needs at most 2 bytes per frame index. The common case (indices under 128) +needs only 1 byte. + +### Signed Varint (Zigzag) + +Standard LEB128 encodes −1 as a very large unsigned value, requiring many +bytes. Zigzag encoding interleaves positive and negative values: + +``` + 0 -> 0 -1 -> 1 1 -> 2 -2 -> 3 2 -> 4 +``` + +This ensures small-magnitude values (whether positive or negative) encode +in few bytes. + +## Compression + +When compression is enabled, the sample data region contains a zstd stream. +The string table, frame table, and footer remain uncompressed so readers can +access metadata without decompressing the entire file. A tool that only needs +to report "this file contains 50,000 samples of 3 threads" can read the header +and footer without touching the compressed sample data. This also simplifies +the format: the header's offset fields point directly to the tables rather +than to positions within a decompressed stream. + +Zstd provides an excellent balance of compression ratio and speed. Profiling +data compresses very well (often 5-10x) due to repetitive patterns: the same +small set of frame indices appears repeatedly, and delta-encoded timestamps +cluster around the sampling interval. Zstd's streaming API allows compression +without buffering the entire dataset. The writer feeds sample data through +the compressor incrementally, flushing compressed chunks to disk as they +become available. + +Level 5 compression is used as a default. Lower levels (1-3) are faster but +compress less; higher levels (6+) compress more but slow down writing. Level +5 provides good compression with minimal impact on profiling overhead. + +## Reading and Writing + +### Writing + +1. Open the output file and write 64 zero bytes as a placeholder header +2. Initialize empty string and frame dictionaries for deduplication +3. For each sample: + - Intern any new strings, assigning sequential indices + - Intern any new frames, assigning sequential indices + - Encode the sample record and write to the buffer + - Flush the buffer through compression (if enabled) when full +4. Flush remaining buffered data and finalize compression +5. Write the string table (length-prefixed strings in index order) +6. Write the frame table (varint-encoded entries in index order) +7. Write the footer with final counts +8. Seek to offset 0 and write the header with actual values + +The writer maintains two dictionaries: one mapping strings to indices, one +mapping (filename_idx, funcname_idx, lineno) tuples to frame indices. These +enable O(1) lookup during interning. + +### Reading + +1. Read the header magic number to detect endianness (set `needs_swap` flag + if the magic appears byte-swapped) +2. Validate version and read remaining header fields (byte-swapping if needed) +3. Seek to end − 32 and read the footer (byte-swapping counts if needed) +4. Allocate string array of `string_count` elements +5. Parse the string table, populating the array +6. Allocate frame array of `frame_count * 3` uint32 elements +7. Parse the frame table, populating the array +8. If compressed, decompress the sample data region +9. Iterate through samples, resolving indices to strings/frames + (byte-swapping thread_id and interpreter_id if needed) + +The reader builds lookup arrays rather than dictionaries since it only needs +index-to-value mapping, not value-to-index. + +## Platform Considerations + +### Byte Ordering and Cross-Platform Portability + +The binary format uses **native byte order** for all multi-byte integer +fields when writing. However, the reader supports **cross-endian reading**: +files written on a little-endian system (x86, ARM) can be read on a +big-endian system (s390x, PowerPC), and vice versa. + +The magic number doubles as an endianness marker. When read on a system with +different byte order, it appears byte-swapped (`0x48434154` instead of +`0x54414348`). The reader detects this and automatically byte-swaps all +fixed-width integer fields during parsing. + +Writers must use `memcpy()` from properly-sized integer types when writing +fixed-width integer fields. When the source variable's type differs from the +field width (e.g., `size_t` written as 4 bytes), explicit casting to the +correct type (e.g., `uint32_t`) is required before `memcpy()`. On big-endian +systems, copying from an oversized type would copy the wrong bytes—high-order +zeros instead of the actual value. + +The reader tracks whether byte-swapping is needed via a `needs_swap` flag set +during header parsing. All fixed-width fields in the header, footer, and +sample data are conditionally byte-swapped using Python's internal byte-swap +functions (`_Py_bswap32`, `_Py_bswap64` from `pycore_bitutils.h`). + +Variable-length integers (varints) are byte-order independent since they +encode values one byte at a time using the LEB128 scheme, so they require +no special handling for cross-endian reading. + +### Memory-Mapped I/O + +On Unix systems (Linux, macOS), the reader uses `mmap()` to map the file +into the process address space. The kernel handles paging data in and out +as needed, no explicit read() calls or buffer management are required, +multiple readers can share the same physical pages, and sequential access +patterns benefit from kernel read-ahead. + +The implementation uses `madvise()` to hint the access pattern to the kernel: +`MADV_SEQUENTIAL` indicates the file will be read linearly, enabling +aggressive read-ahead. `MADV_WILLNEED` requests pre-faulting of pages. +On Linux, `MAP_POPULATE` pre-faults all pages at mmap time rather than on +first access, moving page fault overhead from the parsing loop to the +initial mapping for more predictable performance. For large files (over +32 MB), `MADV_HUGEPAGE` requests transparent huge pages (2 MB instead of +4 KB) to reduce TLB pressure when accessing large amounts of data. + +On Windows, the implementation falls back to standard file I/O with full +file buffering. Profiling data files are typically small enough (tens to +hundreds of megabytes) that this is acceptable. + +The writer uses a 512 KB buffer to batch small writes. Each sample record +is typically tens of bytes; writing these individually would incur excessive +syscall overhead. The buffer accumulates data until full, then flushes in +one write() call (or feeds through the compression stream). + +## Future Considerations + +The format reserves space for future extensions. The 12 reserved bytes in +the header could hold additional metadata. The 16-byte checksum field in +the footer is currently unused. The version field allows incompatible +changes with graceful rejection. New compression types could be added +(compression_type > 1). + +Any changes that alter the meaning of existing fields or the parsing logic +should increment the version number to prevent older readers from +misinterpreting new files. diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index d078ebfa4ce..afbb70bbcab 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -86,14 +86,15 @@ class REPLThread(threading.Thread): global return_code try: - banner = ( - f'asyncio REPL {sys.version} on {sys.platform}\n' - f'Use "await" directly instead of "asyncio.run()".\n' - f'Type "help", "copyright", "credits" or "license" ' - f'for more information.\n' - ) + if not sys.flags.quiet: + banner = ( + f'asyncio REPL {sys.version} on {sys.platform}\n' + f'Use "await" directly instead of "asyncio.run()".\n' + f'Type "help", "copyright", "credits" or "license" ' + f'for more information.\n' + ) - console.write(banner) + console.write(banner) if startup_path := os.getenv("PYTHONSTARTUP"): sys.audit("cpython.run_startup", startup_path) @@ -240,4 +241,5 @@ if __name__ == '__main__': break console.write('exiting asyncio REPL...\n') + loop.close() sys.exit(return_code) diff --git a/Lib/configparser.py b/Lib/configparser.py index 18af1eadaad..d435a5c2fe0 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -794,7 +794,8 @@ class RawConfigParser(MutableMapping): """ elements_added = set() for section, keys in dictionary.items(): - section = str(section) + if section is not UNNAMED_SECTION: + section = str(section) try: self.add_section(section) except (DuplicateSectionError, ValueError): diff --git a/Lib/locale.py b/Lib/locale.py index 37cafb4a601..0f1b429ea41 100644 --- a/Lib/locale.py +++ b/Lib/locale.py @@ -214,7 +214,7 @@ def format_string(f, val, grouping=False, monetary=False): Grouping is applied if the third parameter is true. Conversion uses monetary thousands separator and grouping strings if - forth parameter monetary is true.""" + fourth parameter monetary is true.""" global _percent_re if _percent_re is None: import re diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 4a44642765c..65923e9c5de 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -2181,11 +2181,7 @@ def _unlock_file(f): def _create_carefully(path): """Create a file if it doesn't exist and open for reading and writing.""" - fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o666) - try: - return open(path, 'rb+') - finally: - os.close(fd) + return open(path, 'xb+') def _create_temporary(path): """Create a temp file based on path and open for reading and writing.""" diff --git a/Lib/pdb.py b/Lib/pdb.py index c1a5db080dc..eee0273fdc4 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -391,17 +391,22 @@ class Pdb(bdb.Bdb, cmd.Cmd): # Read ~/.pdbrc and ./.pdbrc self.rcLines = [] if readrc: + home_rcfile = os.path.expanduser("~/.pdbrc") + local_rcfile = os.path.abspath(".pdbrc") + try: - with open(os.path.expanduser('~/.pdbrc'), encoding='utf-8') as rcFile: - self.rcLines.extend(rcFile) - except OSError: - pass - try: - with open(".pdbrc", encoding='utf-8') as rcFile: - self.rcLines.extend(rcFile) + with open(home_rcfile, encoding='utf-8') as rcfile: + self.rcLines.extend(rcfile) except OSError: pass + if local_rcfile != home_rcfile: + try: + with open(local_rcfile, encoding='utf-8') as rcfile: + self.rcLines.extend(rcfile) + except OSError: + pass + self.commands = {} # associates a command list to breakpoint numbers self.commands_defining = False # True while in the process of defining # a command list @@ -1315,7 +1320,14 @@ class Pdb(bdb.Bdb, cmd.Cmd): reached. """ if not arg: - bnum = len(bdb.Breakpoint.bpbynumber) - 1 + for bp in reversed(bdb.Breakpoint.bpbynumber): + if bp is None: + continue + bnum = bp.number + break + else: + self.error('cannot set commands: no existing breakpoint') + return else: try: bnum = int(arg) diff --git a/Lib/profiling/sampling/__main__.py b/Lib/profiling/sampling/__main__.py index 47bd3a0113e..a45b645eae0 100644 --- a/Lib/profiling/sampling/__main__.py +++ b/Lib/profiling/sampling/__main__.py @@ -46,6 +46,7 @@ system restrictions or missing privileges. """ from .cli import main +from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError, SamplingScriptNotFoundError def handle_permission_error(): """Handle PermissionError by displaying appropriate error message.""" @@ -64,3 +65,9 @@ if __name__ == '__main__': main() except PermissionError: handle_permission_error() + except SamplingUnknownProcessError as err: + print(f"Tachyon cannot find the process: {err}", file=sys.stderr) + sys.exit(1) + except (SamplingModuleNotFoundError, SamplingScriptNotFoundError) as err: + print(f"Tachyon cannot find the target: {err}", file=sys.stderr) + sys.exit(1) diff --git a/Lib/profiling/sampling/_format_utils.py b/Lib/profiling/sampling/_format_utils.py new file mode 100644 index 00000000000..237a4f4186b --- /dev/null +++ b/Lib/profiling/sampling/_format_utils.py @@ -0,0 +1,5 @@ +import locale + + +def fmt(value: int | float, decimals: int = 1) -> str: + return locale.format_string(f'%.{decimals}f', value, grouping=True) diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap.css b/Lib/profiling/sampling/_heatmap_assets/heatmap.css index 4fba9d866ac..9999cd6760f 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap.css +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap.css @@ -5,6 +5,18 @@ This file extends the shared foundation with heatmap-specific styles. ========================================================================== */ +/* Heatmap heat colors - using base.css colors with 60% opacity */ +[data-theme="dark"] { + --heat-1: rgba(90, 123, 167, 0.60); + --heat-2: rgba(106, 148, 168, 0.60); + --heat-3: rgba(122, 172, 172, 0.60); + --heat-4: rgba(142, 196, 152, 0.60); + --heat-5: rgba(168, 216, 136, 0.60); + --heat-6: rgba(200, 222, 122, 0.60); + --heat-7: rgba(244, 212, 93, 0.60); + --heat-8: rgba(255, 122, 69, 0.60); +} + /* -------------------------------------------------------------------------- Layout Overrides (Heatmap-specific) -------------------------------------------------------------------------- */ @@ -1129,6 +1141,10 @@ .line-samples-cumulative { padding: 0 4px; } + + .bytecode-panel { + margin: 8px 10px 8px 160px; + } } .bytecode-toggle { @@ -1160,13 +1176,77 @@ } .bytecode-panel { - margin-left: 90px; - padding: 8px 15px; - background: var(--bg-secondary); - border-left: 3px solid var(--accent); + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: var(--shadow-md); font-family: var(--font-mono); font-size: 12px; - margin-bottom: 4px; + color: var(--text-primary); + line-height: 1.5; + word-wrap: break-word; + overflow-wrap: break-word; + padding: 0; + margin: 8px 10px 8px 250px; + position: relative; + z-index: 1; + overflow-y: auto; + max-height: 500px; + flex: 1; + transition: padding 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.bytecode-panel.expanded { + padding: 14px; +} + +.bytecode-wrapper { + position: relative; + display: flex; + overflow: visible; + max-height: 0; + opacity: 0; + transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease-in-out; +} + +.bytecode-wrapper.expanded { + max-height: 600px; + opacity: 1; + transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease-in-out; +} + +/* Column backdrop matching table header columns (line/self/total) */ +.bytecode-columns { + display: none; + position: absolute; + left: 0; + overflow: hidden; + pointer-events: none; + z-index: 0; +} + +.bytecode-wrapper.expanded .bytecode-columns { + display: flex; + top: 0; + bottom: 0; +} + +.bytecode-panel::-webkit-scrollbar { + width: 8px; +} + +.bytecode-panel::-webkit-scrollbar-track { + background: var(--bg-secondary); + border-radius: 4px; +} + +.bytecode-panel::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; +} + +.bytecode-panel::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); } /* Specialization summary bar */ diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap.js b/Lib/profiling/sampling/_heatmap_assets/heatmap.js index 8ac4ef43e53..53928b7b20f 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap.js +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap.js @@ -15,37 +15,13 @@ let coldCodeHidden = false; // ============================================================================ function toggleTheme() { - const html = document.documentElement; - const current = html.getAttribute('data-theme') || 'light'; - const next = current === 'light' ? 'dark' : 'light'; - html.setAttribute('data-theme', next); - localStorage.setItem('heatmap-theme', next); - - // Update theme button icon - const btn = document.getElementById('theme-btn'); - if (btn) { - btn.querySelector('.icon-moon').style.display = next === 'dark' ? 'none' : ''; - btn.querySelector('.icon-sun').style.display = next === 'dark' ? '' : 'none'; - } + toggleAndSaveTheme(); applyLineColors(); // Rebuild scroll marker with new theme colors buildScrollMarker(); } -function restoreUIState() { - // Restore theme - const savedTheme = localStorage.getItem('heatmap-theme'); - if (savedTheme) { - document.documentElement.setAttribute('data-theme', savedTheme); - const btn = document.getElementById('theme-btn'); - if (btn) { - btn.querySelector('.icon-moon').style.display = savedTheme === 'dark' ? 'none' : ''; - btn.querySelector('.icon-sun').style.display = savedTheme === 'dark' ? '' : 'none'; - } - } -} - // ============================================================================ // Utility Functions // ============================================================================ @@ -542,20 +518,23 @@ function toggleBytecode(button) { const lineId = lineDiv.id; const lineNum = lineId.replace('line-', ''); const panel = document.getElementById(`bytecode-${lineNum}`); + const wrapper = document.getElementById(`bytecode-wrapper-${lineNum}`); - if (!panel) return; + if (!panel || !wrapper) return; - const isExpanded = panel.style.display !== 'none'; + const isExpanded = panel.classList.contains('expanded'); if (isExpanded) { - panel.style.display = 'none'; + panel.classList.remove('expanded'); + wrapper.classList.remove('expanded'); button.classList.remove('expanded'); button.innerHTML = '▶'; // Right arrow } else { if (!panel.dataset.populated) { populateBytecodePanel(panel, button); } - panel.style.display = 'block'; + panel.classList.add('expanded'); + wrapper.classList.add('expanded'); button.classList.add('expanded'); button.innerHTML = '▼'; // Down arrow } @@ -598,10 +577,12 @@ function populateBytecodePanel(panel, button) { else if (specPct >= 33) specClass = 'medium'; // Build specialization summary + const instruction_word = instructions.length === 1 ? 'instruction' : 'instructions'; + const sample_word = totalSamples === 1 ? 'sample' : 'samples'; let html = `
${specPct}% specialized - (${specializedCount}/${instructions.length} instructions, ${specializedSamples.toLocaleString()}/${totalSamples.toLocaleString()} samples) + (${specializedCount}/${instructions.length} ${instruction_word}, ${specializedSamples.toLocaleString()}/${totalSamples.toLocaleString()} ${sample_word})
`; html += '
' + diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap_index.js b/Lib/profiling/sampling/_heatmap_assets/heatmap_index.js index 8eb6af0db53..db4b5485056 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap_index.js +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap_index.js @@ -19,35 +19,10 @@ function applyHeatmapBarColors() { // ============================================================================ function toggleTheme() { - const html = document.documentElement; - const current = html.getAttribute('data-theme') || 'light'; - const next = current === 'light' ? 'dark' : 'light'; - html.setAttribute('data-theme', next); - localStorage.setItem('heatmap-theme', next); - - // Update theme button icon - const btn = document.getElementById('theme-btn'); - if (btn) { - btn.querySelector('.icon-moon').style.display = next === 'dark' ? 'none' : ''; - btn.querySelector('.icon-sun').style.display = next === 'dark' ? '' : 'none'; - } - + toggleAndSaveTheme(); applyHeatmapBarColors(); } -function restoreUIState() { - // Restore theme - const savedTheme = localStorage.getItem('heatmap-theme'); - if (savedTheme) { - document.documentElement.setAttribute('data-theme', savedTheme); - const btn = document.getElementById('theme-btn'); - if (btn) { - btn.querySelector('.icon-moon').style.display = savedTheme === 'dark' ? 'none' : ''; - btn.querySelector('.icon-sun').style.display = savedTheme === 'dark' ? '' : 'none'; - } - } -} - // ============================================================================ // Type Section Toggle (stdlib, project, etc) // ============================================================================ diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap_index_template.html b/Lib/profiling/sampling/_heatmap_assets/heatmap_index_template.html index 3620f8efb80..8d04149abe3 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap_index_template.html +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap_index_template.html @@ -1,5 +1,5 @@ - + diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap_pyfile_template.html b/Lib/profiling/sampling/_heatmap_assets/heatmap_pyfile_template.html index 91b629b2628..2a9c07647e6 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap_pyfile_template.html +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap_pyfile_template.html @@ -1,5 +1,5 @@ - + diff --git a/Lib/profiling/sampling/_heatmap_assets/heatmap_shared.js b/Lib/profiling/sampling/_heatmap_assets/heatmap_shared.js index 7fcd720d45d..84b13ca0a96 100644 --- a/Lib/profiling/sampling/_heatmap_assets/heatmap_shared.js +++ b/Lib/profiling/sampling/_heatmap_assets/heatmap_shared.js @@ -39,6 +39,42 @@ function intensityToColor(intensity) { return rootStyle.getPropertyValue(`--heat-${level}`).trim(); } +// ============================================================================ +// Theme Support +// ============================================================================ + +// Get the preferred theme from localStorage or browser preference +function getPreferredTheme() { + const saved = localStorage.getItem('heatmap-theme'); + if (saved) return saved; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} + +// Apply theme and update UI. Returns the applied theme. +function applyTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + const btn = document.getElementById('theme-btn'); + if (btn) { + btn.querySelector('.icon-moon').style.display = theme === 'dark' ? 'none' : ''; + btn.querySelector('.icon-sun').style.display = theme === 'dark' ? '' : 'none'; + } + return theme; +} + +// Toggle theme and save preference. Returns the new theme. +function toggleAndSaveTheme() { + const current = document.documentElement.getAttribute('data-theme') || 'light'; + const next = current === 'light' ? 'dark' : 'light'; + applyTheme(next); + localStorage.setItem('heatmap-theme', next); + return next; +} + +// Restore theme from localStorage, or use browser preference +function restoreUIState() { + applyTheme(getPreferredTheme()); +} + // ============================================================================ // Favicon (Reuse logo image as favicon) // ============================================================================ diff --git a/Lib/profiling/sampling/_shared_assets/base.css b/Lib/profiling/sampling/_shared_assets/base.css index 39bdd52e943..cb59a0f77c5 100644 --- a/Lib/profiling/sampling/_shared_assets/base.css +++ b/Lib/profiling/sampling/_shared_assets/base.css @@ -124,15 +124,15 @@ --header-gradient: linear-gradient(135deg, #21262d 0%, #30363d 100%); - /* Dark mode heat palette - muted colors that provide sufficient contrast with light text */ - --heat-1: rgba(74, 123, 167, 0.35); - --heat-2: rgba(90, 159, 168, 0.38); - --heat-3: rgba(106, 181, 181, 0.40); - --heat-4: rgba(126, 196, 136, 0.42); - --heat-5: rgba(160, 216, 120, 0.45); - --heat-6: rgba(196, 222, 106, 0.48); - --heat-7: rgba(244, 212, 77, 0.50); - --heat-8: rgba(255, 107, 53, 0.55); + /* Dark mode heat palette - cool to warm gradient for visualization */ + --heat-1: rgba(90, 123, 167, 1); + --heat-2: rgba(106, 148, 168, 1); + --heat-3: rgba(122, 172, 172, 1); + --heat-4: rgba(142, 196, 152, 1); + --heat-5: rgba(168, 216, 136, 1); + --heat-6: rgba(200, 222, 122, 1); + --heat-7: rgba(244, 212, 93, 1); + --heat-8: rgba(255, 122, 69, 1); /* Code view specific - dark mode */ --code-bg: #0d1117; diff --git a/Lib/profiling/sampling/binary_collector.py b/Lib/profiling/sampling/binary_collector.py new file mode 100644 index 00000000000..64afe632fae --- /dev/null +++ b/Lib/profiling/sampling/binary_collector.py @@ -0,0 +1,120 @@ +"""Thin Python wrapper around C binary writer for profiling data.""" + +import time + +import _remote_debugging + +from .collector import Collector + +# Compression type constants (must match binary_io.h) +COMPRESSION_NONE = 0 +COMPRESSION_ZSTD = 1 + + +def _resolve_compression(compression): + """Resolve compression type from string or int. + + Args: + compression: 'auto', 'zstd', 'none', or int (0/1) + + Returns: + int: Compression type constant + """ + if isinstance(compression, int): + return compression + + compression = compression.lower() + if compression == 'none': + return COMPRESSION_NONE + elif compression == 'zstd': + return COMPRESSION_ZSTD + elif compression == 'auto': + # Auto: use zstd if available, otherwise none + if _remote_debugging.zstd_available(): + return COMPRESSION_ZSTD + return COMPRESSION_NONE + else: + raise ValueError(f"Unknown compression type: {compression}") + + +class BinaryCollector(Collector): + """High-performance binary collector using C implementation. + + This collector writes profiling data directly to a binary file format + with optional zstd compression. All I/O is performed in C for maximum + throughput. + + The binary format uses string/frame deduplication and varint encoding + for efficient storage. + """ + + def __init__(self, filename, sample_interval_usec, *, skip_idle=False, + compression='auto'): + """Create a new binary collector. + + Args: + filename: Path to output binary file + sample_interval_usec: Sampling interval in microseconds + skip_idle: If True, skip idle threads (not used in binary format) + compression: 'auto', 'zstd', 'none', or int (0=none, 1=zstd) + """ + self.filename = filename + self.sample_interval_usec = sample_interval_usec + self.skip_idle = skip_idle + + compression_type = _resolve_compression(compression) + start_time_us = int(time.monotonic() * 1_000_000) + self._writer = _remote_debugging.BinaryWriter( + filename, sample_interval_usec, start_time_us, compression=compression_type + ) + + def collect(self, stack_frames, timestamp_us=None): + """Collect profiling data from stack frames. + + This passes stack_frames directly to the C writer which handles + all encoding and buffering. + + Args: + stack_frames: List of InterpreterInfo objects from _remote_debugging + timestamp_us: Optional timestamp in microseconds. If not provided, + uses time.monotonic() to generate one. + """ + if timestamp_us is None: + timestamp_us = int(time.monotonic() * 1_000_000) + self._writer.write_sample(stack_frames, timestamp_us) + + def collect_failed_sample(self): + """Record a failed sample attempt (no-op for binary format).""" + pass + + def export(self, filename=None): + """Finalize and close the binary file. + + Args: + filename: Ignored (binary files are written incrementally) + """ + self._writer.finalize() + + @property + def total_samples(self): + return self._writer.total_samples + + def get_stats(self): + """Get encoding statistics. + + Returns: + Dict with encoding statistics including repeat/full/suffix/pop-push + record counts, frames written/saved, and compression ratio. + """ + return self._writer.get_stats() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Context manager exit - finalize unless there was an error.""" + if exc_type is None: + self._writer.finalize() + else: + self._writer.close() + return False diff --git a/Lib/profiling/sampling/binary_reader.py b/Lib/profiling/sampling/binary_reader.py new file mode 100644 index 00000000000..50c96668cc5 --- /dev/null +++ b/Lib/profiling/sampling/binary_reader.py @@ -0,0 +1,128 @@ +"""Thin Python wrapper around C binary reader for profiling data.""" + + +class BinaryReader: + """High-performance binary reader using C implementation. + + This reader uses memory-mapped I/O (on Unix) for fast replay of + profiling data from binary files. + + Use as a context manager: + with BinaryReader('profile.bin') as reader: + info = reader.get_info() + reader.replay_samples(collector, progress_callback) + """ + + def __init__(self, filename): + """Create a new binary reader. + + Args: + filename: Path to input binary file + """ + self.filename = filename + self._reader = None + + def __enter__(self): + import _remote_debugging + self._reader = _remote_debugging.BinaryReader(self.filename) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self._reader is not None: + self._reader.close() + self._reader = None + return False + + def get_info(self): + """Get metadata about the binary file. + + Returns: + dict: File metadata including: + - sample_count: Number of samples in the file + - sample_interval_us: Sampling interval in microseconds + - start_time_us: Start timestamp in microseconds + - string_count: Number of unique strings + - frame_count: Number of unique frames + - compression: Compression type used + """ + if self._reader is None: + raise RuntimeError("Reader not open. Use as context manager.") + return self._reader.get_info() + + def replay_samples(self, collector, progress_callback=None): + """Replay samples from binary file through a collector. + + This allows converting binary profiling data to other formats + (e.g., flamegraph, pstats) by replaying through the appropriate + collector. + + Args: + collector: A Collector instance with a collect() method + progress_callback: Optional callable(current, total) for progress + + Returns: + int: Number of samples replayed + """ + if self._reader is None: + raise RuntimeError("Reader not open. Use as context manager.") + return self._reader.replay(collector, progress_callback) + + @property + def sample_count(self): + if self._reader is None: + raise RuntimeError("Reader not open. Use as context manager.") + return self._reader.get_info()['sample_count'] + + def get_stats(self): + """Get reconstruction statistics from replay. + + Returns: + dict: Statistics about record types decoded and samples + reconstructed during replay. + """ + if self._reader is None: + raise RuntimeError("Reader not open. Use as context manager.") + return self._reader.get_stats() + + +def convert_binary_to_format(input_file, output_file, output_format, + sample_interval_usec=None, progress_callback=None): + """Convert a binary profiling file to another format. + + Args: + input_file: Path to input binary file + output_file: Path to output file + output_format: Target format ('flamegraph', 'collapsed', 'pstats', etc.) + sample_interval_usec: Override sample interval (uses file's if None) + progress_callback: Optional callable(current, total) for progress + + Returns: + int: Number of samples converted + """ + from .gecko_collector import GeckoCollector + from .stack_collector import FlamegraphCollector, CollapsedStackCollector + from .pstats_collector import PStatsCollector + + with BinaryReader(input_file) as reader: + info = reader.get_info() + interval = sample_interval_usec or info['sample_interval_us'] + + # Create appropriate collector based on format + if output_format == 'flamegraph': + collector = FlamegraphCollector(interval) + elif output_format == 'collapsed': + collector = CollapsedStackCollector(interval) + elif output_format == 'pstats': + collector = PStatsCollector(interval) + elif output_format == 'gecko': + collector = GeckoCollector(interval) + else: + raise ValueError(f"Unknown output format: {output_format}") + + # Replay samples through collector + count = reader.replay_samples(collector, progress_callback) + + # Export to target format + collector.export(output_file) + + return count diff --git a/Lib/profiling/sampling/cli.py b/Lib/profiling/sampling/cli.py index e1ff3758c0d..0403c75c80f 100644 --- a/Lib/profiling/sampling/cli.py +++ b/Lib/profiling/sampling/cli.py @@ -2,6 +2,7 @@ import argparse import importlib.util +import locale import os import selectors import socket @@ -10,11 +11,14 @@ import sys import time from contextlib import nullcontext -from .sample import sample, sample_live +from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError, SamplingScriptNotFoundError +from .sample import sample, sample_live, _is_process_running from .pstats_collector import PstatsCollector from .stack_collector import CollapsedStackCollector, FlamegraphCollector from .heatmap_collector import HeatmapCollector from .gecko_collector import GeckoCollector +from .binary_collector import BinaryCollector +from .binary_reader import BinaryReader from .constants import ( PROFILING_MODE_ALL, PROFILING_MODE_WALL, @@ -74,6 +78,7 @@ FORMAT_EXTENSIONS = { "flamegraph": "html", "gecko": "json", "heatmap": "html", + "binary": "bin", } COLLECTOR_MAP = { @@ -82,6 +87,7 @@ COLLECTOR_MAP = { "flamegraph": FlamegraphCollector, "gecko": GeckoCollector, "heatmap": HeatmapCollector, + "binary": BinaryCollector, } def _setup_child_monitor(args, parent_pid): @@ -179,7 +185,7 @@ def _parse_mode(mode_string): def _check_process_died(process): """Check if process died and raise an error with stderr if available.""" if process.poll() is None: - return # Process still running + return # Process died - try to get stderr for error message stderr_msg = "" @@ -364,7 +370,7 @@ def _add_mode_options(parser): ) -def _add_format_options(parser): +def _add_format_options(parser, include_compression=True, include_binary=True): """Add output format options to a parser.""" output_group = parser.add_argument_group("Output options") format_group = output_group.add_mutually_exclusive_group() @@ -403,8 +409,24 @@ def _add_format_options(parser): dest="format", help="Generate interactive HTML heatmap visualization with line-level sample counts", ) + if include_binary: + format_group.add_argument( + "--binary", + action="store_const", + const="binary", + dest="format", + help="Generate high-performance binary format (use 'replay' command to convert)", + ) parser.set_defaults(format="pstats") + if include_compression: + output_group.add_argument( + "--compression", + choices=["auto", "zstd", "none"], + default="auto", + help="Compression for binary format: auto (use zstd if available), zstd, none", + ) + output_group.add_argument( "-o", "--output", @@ -459,15 +481,18 @@ def _sort_to_mode(sort_choice): return sort_map.get(sort_choice, SORT_MODE_NSAMPLES) -def _create_collector(format_type, interval, skip_idle, opcodes=False): +def _create_collector(format_type, interval, skip_idle, opcodes=False, + output_file=None, compression='auto'): """Create the appropriate collector based on format type. Args: - format_type: The output format ('pstats', 'collapsed', 'flamegraph', 'gecko', 'heatmap') + format_type: The output format ('pstats', 'collapsed', 'flamegraph', 'gecko', 'heatmap', 'binary') interval: Sampling interval in microseconds skip_idle: Whether to skip idle samples opcodes: Whether to collect opcode information (only used by gecko format for creating interval markers in Firefox Profiler) + output_file: Output file path (required for binary format) + compression: Compression type for binary format ('auto', 'zstd', 'none') Returns: A collector instance of the appropriate type @@ -476,6 +501,13 @@ def _create_collector(format_type, interval, skip_idle, opcodes=False): if collector_class is None: raise ValueError(f"Unknown format: {format_type}") + # Binary format requires output file and compression + if format_type == "binary": + if output_file is None: + raise ValueError("Binary format requires an output file") + return collector_class(output_file, interval, skip_idle=skip_idle, + compression=compression) + # Gecko format never skips idle (it needs both GIL and CPU data) # and is the only format that uses opcodes for interval markers if format_type == "gecko": @@ -511,7 +543,12 @@ def _handle_output(collector, args, pid, mode): pid: Process ID (for generating filenames) mode: Profiling mode used """ - if args.format == "pstats": + if args.format == "binary": + # Binary format already wrote to file incrementally, just finalize + collector.export(None) + filename = collector.filename + print(f"Binary profile written to {filename} ({collector.total_samples} samples)") + elif args.format == "pstats": if args.outfile: # If outfile is a directory, generate filename inside it if os.path.isdir(args.outfile): @@ -544,6 +581,10 @@ def _validate_args(args, parser): args: Parsed command-line arguments parser: ArgumentParser instance for error reporting """ + # Replay command has no special validation needed + if getattr(args, 'command', None) == "replay": + return + # Check if live mode is available if hasattr(args, 'live') and args.live and LiveStatsCollector is None: parser.error( @@ -556,7 +597,7 @@ def _validate_args(args, parser): 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: + if getattr(args, 'async_aware', False): issues = [] if args.native: issues.append("--native") @@ -573,7 +614,7 @@ def _validate_args(args, parser): ) # --async-mode requires --async-aware - if hasattr(args, 'async_mode') and args.async_mode != "running" and not args.async_aware: + if hasattr(args, 'async_mode') and args.async_mode != "running" and not getattr(args, 'async_aware', False): parser.error("--async-mode requires --async-aware to be enabled.") # Live mode is incompatible with format options @@ -601,7 +642,7 @@ def _validate_args(args, parser): return # Validate gecko mode doesn't use non-wall mode - if args.format == "gecko" and args.mode != "wall": + if args.format == "gecko" and getattr(args, 'mode', 'wall') != "wall": parser.error( "--mode option is incompatible with --gecko. " "Gecko format automatically includes both GIL-holding and CPU status analysis." @@ -609,7 +650,7 @@ def _validate_args(args, parser): # Validate --opcodes is only used with compatible formats opcodes_compatible_formats = ("live", "gecko", "flamegraph", "heatmap") - if args.opcodes and args.format not in opcodes_compatible_formats: + if getattr(args, 'opcodes', False) and args.format not in opcodes_compatible_formats: parser.error( f"--opcodes is only compatible with {', '.join('--' + f for f in opcodes_compatible_formats)}." ) @@ -633,6 +674,16 @@ def _validate_args(args, parser): def main(): """Main entry point for the CLI.""" + # Set locale for number formatting, restore on exit + old_locale = locale.setlocale(locale.LC_ALL, None) + locale.setlocale(locale.LC_ALL, "") + try: + _main() + finally: + locale.setlocale(locale.LC_ALL, old_locale) + + +def _main(): # Create the main parser parser = argparse.ArgumentParser( description=_HELP_DESCRIPTION, @@ -721,6 +772,30 @@ Examples: _add_format_options(attach_parser) _add_pstats_options(attach_parser) + # === REPLAY COMMAND === + replay_parser = subparsers.add_parser( + "replay", + help="Replay a binary profile and convert to another format", + formatter_class=CustomFormatter, + description="""Replay a binary profile file and convert to another format + +Examples: + # Convert binary to flamegraph + `python -m profiling.sampling replay --flamegraph -o output.html profile.bin` + + # Convert binary to pstats and print to stdout + `python -m profiling.sampling replay profile.bin` + + # Convert binary to gecko format + `python -m profiling.sampling replay --gecko -o profile.json profile.bin`""", + ) + replay_parser.add_argument( + "input_file", + help="Binary profile file to replay", + ) + _add_format_options(replay_parser, include_compression=False, include_binary=False) + _add_pstats_options(replay_parser) + # Parse arguments args = parser.parse_args() @@ -731,6 +806,7 @@ Examples: command_handlers = { "run": _handle_run, "attach": _handle_attach, + "replay": _handle_replay, } # Execute the appropriate command @@ -743,6 +819,8 @@ Examples: def _handle_attach(args): """Handle the 'attach' command.""" + if not _is_process_running(args.pid): + raise SamplingUnknownProcessError(args.pid) # Check if live mode is requested if args.live: _handle_live_attach(args, args.pid) @@ -760,8 +838,16 @@ def _handle_attach(args): mode != PROFILING_MODE_WALL if mode != PROFILING_MODE_ALL else False ) + output_file = None + if args.format == "binary": + output_file = args.outfile or _generate_output_filename(args.format, args.pid) + # Create the appropriate collector - collector = _create_collector(args.format, args.interval, skip_idle, args.opcodes) + collector = _create_collector( + args.format, args.interval, skip_idle, args.opcodes, + output_file=output_file, + compression=getattr(args, 'compression', 'auto') + ) with _get_child_monitor_context(args, args.pid): collector = sample( @@ -792,13 +878,13 @@ def _handle_run(args): added_cwd = True try: if importlib.util.find_spec(args.target) is None: - sys.exit(f"Error: Module not found: {args.target}") + raise SamplingModuleNotFoundError(args.target) finally: if added_cwd: sys.path.remove(cwd) else: if not os.path.exists(args.target): - sys.exit(f"Error: Script not found: {args.target}") + raise SamplingScriptNotFoundError(args.target) # Check if live mode is requested if args.live: @@ -829,8 +915,16 @@ def _handle_run(args): mode != PROFILING_MODE_WALL if mode != PROFILING_MODE_ALL else False ) + output_file = None + if args.format == "binary": + output_file = args.outfile or _generate_output_filename(args.format, process.pid) + # Create the appropriate collector - collector = _create_collector(args.format, args.interval, skip_idle, args.opcodes) + collector = _create_collector( + args.format, args.interval, skip_idle, args.opcodes, + output_file=output_file, + compression=getattr(args, 'compression', 'auto') + ) with _get_child_monitor_context(args, process.pid): try: @@ -949,5 +1043,48 @@ def _handle_live_run(args): process.wait() +def _handle_replay(args): + """Handle the 'replay' command - convert binary profile to another format.""" + import os + + if not os.path.exists(args.input_file): + sys.exit(f"Error: Input file not found: {args.input_file}") + + with BinaryReader(args.input_file) as reader: + info = reader.get_info() + interval = info['sample_interval_us'] + + print(f"Replaying {info['sample_count']} samples from {args.input_file}") + print(f" Sample interval: {interval} us") + print(f" Compression: {'zstd' if info.get('compression_type', 0) == 1 else 'none'}") + + collector = _create_collector(args.format, interval, skip_idle=False) + + def progress_callback(current, total): + if total > 0: + pct = current / total + bar_width = 40 + filled = int(bar_width * pct) + bar = '█' * filled + '░' * (bar_width - filled) + print(f"\r [{bar}] {pct*100:5.1f}% ({current:,}/{total:,})", end="", flush=True) + + count = reader.replay_samples(collector, progress_callback) + print() + + if args.format == "pstats": + if args.outfile: + collector.export(args.outfile) + else: + sort_choice = args.sort if args.sort is not None else "nsamples" + limit = args.limit if args.limit is not None else 15 + sort_mode = _sort_to_mode(sort_choice) + collector.print_stats(sort_mode, limit, not args.no_summary, PROFILING_MODE_WALL) + else: + filename = args.outfile or _generate_output_filename(args.format, os.getpid()) + collector.export(filename) + + print(f"Replayed {count} samples") + + if __name__ == "__main__": main() diff --git a/Lib/profiling/sampling/collector.py b/Lib/profiling/sampling/collector.py index a1f6ec190f6..c70e1eefe27 100644 --- a/Lib/profiling/sampling/collector.py +++ b/Lib/profiling/sampling/collector.py @@ -44,8 +44,17 @@ def extract_lineno(location): class Collector(ABC): @abstractmethod - def collect(self, stack_frames): - """Collect profiling data from stack frames.""" + def collect(self, stack_frames, timestamps_us=None): + """Collect profiling data from stack frames. + + Args: + stack_frames: List of InterpreterInfo objects + timestamps_us: Optional list of timestamps in microseconds. If provided + (from binary replay with RLE batching), use these instead of current + time. If None, collectors should use time.monotonic() or similar. + The list may contain multiple timestamps when samples are batched + together (same stack, different times). + """ def collect_failed_sample(self): """Collect data about a failed sample attempt.""" @@ -79,6 +88,17 @@ class Collector(ABC): # Phase 3: Build linear stacks from each leaf to root (optimized - no sorting!) yield from self._build_linear_stacks(leaf_task_ids, task_map, child_to_parent) + def _iter_stacks(self, stack_frames, skip_idle=False): + """Yield (frames, thread_id) for all stacks, handling both sync and async modes.""" + if stack_frames and hasattr(stack_frames[0], "awaited_by"): + for frames, thread_id, _ in self._iter_async_frames(stack_frames): + if frames: + yield frames, thread_id + else: + for frames, thread_id in self._iter_all_frames(stack_frames, skip_idle=skip_idle): + if frames: + yield frames, thread_id + def _build_task_graph(self, awaited_info_list): task_map = {} child_to_parent = {} # Maps child_id -> (selected_parent_id, parent_count) diff --git a/Lib/profiling/sampling/errors.py b/Lib/profiling/sampling/errors.py new file mode 100644 index 00000000000..0832ad2d438 --- /dev/null +++ b/Lib/profiling/sampling/errors.py @@ -0,0 +1,19 @@ +"""Custom exceptions for the sampling profiler.""" + +class SamplingProfilerError(Exception): + """Base exception for sampling profiler errors.""" + +class SamplingUnknownProcessError(SamplingProfilerError): + def __init__(self, pid): + self.pid = pid + super().__init__(f"Process with PID '{pid}' does not exist.") + +class SamplingScriptNotFoundError(SamplingProfilerError): + def __init__(self, script_path): + self.script_path = script_path + super().__init__(f"Script '{script_path}' not found.") + +class SamplingModuleNotFoundError(SamplingProfilerError): + def __init__(self, module_name): + self.module_name = module_name + super().__init__(f"Module '{module_name}' not found.") diff --git a/Lib/profiling/sampling/gecko_collector.py b/Lib/profiling/sampling/gecko_collector.py index 608a15da483..c1c9cfcf3b9 100644 --- a/Lib/profiling/sampling/gecko_collector.py +++ b/Lib/profiling/sampling/gecko_collector.py @@ -66,7 +66,7 @@ class GeckoCollector(Collector): self.sample_interval_usec = sample_interval_usec self.skip_idle = skip_idle self.opcodes_enabled = opcodes - self.start_time = time.time() * 1000 # milliseconds since epoch + self.start_time = time.monotonic() * 1000 # milliseconds since start # Global string table (shared across all threads) self.global_strings = ["(root)"] # Start with root @@ -103,6 +103,9 @@ class GeckoCollector(Collector): # Opcode state tracking per thread: tid -> (opcode, lineno, col_offset, funcname, filename, start_time) self.opcode_state = {} + # For binary replay: track base timestamp (first sample's timestamp) + self._replay_base_timestamp_us = None + def _track_state_transition(self, tid, condition, active_dict, inactive_dict, active_name, inactive_name, category, current_time): """Track binary state transitions and emit markers. @@ -138,18 +141,35 @@ class GeckoCollector(Collector): self._add_marker(tid, active_name, active_dict.pop(tid), current_time, category) - def collect(self, stack_frames): - """Collect a sample from stack frames.""" - current_time = (time.time() * 1000) - self.start_time + def collect(self, stack_frames, timestamps_us=None): + """Collect samples from stack frames. + + Args: + stack_frames: List of interpreter/thread frame info + timestamps_us: List of timestamps in microseconds (None for live sampling) + """ + # Handle live sampling (no timestamps provided) + if timestamps_us is None: + current_time = (time.monotonic() * 1000) - self.start_time + times = [current_time] + else: + if not timestamps_us: + return + # Initialize base timestamp if needed + if self._replay_base_timestamp_us is None: + self._replay_base_timestamp_us = timestamps_us[0] + # Convert all timestamps to times (ms relative to first sample) + base = self._replay_base_timestamp_us + times = [(ts - base) / 1000 for ts in timestamps_us] + + first_time = times[0] # Update interval calculation if self.sample_count > 0 and self.last_sample_time > 0: - self.interval = ( - current_time - self.last_sample_time - ) / self.sample_count - self.last_sample_time = current_time + self.interval = (times[-1] - self.last_sample_time) / self.sample_count + self.last_sample_time = times[-1] - # Process threads and track GC per thread + # Process threads for interpreter_info in stack_frames: for thread_info in interpreter_info.threads: frames = thread_info.frame_info @@ -167,92 +187,86 @@ class GeckoCollector(Collector): on_cpu = bool(status_flags & THREAD_STATUS_ON_CPU) gil_requested = bool(status_flags & THREAD_STATUS_GIL_REQUESTED) - # Track GIL possession (Has GIL / No GIL) + # Track state transitions using first timestamp self._track_state_transition( tid, has_gil, self.has_gil_start, self.no_gil_start, - "Has GIL", "No GIL", CATEGORY_GIL, current_time + "Has GIL", "No GIL", CATEGORY_GIL, first_time ) - - # Track CPU state (On CPU / Off CPU) self._track_state_transition( tid, on_cpu, self.on_cpu_start, self.off_cpu_start, - "On CPU", "Off CPU", CATEGORY_CPU, current_time + "On CPU", "Off CPU", CATEGORY_CPU, first_time ) - # Track code type (Python Code / Native Code) - # This is tri-state: Python (has_gil), Native (on_cpu without gil), or Neither + # Track code type if has_gil: self._track_state_transition( tid, True, self.python_code_start, self.native_code_start, - "Python Code", "Native Code", CATEGORY_CODE_TYPE, current_time + "Python Code", "Native Code", CATEGORY_CODE_TYPE, first_time ) elif on_cpu: self._track_state_transition( tid, True, self.native_code_start, self.python_code_start, - "Native Code", "Python Code", CATEGORY_CODE_TYPE, current_time + "Native Code", "Python Code", CATEGORY_CODE_TYPE, first_time ) else: - # Thread is idle (neither has GIL nor on CPU) - close any open code markers - # This handles the third state that _track_state_transition doesn't cover if tid in self.initialized_threads: if tid in self.python_code_start: self._add_marker(tid, "Python Code", self.python_code_start.pop(tid), - current_time, CATEGORY_CODE_TYPE) + first_time, CATEGORY_CODE_TYPE) if tid in self.native_code_start: self._add_marker(tid, "Native Code", self.native_code_start.pop(tid), - current_time, CATEGORY_CODE_TYPE) + first_time, CATEGORY_CODE_TYPE) - # Track "Waiting for GIL" intervals (one-sided tracking) + # Track GIL wait if gil_requested: - self.gil_wait_start.setdefault(tid, current_time) + self.gil_wait_start.setdefault(tid, first_time) elif tid in self.gil_wait_start: self._add_marker(tid, "Waiting for GIL", self.gil_wait_start.pop(tid), - current_time, CATEGORY_GIL) + first_time, CATEGORY_GIL) - # Track exception state (Has Exception / No Exception) + # Track exception state has_exception = bool(status_flags & THREAD_STATUS_HAS_EXCEPTION) self._track_state_transition( tid, has_exception, self.exception_start, self.no_exception_start, - "Has Exception", "No Exception", CATEGORY_EXCEPTION, current_time + "Has Exception", "No Exception", CATEGORY_EXCEPTION, first_time ) - # Track GC events by detecting frames in the stack trace - # This leverages the improved GC frame tracking from commit 336366fd7ca - # which precisely identifies the thread that initiated GC collection + # Track GC events has_gc_frame = any(frame[2] == "" for frame in frames) if has_gc_frame: - # This thread initiated GC collection if tid not in self.gc_start_per_thread: - self.gc_start_per_thread[tid] = current_time + self.gc_start_per_thread[tid] = first_time elif tid in self.gc_start_per_thread: - # End GC marker when no more GC frames are detected self._add_marker(tid, "GC Collecting", self.gc_start_per_thread.pop(tid), - current_time, CATEGORY_GC) + first_time, CATEGORY_GC) - # Mark thread as initialized after processing all state transitions + # Mark thread as initialized self.initialized_threads.add(tid) - # Categorize: idle if neither has GIL nor on CPU + # Skip idle threads if requested is_idle = not has_gil and not on_cpu - - # Skip idle threads if skip_idle is enabled if self.skip_idle and is_idle: continue if not frames: continue - # Process the stack + # Process stack once to get stack_index stack_index = self._process_stack(thread_data, frames) - # Add sample - cache references to avoid dictionary lookups + # Add samples with timestamps samples = thread_data["samples"] - samples["stack"].append(stack_index) - samples["time"].append(current_time) - samples["eventDelay"].append(None) + samples_stack = samples["stack"] + samples_time = samples["time"] + samples_delay = samples["eventDelay"] - # Track opcode state changes for interval markers (leaf frame only) - if self.opcodes_enabled: + for t in times: + samples_stack.append(stack_index) + samples_time.append(t) + samples_delay.append(None) + + # Handle opcodes + if self.opcodes_enabled and frames: leaf_frame = frames[0] filename, location, funcname, opcode = leaf_frame if isinstance(location, tuple): @@ -264,18 +278,15 @@ class GeckoCollector(Collector): current_state = (opcode, lineno, col_offset, funcname, filename) if tid not in self.opcode_state: - # First observation - start tracking - self.opcode_state[tid] = (*current_state, current_time) + self.opcode_state[tid] = (*current_state, first_time) elif self.opcode_state[tid][:5] != current_state: - # State changed - emit marker for previous state prev_opcode, prev_lineno, prev_col, prev_funcname, prev_filename, prev_start = self.opcode_state[tid] self._add_opcode_interval_marker( - tid, prev_opcode, prev_lineno, prev_col, prev_funcname, prev_start, current_time + tid, prev_opcode, prev_lineno, prev_col, prev_funcname, prev_start, first_time ) - # Start tracking new state - self.opcode_state[tid] = (*current_state, current_time) + self.opcode_state[tid] = (*current_state, first_time) - self.sample_count += 1 + self.sample_count += len(times) def _create_thread(self, tid): """Create a new thread structure with processed profile format.""" diff --git a/Lib/profiling/sampling/heatmap_collector.py b/Lib/profiling/sampling/heatmap_collector.py index 5b4c89283be..bb810fa485b 100644 --- a/Lib/profiling/sampling/heatmap_collector.py +++ b/Lib/profiling/sampling/heatmap_collector.py @@ -5,6 +5,7 @@ import collections import html import importlib.resources import json +import locale import math import os import platform @@ -15,6 +16,7 @@ from pathlib import Path from typing import Dict, List, Tuple from ._css_utils import get_combined_css +from ._format_utils import fmt from .collector import normalize_location, extract_lineno from .stack_collector import StackTraceCollector @@ -343,7 +345,7 @@ class _HtmlRenderer:
{icon} {type_names[module_type]} - ({tree.count} {file_word}, {tree.samples:,} {sample_word}) + ({tree.count} {file_word}, {tree.samples:n} {sample_word})
''' @@ -390,7 +392,7 @@ class _HtmlRenderer: parts.append(f'{indent} ') parts.append(f'{indent} 📁 {html.escape(name)}') parts.append(f'{indent} ' - f'({node.count} {file_word}, {node.samples:,} {sample_word})') + f'({node.count} {file_word}, {node.samples:n} {sample_word})') parts.append(f'{indent}
') parts.append(f'{indent}