Merge branch 'main' into gh-141805

This commit is contained in:
Mikhail Efimov 2025-12-22 08:21:33 +03:00 committed by GitHub
commit de4dbb4b7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
110 changed files with 3925 additions and 2149 deletions

View file

@ -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:
* ``<assert.h>``
* ``<intrin.h>`` (on Windows)
* ``<inttypes.h>``
* ``<limits.h>``
* ``<math.h>``
* ``<stdarg.h>``
* ``<wchar.h>``
* ``<sys/types.h>`` (if present)
The following are included for backwards compatibility, unless using
:ref:`Limited API <limited-c-api>` 3.13 or newer:
* ``<ctype.h>``
* ``<unistd.h>`` (on POSIX)
The following are included for backwards compatibility, unless using
:ref:`Limited API <limited-c-api>` 3.11 or newer:
* ``<errno.h>``
* ``<stdio.h>``
* ``<stdlib.h>``
* ``<string.h>``
.. 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
=============

View file

@ -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`.)

View file

@ -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
<https://cffi.readthedocs.io/>`_ 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 <Python.h>
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:
* ``<assert.h>``
* ``<intrin.h>`` (on Windows)
* ``<inttypes.h>``
* ``<limits.h>``
* ``<math.h>``
* ``<stdarg.h>``
* ``<wchar.h>``
* ``<sys/types.h>`` (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:
* ``<ctype.h>``
* ``<unistd.h>`` (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:
* ``<errno.h>``
* ``<stdio.h>``
* ``<stdlib.h>``
* ``<string.h>``
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 <first-extension-module>` 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 <Python.h>
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 <isolating-extensions-howto>`,
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 <first-extension-module>` 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 <Python.h>
::
@ -773,6 +478,17 @@ Some example calls::
Keyword Parameters for Extension Functions
==========================================
If you also want your function to accept
:term:`keyword arguments <keyword argument>`, 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 <first-extension-module>`.
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.

View file

@ -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 <c-api-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 <https://pip.pypa.io/>`__ (``pip install``), but you
can substitute any tool that can build and install ``pyproject.toml``-based
projects, like `uv <https://docs.astral.sh/uv/>`_ (``uv pip install``).
Preferably, have a :ref:`virtual environment <venv-def>` 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 <stdlib.h>
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: <Python.h>
:end-at: <stdlib.h>
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 <meson-python>`_ 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 <venv-def>`.
(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 <extension-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)
<module 'spam' from '/home/encukou/dev/cpython/spam.so'>
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)
<built-in function 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 <https://packaging.python.org/en/latest/guides/tool-recommendations/#build-backends-for-extension-modules>`_;
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 <extension-export-hook>`,
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 <capi-system-includes>` 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``.

View file

@ -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
<https://cffi.readthedocs.io/>`_ 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
<c-api-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`

View file

@ -0,0 +1,55 @@
/* This file needs to be kept in sync with the tutorial
* at Doc/extending/first-extension-module.rst
*/
/// Includes
#include <Python.h>
#include <stdlib.h> // 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;
}

View file

@ -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()

View file

@ -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

View file

@ -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
<https://web.archive.org/web/20220531051136/https://webspace.science.uu.nl/~gent0113/calendar/isocalendar.htm>`_
for a good explanation.
.. [#] Passing ``datetime.strptime('Feb 29', '%b %d')`` will fail since 1900 is not a leap year.

View file

@ -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 (``<auto instance>, -3`` is used to
* ``THREE = [auto(), -3]`` will *not* work (``[<auto instance>, -3]`` is used to
create the ``THREE`` enum member)
.. versionchanged:: 3.11.1

View file

@ -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/<pid>/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 <debug-build>`.
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

View file

@ -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`,

View file

@ -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.

View file

@ -592,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 <PR_SET_VMA(2const)>` (Linux 5.17 or newer).
(Contributed by Donghee Na in :gh:`142419`.)
os
--
@ -1104,9 +1109,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`
@ -1127,6 +1132,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`.)

View file

@ -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);

View file

@ -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); }

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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
);

View file

@ -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)

View file

@ -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;

View file

@ -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

View file

@ -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 } } },

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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`.
*/

File diff suppressed because it is too large Load diff

View file

@ -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,
@ -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 },
},
},
@ -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,
@ -3611,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,
@ -3818,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,
@ -3906,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",
@ -3932,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",
@ -4170,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",
@ -4764,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",
@ -5481,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:

View file

@ -240,4 +240,5 @@ if __name__ == '__main__':
break
console.write('exiting asyncio REPL...\n')
loop.close()
sys.exit(return_code)

View file

@ -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):

View file

@ -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

View file

@ -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)

View file

@ -1141,6 +1141,10 @@
.line-samples-cumulative {
padding: 0 4px;
}
.bytecode-panel {
margin: 8px 10px 8px 160px;
}
}
.bytecode-toggle {
@ -1172,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 */

View file

@ -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 = '&#9654;'; // 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 = '&#9660;'; // Down arrow
}

View file

@ -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)
// ============================================================================

View file

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" data-theme="light">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" data-theme="light">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

View file

@ -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)
// ============================================================================

View file

@ -978,7 +978,17 @@ class HeatmapCollector(StackTraceCollector):
f'data-spec-pct="{spec_pct}" '
f'onclick="toggleBytecode(this)" title="Show bytecode">&#9654;</button>'
)
bytecode_panel_html = f' <div class="bytecode-panel" id="bytecode-{line_num}" style="display:none;"></div>\n'
# Wrapper contains columns + content panel
bytecode_panel_html = (
f' <div class="bytecode-wrapper" id="bytecode-wrapper-{line_num}">\n'
f' <div class="bytecode-columns">'
f'<div class="line-number"></div>'
f'<div class="line-samples-self"></div>'
f'<div class="line-samples-cumulative"></div>'
f'</div>\n'
f' <div class="bytecode-panel" id="bytecode-{line_num}"></div>\n'
f' </div>\n'
)
elif self.opcodes_enabled:
# Add invisible spacer to maintain consistent indentation when opcodes are enabled
bytecode_btn_html = '<div class="bytecode-spacer"></div>'

View file

@ -2060,6 +2060,37 @@ class ByteArrayTest(BaseBytesTest, unittest.TestCase):
self.assertEqual(instance.ba[0], ord("?"), "Assigned bytearray not altered")
self.assertEqual(instance.new_ba, bytearray(0x180), "Wrong object altered")
def test_search_methods_reentrancy_raises_buffererror(self):
# gh-142560: Raise BufferError if buffer mutates during search arg conversion.
class Evil:
def __init__(self, ba):
self.ba = ba
def __buffer__(self, flags):
self.ba.clear()
return memoryview(self.ba)
def __release_buffer__(self, view: memoryview) -> None:
view.release()
def __index__(self):
self.ba.clear()
return ord("A")
def make_case():
ba = bytearray(b"A")
return ba, Evil(ba)
for name in ("find", "count", "index", "rindex", "rfind"):
ba, evil = make_case()
with self.subTest(name):
with self.assertRaises(BufferError):
getattr(ba, name)(evil)
ba, evil = make_case()
with self.assertRaises(BufferError):
evil in ba
with self.assertRaises(BufferError):
ba.split(evil)
with self.assertRaises(BufferError):
ba.rsplit(evil)
class AssortedBytesTest(unittest.TestCase):
#

View file

@ -1877,6 +1877,26 @@ class TestUopsOptimization(unittest.TestCase):
self.assertNotIn("_GUARD_TOS_UNICODE", uops)
self.assertIn("_BINARY_OP_ADD_UNICODE", uops)
def test_binary_op_subscr_str_int(self):
def testfunc(n):
x = 0
s = "hello"
for _ in range(n):
c = s[1] # _BINARY_OP_SUBSCR_STR_INT
if c == 'e':
x += 1
return x
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_SUBSCR_STR_INT", uops)
self.assertIn("_COMPARE_OP_STR", uops)
self.assertIn("_POP_TOP_NOP", uops)
self.assertNotIn("_POP_TOP", uops)
self.assertNotIn("_POP_TOP_INT", uops)
def test_call_type_1_guards_removed(self):
def testfunc(n):
x = 0
@ -2552,6 +2572,22 @@ class TestUopsOptimization(unittest.TestCase):
self.assertIn("_POP_TOP_NOP", uops)
self.assertNotIn("_POP_TOP", uops)
def test_unicode_add_op_refcount_elimination(self):
def testfunc(n):
c = "a"
res = ""
for _ in range(n):
res = c + c
return res
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, "aa")
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_ADD_UNICODE", uops)
self.assertIn("_POP_TOP_NOP", uops)
self.assertNotIn("_POP_TOP", uops)
def test_remove_guard_for_slice_list(self):
def f(n):
for i in range(n):
@ -2972,6 +3008,74 @@ class TestUopsOptimization(unittest.TestCase):
for _ in range(TIER2_THRESHOLD+1):
obj.attr = EvilAttr(obj.__dict__)
def test_promoted_global_refcount_eliminated(self):
result = script_helper.run_python_until_end('-c', textwrap.dedent("""
import _testinternalcapi
import opcode
import _opcode
def get_first_executor(func):
code = func.__code__
co_code = code.co_code
for i in range(0, len(co_code), 2):
try:
return _opcode.get_executor(code, i)
except ValueError:
pass
return None
def get_opnames(ex):
return {item[0] for item in ex}
def testfunc(n):
y = []
for i in range(n):
x = tuple(y)
return x
testfunc(_testinternalcapi.TIER2_THRESHOLD)
ex = get_first_executor(testfunc)
assert ex is not None
uops = get_opnames(ex)
assert "_LOAD_GLOBAL_BUILTIN" not in uops
assert "_LOAD_CONST_INLINE_BORROW" in uops
assert "_POP_TOP_NOP" in uops
assert "_POP_TOP" not in uops
"""), PYTHON_JIT="1")
self.assertEqual(result[0].rc, 0, result)
def test_constant_fold_tuple(self):
def testfunc(n):
for _ in range(n):
t = (1,)
p = len(t)
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_CALL_LEN", uops)
def test_binary_subscr_list_int(self):
def testfunc(n):
l = [1]
x = 0
for _ in range(n):
y = l[0]
x += y
return x
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
self.assertEqual(res, TIER2_THRESHOLD)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_SUBSCR_LIST_INT", uops)
self.assertNotIn("_POP_TOP", uops)
self.assertNotIn("_POP_TOP_INT", uops)
self.assertIn("_POP_TOP_NOP", uops)
def global_identity(x):
return x

View file

@ -2215,6 +2215,16 @@ class SectionlessTestCase(unittest.TestCase):
cfg.add_section(configparser.UNNAMED_SECTION)
cfg.set(configparser.UNNAMED_SECTION, 'a', '1')
self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a'])
output = io.StringIO()
cfg.write(output)
self.assertEqual(output.getvalue(), 'a = 1\n\n')
cfg = configparser.ConfigParser(allow_unnamed_section=True)
cfg[configparser.UNNAMED_SECTION] = {'a': '1'}
self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a'])
output = io.StringIO()
cfg.write(output)
self.assertEqual(output.getvalue(), 'a = 1\n\n')
def test_disabled_error(self):
with self.assertRaises(configparser.MissingSectionHeaderError):
@ -2223,6 +2233,9 @@ class SectionlessTestCase(unittest.TestCase):
with self.assertRaises(configparser.UnnamedSectionDisabledError):
configparser.ConfigParser().add_section(configparser.UNNAMED_SECTION)
with self.assertRaises(configparser.UnnamedSectionDisabledError):
configparser.ConfigParser()[configparser.UNNAMED_SECTION] = {'a': '1'}
def test_multiple_configs(self):
cfg = configparser.ConfigParser(allow_unnamed_section=True)
cfg.read_string('a = 1')

View file

@ -49,3 +49,74 @@ class TestFTGenerators(TestCase):
self.concurrent_write_with_func(func=set_gen_name)
with self.subTest(func=set_gen_qualname):
self.concurrent_write_with_func(func=set_gen_qualname)
def test_concurrent_send(self):
def gen():
yield 1
yield 2
yield 3
yield 4
yield 5
def run_test(drive_generator):
g = gen()
values = []
threading_helper.run_concurrently(drive_generator, self.NUM_THREADS, args=(g, values,))
self.assertEqual(sorted(values), [1, 2, 3, 4, 5])
def call_next(g, values):
while True:
try:
values.append(next(g))
except ValueError:
continue
except StopIteration:
break
with self.subTest(method='next'):
run_test(call_next)
def call_send(g, values):
while True:
try:
values.append(g.send(None))
except ValueError:
continue
except StopIteration:
break
with self.subTest(method='send'):
run_test(call_send)
def for_iter_gen(g, values):
while True:
try:
for value in g:
values.append(value)
else:
break
except ValueError:
continue
with self.subTest(method='for'):
run_test(for_iter_gen)
def test_concurrent_close(self):
def gen():
for i in range(10):
yield i
time.sleep(0.001)
def drive_generator(g):
while True:
try:
for value in g:
if value == 5:
g.close()
else:
return
except ValueError as e:
self.assertEqual(e.args[0], "generator already executing")
g = gen()
threading_helper.run_concurrently(drive_generator, self.NUM_THREADS, args=(g,))

View file

@ -134,6 +134,18 @@ class FinalizationTest(unittest.TestCase):
self.assertEqual(len(resurrected), 1)
self.assertIsInstance(resurrected[0].gi_code, types.CodeType)
def test_exhausted_generator_frame_cycle(self):
def g():
yield
generator = g()
frame = generator.gi_frame
self.assertIsNone(frame.f_back)
next(generator)
self.assertIsNone(frame.f_back)
next(generator, None)
self.assertIsNone(frame.f_back)
class GeneratorTest(unittest.TestCase):
@ -290,6 +302,33 @@ class GeneratorTest(unittest.TestCase):
self.assertEqual([1,2], list(i for i in C()))
def test_close_clears_frame(self):
# gh-142766: Test that closing a generator clears its frame
class DetectDelete:
def __init__(self):
DetectDelete.deleted = False
def __del__(self):
DetectDelete.deleted = True
def generator(arg):
yield
# Test a freshly created generator (not suspended)
g = generator(DetectDelete())
g.close()
self.assertTrue(DetectDelete.deleted)
# Test a suspended generator
g = generator(DetectDelete())
next(g)
g.close()
self.assertTrue(DetectDelete.deleted)
# Clear via gi_frame.clear()
g = generator(DetectDelete())
g.gi_frame.clear()
self.assertTrue(DetectDelete.deleted)
class ModifyUnderlyingIterableTest(unittest.TestCase):
iterables = [

View file

@ -174,6 +174,7 @@ class MinidomTest(unittest.TestCase):
self.assertEqual(dom.documentElement.childNodes[-1].data, "Hello")
dom.unlink()
@support.requires_resource('cpu')
def testAppendChildNoQuadraticComplexity(self):
impl = getDOMImplementation()
@ -182,14 +183,18 @@ class MinidomTest(unittest.TestCase):
children = [newdoc.createElement(f"child-{i}") for i in range(1, 2 ** 15 + 1)]
element = top_element
start = time.time()
start = time.monotonic()
for child in children:
element.appendChild(child)
element = child
end = time.time()
end = time.monotonic()
# This example used to take at least 30 seconds.
self.assertLess(end - start, 1)
# Conservative assertion due to the wide variety of systems and
# build configs timing based tests wind up run under.
# A --with-address-sanitizer --with-pydebug build on a rpi5 still
# completes this loop in <0.5 seconds.
self.assertLess(end - start, 4)
def testSetAttributeNodeWithoutOwnerDocument(self):
# regression test for gh-142754

View file

@ -6,6 +6,7 @@ from test.support import (
from test.support.import_helper import import_module
from test.support.os_helper import TESTFN, unlink
from test.support.script_helper import assert_python_ok
import errno
import unittest
import os
import re
@ -1165,6 +1166,46 @@ class MmapTests(unittest.TestCase):
m.flush(PAGESIZE)
m.flush(PAGESIZE, PAGESIZE)
@unittest.skipUnless(sys.platform == 'linux', 'Linux only')
@support.requires_linux_version(5, 17, 0)
def test_set_name(self):
# Test setting name on anonymous mmap
m = mmap.mmap(-1, PAGESIZE)
self.addCleanup(m.close)
try:
result = m.set_name('test_mapping')
except OSError as exc:
if exc.errno == errno.EINVAL:
# gh-142419: On Fedora, prctl(PR_SET_VMA_ANON_NAME) fails with
# EINVAL because the kernel option CONFIG_ANON_VMA_NAME is
# disabled.
# See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746
self.skipTest("prctl() failed with EINVAL")
else:
raise
self.assertIsNone(result)
# Test name length limit (80 chars including prefix "cpython:mmap:" and '\0')
# Prefix is 13 chars, so max name is 66 chars
long_name = 'x' * 66
result = m.set_name(long_name)
self.assertIsNone(result)
# Test name too long
too_long_name = 'x' * 67
with self.assertRaises(ValueError):
m.set_name(too_long_name)
# Test that file-backed mmap raises error
with open(TESTFN, 'wb+') as f:
f.write(b'x' * PAGESIZE)
f.flush()
m2 = mmap.mmap(f.fileno(), PAGESIZE)
self.addCleanup(m2.close)
with self.assertRaises(ValueError):
m2.set_name('should_fail')
class LargeMmapTests(unittest.TestCase):

View file

@ -3478,6 +3478,49 @@ def test_pdb_issue_gh_65052():
(Pdb) continue
"""
def test_pdb_commands_last_breakpoint():
"""See GH-142834
>>> def test_function():
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
... foo = 1
... bar = 2
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
... 'break 4',
... 'break 3',
... 'clear 2',
... 'commands',
... 'p "success"',
... 'end',
... 'continue',
... 'clear 1',
... 'commands',
... 'continue',
... ]):
... test_function()
> <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>(2)test_function()
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
(Pdb) break 4
Breakpoint 1 at <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>:4
(Pdb) break 3
Breakpoint 2 at <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>:3
(Pdb) clear 2
Deleted breakpoint 2 at <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>:3
(Pdb) commands
(com) p "success"
(com) end
(Pdb) continue
'success'
> <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>(4)test_function()
-> bar = 2
(Pdb) clear 1
Deleted breakpoint 1 at <doctest test.test_pdb.test_pdb_commands_last_breakpoint[0]>:4
(Pdb) commands
*** cannot set commands: no existing breakpoint
(Pdb) continue
"""
@support.force_not_colorized_test_class
@support.requires_subprocess()
@ -4050,6 +4093,23 @@ def bœr():
f.write("invalid")
self.assertEqual(pdb.Pdb().rcLines[0], "invalid")
def test_readrc_current_dir(self):
with os_helper.temp_cwd() as cwd:
rc_path = os.path.join(cwd, ".pdbrc")
with open(rc_path, "w") as f:
f.write("invalid")
self.assertEqual(pdb.Pdb().rcLines[-1], "invalid")
def test_readrc_cwd_is_home(self):
with os_helper.EnvironmentVarGuard() as env:
env.unset("HOME")
with os_helper.temp_cwd() as cwd, patch("os.path.expanduser"):
rc_path = os.path.join(cwd, ".pdbrc")
os.path.expanduser.return_value = rc_path
with open(rc_path, "w") as f:
f.write("invalid")
self.assertEqual(pdb.Pdb().rcLines, ["invalid"])
def test_header(self):
stdout = StringIO()
header = 'Nobody expects... blah, blah, blah'

View file

@ -1573,5 +1573,17 @@ class TestModuleAll(unittest.TestCase):
check__all__(self, sax, extra=extra)
class TestModule(unittest.TestCase):
def test_deprecated__version__and__date__(self):
for module in (sax.expatreader, sax.handler):
with self.subTest(module=module):
with self.assertWarnsRegex(
DeprecationWarning,
"'version' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(module, "version")
self.assertEqual(cm.filename, __file__)
if __name__ == "__main__":
unittest.main()

View file

@ -759,7 +759,7 @@ class NormalizationTest(unittest.TestCase):
@requires_resource('cpu')
def test_normalization_3_2_0(self):
testdatafile = findfile('NormalizationTest-3.2.0.txt', 'data')
testdatafile = findfile('NormalizationTest-3.2.0.txt')
with open(testdatafile, encoding='utf-8') as testdata:
self.run_normalization_tests(testdata, unicodedata.ucd_3_2_0)

View file

@ -577,6 +577,23 @@ class OpenerDirectorTests(unittest.TestCase):
self.assertRaises(TypeError,
OpenerDirector().add_handler, NonHandler())
def test_no_protocol_methods(self):
# test the case that methods starts with handler type without the protocol
# like open*() or _open*().
# These methods should be ignored
o = OpenerDirector()
meth_spec = [
["open"],
["_open"],
["error"]
]
add_ordered_mock_handlers(o, meth_spec)
self.assertEqual(len(o.handle_open), 0)
self.assertEqual(len(o.handle_error), 0)
def test_badly_named_methods(self):
# test work-around for three methods that accidentally follow the
# naming conventions for handler methods

View file

@ -4705,6 +4705,19 @@ class C14NTest(unittest.TestCase):
# --------------------------------------------------------------------
class TestModule(unittest.TestCase):
def test_deprecated_version(self):
with self.assertWarnsRegex(
DeprecationWarning,
"'VERSION' is deprecated and slated for removal in Python 3.20",
) as cm:
getattr(ET, "VERSION")
self.assertEqual(cm.filename, __file__)
# --------------------------------------------------------------------
def setUpModule(module=None):
# When invoked without a module, runs the Python ET tests by loading pyET.
# Otherwise, uses the given module as the ET.

View file

@ -415,6 +415,8 @@ class OpenerDirector:
continue
i = meth.find("_")
if i < 1:
continue
protocol = meth[:i]
condition = meth[i+1:]

View file

@ -83,15 +83,12 @@ __all__ = [
"SubElement",
"tostring", "tostringlist",
"TreeBuilder",
"VERSION",
"XML", "XMLID",
"XMLParser", "XMLPullParser",
"register_namespace",
"canonicalize", "C14NWriterTarget",
]
VERSION = "1.3.0"
import sys
import re
import warnings
@ -2104,3 +2101,14 @@ except ImportError:
pass
else:
_set_factories(Comment, ProcessingInstruction)
# --------------------------------------------------------------------
def __getattr__(name):
if name == "VERSION":
from warnings import _deprecated
_deprecated("VERSION", remove=(3, 20))
return "1.3.0" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View file

@ -3,8 +3,6 @@ SAX driver for the pyexpat C module. This driver works with
pyexpat.__version__ == '2.22'.
"""
version = "0.20"
from xml.sax._exceptions import *
from xml.sax.handler import feature_validation, feature_namespaces
from xml.sax.handler import feature_namespace_prefixes
@ -446,6 +444,16 @@ def create_parser(*args, **kwargs):
# ---
def __getattr__(name):
if name == "version":
from warnings import _deprecated
_deprecated("version", remove=(3, 20))
return "0.20" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# ---
if __name__ == "__main__":
import xml.sax.saxutils
p = create_parser()

View file

@ -9,8 +9,6 @@ of the interfaces.
$Id$
"""
version = '2.0beta'
#============================================================================
#
# HANDLER INTERFACES
@ -385,3 +383,12 @@ class LexicalHandler:
def endCDATA(self):
"""Reports the end of a CDATA marked section."""
def __getattr__(name):
if name == "version":
from warnings import _deprecated
_deprecated("version", remove=(3, 20))
return "2.0beta" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View file

@ -0,0 +1 @@
Fix reference cycle in exhausted generator frames. Patch by Savannah Ostrowski.

View file

@ -0,0 +1,2 @@
Fix a memory leak in the experimental Tier 2 optimizer when creating
executors. Patched by Shamil Abdulaev.

View file

@ -0,0 +1 @@
Clear the frame of a generator when :meth:`generator.close` is called.

View file

@ -0,0 +1,2 @@
Make concurrent iteration over the same range iterator thread-safe in the
free threading build.

View file

@ -0,0 +1 @@
Fix building JIT stencils on free-threaded builds.

View file

@ -0,0 +1 @@
Fix a file descriptor leak in import.c

View file

@ -0,0 +1 @@
Fix a segfault in the JIT when constant folding ``len(tuple)``.

View file

@ -0,0 +1,2 @@
Add support for :const:`~configparser.UNNAMED_SECTION` when creating a
section via the mapping protocol access

View file

@ -0,0 +1,4 @@
Fix issue where methods in handlers that lacked the protocol name but
matched a valid base handler method (e.g., ``_open()`` or ``error()``)
were incorrectly added to :class:`urllib.request.OpenerDirector`'s
handlers. Contributed by Andrea Mattei.

View file

@ -0,0 +1,3 @@
:meth:`mmap.mmap.set_name` method added to annotate an anonymous memory map
if Linux kernel supports ``PR_SET_VMA_ANON_NAME`` (Linux 5.17 or newer).
Patch by Donghee Na.

View file

@ -0,0 +1 @@
Fix use-after-free in :class:`bytearray` search-like methods (:meth:`~bytearray.find`, :meth:`~bytearray.count`, :meth:`~bytearray.index`, :meth:`~bytearray.rindex`, and :meth:`~bytearray.rfind`) by marking the storage as exported which causes reallocation attempts to raise :exc:`BufferError`. For :func:`~operator.contains`, :meth:`~bytearray.split`, and :meth:`~bytearray.rsplit` the :ref:`buffer protocol <bufferobjects>` is used for this.

View file

@ -0,0 +1,3 @@
The :mod:`asyncio` REPL now properly closes the loop upon the end of interactive session.
Previously, it could cause surprising warnings.
Contributed by Bartosz Sławecki.

View file

@ -0,0 +1,3 @@
Deprecate ``VERSION`` from :mod:`xml.etree.ElementTree` and ``version`` from
:mod:`!xml.sax.expatreader` and :mod:`xml.sax.handler`. Patch by Hugo van
Kemenade.

View file

@ -0,0 +1 @@
Change the :mod:`pdb` ``commands`` command to use the last available breakpoint instead of failing when the most recently created breakpoint was deleted.

View file

@ -0,0 +1 @@
Fix issue where ``pdb`` would read a ``.pdbrc`` twice if launched from the home directory

View file

@ -279,6 +279,35 @@ typedef struct {
size_t count;
} StackChunkList;
/*
* Context for frame chain traversal operations.
*/
typedef struct {
/* Inputs */
uintptr_t frame_addr; // Starting frame address
uintptr_t base_frame_addr; // Sentinel at bottom (for validation)
uintptr_t gc_frame; // GC frame address (0 if not tracking)
uintptr_t last_profiled_frame; // Last cached frame (0 if no cache)
StackChunkList *chunks; // Pre-copied stack chunks
/* Outputs */
PyObject *frame_info; // List to append FrameInfo objects
uintptr_t *frame_addrs; // Array of visited frame addresses
Py_ssize_t num_addrs; // Count of addresses collected
Py_ssize_t max_addrs; // Capacity of frame_addrs array
uintptr_t last_frame_visited; // Last frame address visited
int stopped_at_cached_frame; // Whether we stopped at cached frame
} FrameWalkContext;
/*
* Context for code object parsing.
*/
typedef struct {
uintptr_t code_addr; // Code object address in remote process
uintptr_t instruction_pointer; // Current instruction pointer
int32_t tlbc_index; // Thread-local bytecode index (free-threading)
} CodeObjectContext;
/* Function pointer types for iteration callbacks */
typedef int (*thread_processor_func)(
RemoteUnwinderObject *unwinder,
@ -343,10 +372,7 @@ extern long read_py_long(RemoteUnwinderObject *unwinder, uintptr_t address);
extern int parse_code_object(
RemoteUnwinderObject *unwinder,
PyObject **result,
uintptr_t address,
uintptr_t instruction_pointer,
uintptr_t *previous_frame,
int32_t tlbc_index
const CodeObjectContext *ctx
);
extern PyObject *make_location_info(
@ -420,17 +446,7 @@ extern void *find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr);
extern int process_frame_chain(
RemoteUnwinderObject *unwinder,
uintptr_t initial_frame_addr,
StackChunkList *chunks,
PyObject *frame_info,
uintptr_t base_frame_addr,
uintptr_t gc_frame,
uintptr_t last_profiled_frame,
int *stopped_at_cached_frame,
uintptr_t *frame_addrs,
Py_ssize_t *num_addrs,
Py_ssize_t max_addrs,
uintptr_t *out_last_frame_addr
FrameWalkContext *ctx
);
/* Frame cache functions */
@ -460,12 +476,7 @@ extern int frame_cache_store(
extern int collect_frames_with_cache(
RemoteUnwinderObject *unwinder,
uintptr_t frame_addr,
StackChunkList *chunks,
PyObject *frame_info,
uintptr_t base_frame_addr,
uintptr_t gc_frame,
uintptr_t last_profiled_frame,
FrameWalkContext *ctx,
uint64_t thread_id);
/* ============================================================================

View file

@ -76,6 +76,7 @@ cache_tlbc_array(RemoteUnwinderObject *unwinder, uintptr_t code_addr, uintptr_t
PyErr_SetString(PyExc_RuntimeError, "TLBC array size exceeds maximum limit");
return 0; // Invalid size
}
assert(tlbc_size > 0 && tlbc_size <= MAX_TLBC_SIZE);
// Allocate and read the entire TLBC array
size_t array_data_size = tlbc_size * sizeof(void*);
@ -156,8 +157,11 @@ parse_linetable(const uintptr_t addrq, const char* linetable, int firstlineno, L
const uint8_t* ptr = (const uint8_t*)(linetable);
uintptr_t addr = 0;
int computed_line = firstlineno; // Running accumulator, separate from output
const size_t MAX_LINETABLE_ENTRIES = 65536;
size_t entry_count = 0;
while (*ptr != '\0') {
while (*ptr != '\0' && entry_count < MAX_LINETABLE_ENTRIES) {
entry_count++;
uint8_t first_byte = *(ptr++);
uint8_t code = (first_byte >> 3) & 15;
size_t length = (first_byte & 7) + 1;
@ -277,12 +281,9 @@ make_frame_info(RemoteUnwinderObject *unwinder, PyObject *file, PyObject *locati
int
parse_code_object(RemoteUnwinderObject *unwinder,
PyObject **result,
uintptr_t address,
uintptr_t instruction_pointer,
uintptr_t *previous_frame,
int32_t tlbc_index)
const CodeObjectContext *ctx)
{
void *key = (void *)address;
void *key = (void *)ctx->code_addr;
CachedCodeMetadata *meta = NULL;
PyObject *func = NULL;
PyObject *file = NULL;
@ -291,9 +292,9 @@ parse_code_object(RemoteUnwinderObject *unwinder,
#ifdef Py_GIL_DISABLED
// In free threading builds, code object addresses might have the low bit set
// as a flag, so we need to mask it off to get the real address
uintptr_t real_address = address & (~1);
uintptr_t real_address = ctx->code_addr & (~1);
#else
uintptr_t real_address = address;
uintptr_t real_address = ctx->code_addr;
#endif
if (unwinder && unwinder->code_object_cache != NULL) {
@ -360,12 +361,12 @@ parse_code_object(RemoteUnwinderObject *unwinder,
linetable = NULL;
}
uintptr_t ip = instruction_pointer;
uintptr_t ip = ctx->instruction_pointer;
ptrdiff_t addrq;
#ifdef Py_GIL_DISABLED
// Handle thread-local bytecode (TLBC) in free threading builds
if (tlbc_index == 0 || unwinder->debug_offsets.code_object.co_tlbc == 0 || unwinder == NULL) {
if (ctx->tlbc_index == 0 || unwinder->debug_offsets.code_object.co_tlbc == 0 || unwinder == NULL) {
// No TLBC or no unwinder - use main bytecode directly
addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
goto done_tlbc;
@ -383,10 +384,12 @@ parse_code_object(RemoteUnwinderObject *unwinder,
tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
}
if (tlbc_entry && tlbc_index < tlbc_entry->tlbc_array_size) {
if (tlbc_entry && ctx->tlbc_index < tlbc_entry->tlbc_array_size) {
assert(ctx->tlbc_index >= 0);
assert(tlbc_entry->tlbc_array_size > 0);
// Use cached TLBC data
uintptr_t *entries = (uintptr_t *)((char *)tlbc_entry->tlbc_array + sizeof(Py_ssize_t));
uintptr_t tlbc_bytecode_addr = entries[tlbc_index];
uintptr_t tlbc_bytecode_addr = entries[ctx->tlbc_index];
if (tlbc_bytecode_addr != 0) {
// Calculate offset from TLBC bytecode
@ -401,8 +404,6 @@ parse_code_object(RemoteUnwinderObject *unwinder,
done_tlbc:
#else
// Non-free-threaded build, always use the main bytecode
(void)tlbc_index; // Suppress unused parameter warning
(void)unwinder; // Suppress unused parameter warning
addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
#endif
; // Empty statement to avoid C23 extension warning

View file

@ -44,7 +44,9 @@ frame_cache_find(RemoteUnwinderObject *unwinder, uint64_t thread_id)
return NULL;
}
for (int i = 0; i < FRAME_CACHE_MAX_THREADS; i++) {
assert(i >= 0 && i < FRAME_CACHE_MAX_THREADS);
if (unwinder->frame_cache[i].thread_id == thread_id) {
assert(unwinder->frame_cache[i].num_addrs <= FRAME_CACHE_MAX_FRAMES);
return &unwinder->frame_cache[i];
}
}
@ -154,6 +156,8 @@ frame_cache_lookup_and_extend(
return 0;
}
assert(entry->num_addrs >= 0 && entry->num_addrs <= FRAME_CACHE_MAX_FRAMES);
// Find the index where last_profiled_frame matches
Py_ssize_t start_idx = -1;
for (Py_ssize_t i = 0; i < entry->num_addrs; i++) {
@ -166,6 +170,7 @@ frame_cache_lookup_and_extend(
if (start_idx < 0) {
return 0; // Not found
}
assert(start_idx < entry->num_addrs);
Py_ssize_t num_frames = PyList_GET_SIZE(entry->frame_list);
@ -225,6 +230,7 @@ frame_cache_store(
if (num_addrs > FRAME_CACHE_MAX_FRAMES) {
num_addrs = FRAME_CACHE_MAX_FRAMES;
}
assert(num_addrs >= 0 && num_addrs <= FRAME_CACHE_MAX_FRAMES);
FrameCacheEntry *entry = frame_cache_alloc_slot(unwinder, thread_id);
if (!entry) {
@ -245,6 +251,8 @@ frame_cache_store(
entry->thread_id = thread_id;
memcpy(entry->addrs, addrs, num_addrs * sizeof(uintptr_t));
entry->num_addrs = num_addrs;
assert(entry->num_addrs == num_addrs);
assert(entry->thread_id == thread_id);
return 1;
}

View file

@ -88,7 +88,8 @@ copy_stack_chunks(RemoteUnwinderObject *unwinder,
return -1;
}
while (chunk_addr != 0) {
const size_t MAX_STACK_CHUNKS = 4096;
while (chunk_addr != 0 && count < MAX_STACK_CHUNKS) {
// Grow array if needed
if (count >= max_chunks) {
max_chunks *= 2;
@ -128,6 +129,7 @@ void *
find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr)
{
for (size_t i = 0; i < chunks->count; ++i) {
assert(chunks->chunks[i].size > offsetof(_PyStackChunk, data));
uintptr_t base = chunks->chunks[i].remote_addr + offsetof(_PyStackChunk, data);
size_t payload = chunks->chunks[i].size - offsetof(_PyStackChunk, data);
@ -209,7 +211,13 @@ parse_frame_object(
#endif
*address_of_code_object = code_object;
return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
CodeObjectContext code_ctx = {
.code_addr = code_object,
.instruction_pointer = instruction_pointer,
.tlbc_index = tlbc_index,
};
return parse_code_object(unwinder, result, &code_ctx);
}
int
@ -246,7 +254,12 @@ parse_frame_from_chunks(
}
#endif
return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
CodeObjectContext code_ctx = {
.code_addr = code_object,
.instruction_pointer = instruction_pointer,
.tlbc_index = tlbc_index,
};
return parse_code_object(unwinder, result, &code_ctx);
}
/* ============================================================================
@ -256,105 +269,80 @@ parse_frame_from_chunks(
int
process_frame_chain(
RemoteUnwinderObject *unwinder,
uintptr_t initial_frame_addr,
StackChunkList *chunks,
PyObject *frame_info,
uintptr_t base_frame_addr,
uintptr_t gc_frame,
uintptr_t last_profiled_frame,
int *stopped_at_cached_frame,
uintptr_t *frame_addrs, // optional: C array to receive frame addresses
Py_ssize_t *num_addrs, // in/out: current count / updated count
Py_ssize_t max_addrs, // max capacity of frame_addrs array
uintptr_t *out_last_frame_addr) // optional: receives last frame address visited
FrameWalkContext *ctx)
{
uintptr_t frame_addr = initial_frame_addr;
uintptr_t frame_addr = ctx->frame_addr;
uintptr_t prev_frame_addr = 0;
uintptr_t last_frame_addr = 0; // Track last frame visited for validation
uintptr_t last_frame_addr = 0;
const size_t MAX_FRAMES = 1024 + 512;
size_t frame_count = 0;
assert(MAX_FRAMES > 0 && MAX_FRAMES < 10000);
// Initialize output parameters
if (stopped_at_cached_frame) {
*stopped_at_cached_frame = 0;
}
if (out_last_frame_addr) {
*out_last_frame_addr = 0;
}
ctx->stopped_at_cached_frame = 0;
ctx->last_frame_visited = 0;
// Quick check: if current_frame == last_profiled_frame, entire stack is unchanged
if (last_profiled_frame != 0 && initial_frame_addr == last_profiled_frame) {
if (stopped_at_cached_frame) {
*stopped_at_cached_frame = 1;
}
if (ctx->last_profiled_frame != 0 && ctx->frame_addr == ctx->last_profiled_frame) {
ctx->stopped_at_cached_frame = 1;
return 0;
}
while ((void*)frame_addr != NULL) {
// Check if we've reached the cached frame - if so, stop here
if (last_profiled_frame != 0 && frame_addr == last_profiled_frame) {
if (stopped_at_cached_frame) {
*stopped_at_cached_frame = 1;
}
if (ctx->last_profiled_frame != 0 && frame_addr == ctx->last_profiled_frame) {
ctx->stopped_at_cached_frame = 1;
break;
}
PyObject *frame = NULL;
uintptr_t next_frame_addr = 0;
uintptr_t stackpointer = 0;
last_frame_addr = frame_addr; // Remember this frame address
last_frame_addr = frame_addr;
if (++frame_count > MAX_FRAMES) {
PyErr_SetString(PyExc_RuntimeError, "Too many stack frames (possible infinite loop)");
set_exception_cause(unwinder, PyExc_RuntimeError, "Frame chain iteration limit exceeded");
return -1;
}
assert(frame_count <= MAX_FRAMES);
if (parse_frame_from_chunks(unwinder, &frame, frame_addr, &next_frame_addr, &stackpointer, chunks) < 0) {
if (parse_frame_from_chunks(unwinder, &frame, frame_addr, &next_frame_addr, &stackpointer, ctx->chunks) < 0) {
PyErr_Clear();
uintptr_t address_of_code_object = 0;
if (parse_frame_object(unwinder, &frame, frame_addr, &address_of_code_object ,&next_frame_addr) < 0) {
if (parse_frame_object(unwinder, &frame, frame_addr, &address_of_code_object, &next_frame_addr) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in chain");
return -1;
}
}
if (frame == NULL && PyList_GET_SIZE(frame_info) == 0) {
if (frame == NULL && PyList_GET_SIZE(ctx->frame_info) == 0) {
const char *e = "Failed to parse initial frame in chain";
PyErr_SetString(PyExc_RuntimeError, e);
return -1;
}
PyObject *extra_frame = NULL;
// This frame kicked off the current GC collection:
if (unwinder->gc && frame_addr == gc_frame) {
if (unwinder->gc && frame_addr == ctx->gc_frame) {
_Py_DECLARE_STR(gc, "<GC>");
extra_frame = &_Py_STR(gc);
}
// Otherwise, check for native frames to insert:
else if (unwinder->native &&
// We've reached an interpreter trampoline frame:
frame == NULL &&
// Bottommost frame is always native, so skip that one:
next_frame_addr &&
// Only suppress native frames if GC tracking is enabled and the next frame will be a GC frame:
!(unwinder->gc && next_frame_addr == gc_frame))
!(unwinder->gc && next_frame_addr == ctx->gc_frame))
{
_Py_DECLARE_STR(native, "<native>");
extra_frame = &_Py_STR(native);
}
if (extra_frame) {
// Use "~" as file, None as location (synthetic frame), None as opcode
PyObject *extra_frame_info = make_frame_info(
unwinder, _Py_LATIN1_CHR('~'), Py_None, extra_frame, Py_None);
if (extra_frame_info == NULL) {
return -1;
}
if (PyList_Append(frame_info, extra_frame_info) < 0) {
if (PyList_Append(ctx->frame_info, extra_frame_info) < 0) {
Py_DECREF(extra_frame_info);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append extra frame");
return -1;
}
// Extra frames use 0 as address (they're synthetic)
if (frame_addrs && *num_addrs < max_addrs) {
frame_addrs[(*num_addrs)++] = 0;
if (ctx->frame_addrs && ctx->num_addrs < ctx->max_addrs) {
assert(ctx->num_addrs >= 0);
ctx->frame_addrs[ctx->num_addrs++] = 0;
}
Py_DECREF(extra_frame_info);
}
@ -367,14 +355,14 @@ process_frame_chain(
return -1;
}
if (PyList_Append(frame_info, frame) < 0) {
if (PyList_Append(ctx->frame_info, frame) < 0) {
Py_DECREF(frame);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame");
return -1;
}
// Track the address for this frame
if (frame_addrs && *num_addrs < max_addrs) {
frame_addrs[(*num_addrs)++] = frame_addr;
if (ctx->frame_addrs && ctx->num_addrs < ctx->max_addrs) {
assert(ctx->num_addrs >= 0);
ctx->frame_addrs[ctx->num_addrs++] = frame_addr;
}
Py_DECREF(frame);
}
@ -383,21 +371,14 @@ process_frame_chain(
frame_addr = next_frame_addr;
}
// Validate we reached the base frame (sentinel at bottom of stack)
// Only validate if we walked the full chain (didn't stop at cached frame)
// and base_frame_addr is provided (non-zero)
int stopped_early = stopped_at_cached_frame && *stopped_at_cached_frame;
if (!stopped_early && base_frame_addr != 0 && last_frame_addr != base_frame_addr) {
if (!ctx->stopped_at_cached_frame && ctx->base_frame_addr != 0 && last_frame_addr != ctx->base_frame_addr) {
PyErr_Format(PyExc_RuntimeError,
"Incomplete sample: did not reach base frame (expected 0x%lx, got 0x%lx)",
base_frame_addr, last_frame_addr);
ctx->base_frame_addr, last_frame_addr);
return -1;
}
// Set output parameter for caller (needed for cache validation)
if (out_last_frame_addr) {
*out_last_frame_addr = last_frame_addr;
}
ctx->last_frame_visited = last_frame_addr;
return 0;
}
@ -410,8 +391,11 @@ clear_last_profiled_frames(RemoteUnwinderObject *unwinder)
{
uintptr_t current_interp = unwinder->interpreter_addr;
uintptr_t zero = 0;
const size_t MAX_INTERPRETERS = 256;
size_t interp_count = 0;
while (current_interp != 0) {
while (current_interp != 0 && interp_count < MAX_INTERPRETERS) {
interp_count++;
// Get first thread in this interpreter
uintptr_t tstate_addr;
if (_Py_RemoteDebug_PagedReadRemoteMemory(
@ -425,7 +409,10 @@ clear_last_profiled_frames(RemoteUnwinderObject *unwinder)
}
// Iterate all threads in this interpreter
while (tstate_addr != 0) {
const size_t MAX_THREADS_PER_INTERP = 8192;
size_t thread_count = 0;
while (tstate_addr != 0 && thread_count < MAX_THREADS_PER_INTERP) {
thread_count++;
// Clear last_profiled_frame
uintptr_t lpf_addr = tstate_addr + unwinder->debug_offsets.thread_state.last_profiled_frame;
if (_Py_RemoteDebug_WriteRemoteMemory(&unwinder->handle, lpf_addr,
@ -468,16 +455,13 @@ clear_last_profiled_frames(RemoteUnwinderObject *unwinder)
static int
try_full_cache_hit(
RemoteUnwinderObject *unwinder,
uintptr_t frame_addr,
uintptr_t last_profiled_frame,
uint64_t thread_id,
PyObject *frame_info)
const FrameWalkContext *ctx,
uint64_t thread_id)
{
if (!unwinder->frame_cache || last_profiled_frame == 0) {
if (!unwinder->frame_cache || ctx->last_profiled_frame == 0) {
return 0;
}
// Full hit only if current frame == last profiled frame
if (frame_addr != last_profiled_frame) {
if (ctx->frame_addr != ctx->last_profiled_frame) {
return 0;
}
@ -486,22 +470,19 @@ try_full_cache_hit(
return 0;
}
// Verify first address matches (sanity check)
if (entry->num_addrs == 0 || entry->addrs[0] != frame_addr) {
if (entry->num_addrs == 0 || entry->addrs[0] != ctx->frame_addr) {
return 0;
}
// Always read the current frame from memory to get updated line number
PyObject *current_frame = NULL;
uintptr_t code_object_addr = 0;
uintptr_t previous_frame = 0;
int parse_result = parse_frame_object(unwinder, &current_frame, frame_addr,
int parse_result = parse_frame_object(unwinder, &current_frame, ctx->frame_addr,
&code_object_addr, &previous_frame);
if (parse_result < 0) {
return -1;
}
// Get cached parent frames first (before modifying frame_info)
Py_ssize_t cached_size = PyList_GET_SIZE(entry->frame_list);
PyObject *parent_slice = NULL;
if (cached_size > 1) {
@ -512,9 +493,8 @@ try_full_cache_hit(
}
}
// Now safe to modify frame_info - add current frame if valid
if (current_frame != NULL) {
if (PyList_Append(frame_info, current_frame) < 0) {
if (PyList_Append(ctx->frame_info, current_frame) < 0) {
Py_DECREF(current_frame);
Py_XDECREF(parent_slice);
return -1;
@ -523,10 +503,9 @@ try_full_cache_hit(
STATS_ADD(unwinder, frames_read_from_memory, 1);
}
// Extend with cached parent frames
if (parent_slice) {
Py_ssize_t cur_size = PyList_GET_SIZE(frame_info);
int result = PyList_SetSlice(frame_info, cur_size, cur_size, parent_slice);
Py_ssize_t cur_size = PyList_GET_SIZE(ctx->frame_info);
int result = PyList_SetSlice(ctx->frame_info, cur_size, cur_size, parent_slice);
Py_DECREF(parent_slice);
if (result < 0) {
return -1;
@ -543,72 +522,67 @@ try_full_cache_hit(
int
collect_frames_with_cache(
RemoteUnwinderObject *unwinder,
uintptr_t frame_addr,
StackChunkList *chunks,
PyObject *frame_info,
uintptr_t base_frame_addr,
uintptr_t gc_frame,
uintptr_t last_profiled_frame,
FrameWalkContext *ctx,
uint64_t thread_id)
{
// Fast path: check for full cache hit first (no allocations needed)
int full_hit = try_full_cache_hit(unwinder, frame_addr, last_profiled_frame,
thread_id, frame_info);
int full_hit = try_full_cache_hit(unwinder, ctx, thread_id);
if (full_hit != 0) {
return full_hit < 0 ? -1 : 0; // Either error or success
return full_hit < 0 ? -1 : 0;
}
uintptr_t addrs[FRAME_CACHE_MAX_FRAMES];
Py_ssize_t num_addrs = 0;
Py_ssize_t frames_before = PyList_GET_SIZE(frame_info);
uintptr_t last_frame_visited = 0;
Py_ssize_t frames_before = PyList_GET_SIZE(ctx->frame_info);
int stopped_at_cached = 0;
if (process_frame_chain(unwinder, frame_addr, chunks, frame_info, base_frame_addr, gc_frame,
last_profiled_frame, &stopped_at_cached,
addrs, &num_addrs, FRAME_CACHE_MAX_FRAMES,
&last_frame_visited) < 0) {
if (process_frame_chain(unwinder, ctx) < 0) {
return -1;
}
// Track frames read from memory (frames added by process_frame_chain)
STATS_ADD(unwinder, frames_read_from_memory, PyList_GET_SIZE(frame_info) - frames_before);
STATS_ADD(unwinder, frames_read_from_memory, PyList_GET_SIZE(ctx->frame_info) - frames_before);
// If stopped at cached frame, extend with cached continuation (both frames and addresses)
if (stopped_at_cached) {
Py_ssize_t frames_before_cache = PyList_GET_SIZE(frame_info);
int cache_result = frame_cache_lookup_and_extend(unwinder, thread_id, last_profiled_frame,
frame_info, addrs, &num_addrs,
FRAME_CACHE_MAX_FRAMES);
if (ctx->stopped_at_cached_frame) {
Py_ssize_t frames_before_cache = PyList_GET_SIZE(ctx->frame_info);
int cache_result = frame_cache_lookup_and_extend(unwinder, thread_id, ctx->last_profiled_frame,
ctx->frame_info, ctx->frame_addrs, &ctx->num_addrs,
ctx->max_addrs);
if (cache_result < 0) {
return -1;
}
if (cache_result == 0) {
// Cache miss - continue walking from last_profiled_frame to get the rest
STATS_INC(unwinder, frame_cache_misses);
Py_ssize_t frames_before_walk = PyList_GET_SIZE(frame_info);
if (process_frame_chain(unwinder, last_profiled_frame, chunks, frame_info, base_frame_addr, gc_frame,
0, NULL, addrs, &num_addrs, FRAME_CACHE_MAX_FRAMES,
&last_frame_visited) < 0) {
Py_ssize_t frames_before_walk = PyList_GET_SIZE(ctx->frame_info);
FrameWalkContext continue_ctx = {
.frame_addr = ctx->last_profiled_frame,
.base_frame_addr = ctx->base_frame_addr,
.gc_frame = ctx->gc_frame,
.last_profiled_frame = 0,
.chunks = ctx->chunks,
.frame_info = ctx->frame_info,
.frame_addrs = ctx->frame_addrs,
.num_addrs = ctx->num_addrs,
.max_addrs = ctx->max_addrs,
};
if (process_frame_chain(unwinder, &continue_ctx) < 0) {
return -1;
}
STATS_ADD(unwinder, frames_read_from_memory, PyList_GET_SIZE(frame_info) - frames_before_walk);
ctx->num_addrs = continue_ctx.num_addrs;
ctx->last_frame_visited = continue_ctx.last_frame_visited;
STATS_ADD(unwinder, frames_read_from_memory, PyList_GET_SIZE(ctx->frame_info) - frames_before_walk);
} else {
// Partial cache hit - cache was validated when stored, so we trust it
// Partial cache hit - cached stack was validated as complete when stored,
// so set last_frame_visited to base_frame_addr for validation in frame_cache_store
ctx->last_frame_visited = ctx->base_frame_addr;
STATS_INC(unwinder, frame_cache_partial_hits);
STATS_ADD(unwinder, frames_read_from_cache, PyList_GET_SIZE(frame_info) - frames_before_cache);
STATS_ADD(unwinder, frames_read_from_cache, PyList_GET_SIZE(ctx->frame_info) - frames_before_cache);
}
} else {
if (last_profiled_frame == 0) {
// No cache involvement (no last_profiled_frame or cache disabled)
if (ctx->last_profiled_frame == 0) {
STATS_INC(unwinder, frame_cache_misses);
}
}
// Store in cache - frame_cache_store validates internally that we have a
// complete stack (reached base_frame_addr) before actually storing
if (frame_cache_store(unwinder, thread_id, frame_info, addrs, num_addrs,
base_frame_addr, last_frame_visited) < 0) {
if (frame_cache_store(unwinder, thread_id, ctx->frame_info, ctx->frame_addrs, ctx->num_addrs,
ctx->base_frame_addr, ctx->last_frame_visited) < 0) {
return -1;
}

View file

@ -23,6 +23,8 @@ iterate_threads(
) {
uintptr_t thread_state_addr;
unsigned long tid = 0;
const size_t MAX_THREADS = 8192;
size_t thread_count = 0;
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
@ -34,7 +36,8 @@ iterate_threads(
return -1;
}
while (thread_state_addr != 0) {
while (thread_state_addr != 0 && thread_count < MAX_THREADS) {
thread_count++;
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
thread_state_addr + (uintptr_t)unwinder->debug_offsets.thread_state.native_thread_id,
@ -425,12 +428,24 @@ unwind_stack_for_thread(
}
}
uintptr_t addrs[FRAME_CACHE_MAX_FRAMES];
FrameWalkContext ctx = {
.frame_addr = frame_addr,
.base_frame_addr = base_frame_addr,
.gc_frame = gc_frame,
.chunks = &chunks,
.frame_info = frame_info,
.frame_addrs = addrs,
.num_addrs = 0,
.max_addrs = FRAME_CACHE_MAX_FRAMES,
};
assert(ctx.max_addrs == FRAME_CACHE_MAX_FRAMES);
if (unwinder->cache_frames) {
// Use cache to avoid re-reading unchanged parent frames
uintptr_t last_profiled_frame = GET_MEMBER(uintptr_t, ts,
ctx.last_profiled_frame = GET_MEMBER(uintptr_t, ts,
unwinder->debug_offsets.thread_state.last_profiled_frame);
if (collect_frames_with_cache(unwinder, frame_addr, &chunks, frame_info,
base_frame_addr, gc_frame, last_profiled_frame, tid) < 0) {
if (collect_frames_with_cache(unwinder, &ctx, tid) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to collect frames");
goto error;
}
@ -443,8 +458,7 @@ unwind_stack_for_thread(
}
} else {
// No caching - process entire frame chain with base_frame validation
if (process_frame_chain(unwinder, frame_addr, &chunks, frame_info,
base_frame_addr, gc_frame, 0, NULL, NULL, NULL, 0, NULL) < 0) {
if (process_frame_chain(unwinder, &ctx) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process frame chain");
goto error;
}

View file

@ -479,6 +479,42 @@ exit:
return return_value;
}
PyDoc_STRVAR(mmap_mmap_set_name__doc__,
"set_name($self, name, /)\n"
"--\n"
"\n");
#define MMAP_MMAP_SET_NAME_METHODDEF \
{"set_name", (PyCFunction)mmap_mmap_set_name, METH_O, mmap_mmap_set_name__doc__},
static PyObject *
mmap_mmap_set_name_impl(mmap_object *self, const char *name);
static PyObject *
mmap_mmap_set_name(PyObject *self, PyObject *arg)
{
PyObject *return_value = NULL;
const char *name;
if (!PyUnicode_Check(arg)) {
_PyArg_BadArgument("set_name", "argument", "str", arg);
goto exit;
}
Py_ssize_t name_length;
name = PyUnicode_AsUTF8AndSize(arg, &name_length);
if (name == NULL) {
goto exit;
}
if (strlen(name) != (size_t)name_length) {
PyErr_SetString(PyExc_ValueError, "embedded null character");
goto exit;
}
return_value = mmap_mmap_set_name_impl((mmap_object *)self, name);
exit:
return return_value;
}
PyDoc_STRVAR(mmap_mmap_seekable__doc__,
"seekable($self, /)\n"
"--\n"
@ -796,4 +832,4 @@ exit:
#ifndef MMAP_MMAP_MADVISE_METHODDEF
#define MMAP_MMAP_MADVISE_METHODDEF
#endif /* !defined(MMAP_MMAP_MADVISE_METHODDEF) */
/*[clinic end generated code: output=381f6cf4986ac867 input=a9049054013a1b77]*/
/*[clinic end generated code: output=fd9ca0ef425af934 input=a9049054013a1b77]*/

View file

@ -1117,6 +1117,47 @@ mmap_mmap_seek_impl(mmap_object *self, Py_ssize_t dist, int how)
return NULL;
}
/*[clinic input]
mmap.mmap.set_name
name: str
/
[clinic start generated code]*/
static PyObject *
mmap_mmap_set_name_impl(mmap_object *self, const char *name)
/*[clinic end generated code: output=1edaf4fd51277760 input=6c7dd91cad205f07]*/
{
#if defined(MAP_ANONYMOUS) && defined(__linux__)
const char *prefix = "cpython:mmap:";
if (strlen(name) + strlen(prefix) > 79) {
PyErr_SetString(PyExc_ValueError, "name is too long");
return NULL;
}
if (self->flags & MAP_ANONYMOUS) {
char buf[80];
sprintf(buf, "%s%s", prefix, name);
if (_PyAnnotateMemoryMap(self->data, self->size, buf) < 0) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
Py_RETURN_NONE;
}
else {
/* cannot name non-anonymous mappings */
PyErr_SetString(PyExc_ValueError,
"Cannot set annotation on non-anonymous mappings");
return NULL;
}
#else
/* naming not supported on this platform */
PyErr_SetString(PyExc_NotImplementedError,
"Annotation of mmap is not supported on this platform");
return NULL;
#endif
}
/*[clinic input]
mmap.mmap.seekable
@ -1397,6 +1438,7 @@ static struct PyMethodDef mmap_object_methods[] = {
MMAP_MMAP_RESIZE_METHODDEF
MMAP_MMAP_SEEK_METHODDEF
MMAP_MMAP_SEEKABLE_METHODDEF
MMAP_MMAP_SET_NAME_METHODDEF
MMAP_MMAP_SIZE_METHODDEF
MMAP_MMAP_TELL_METHODDEF
MMAP_MMAP_WRITE_METHODDEF
@ -1952,7 +1994,11 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
_PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap");
#ifdef MAP_ANONYMOUS
if (m_obj->flags & MAP_ANONYMOUS) {
(void)_PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap");
}
#endif
m_obj->access = (access_mode)access;
return (PyObject *)m_obj;
}

View file

@ -1815,7 +1815,7 @@ convertenviron(void)
#ifdef MS_WINDOWS
k = PyUnicode_FromWideChar(*e, (Py_ssize_t)(p-*e));
#else
k = PyBytes_FromStringAndSize(*e, (int)(p-*e));
k = PyBytes_FromStringAndSize(*e, (Py_ssize_t)(p-*e));
#endif
if (k == NULL) {
Py_DECREF(d);

View file

@ -90,6 +90,25 @@ bytearray_releasebuffer(PyObject *self, Py_buffer *view)
Py_END_CRITICAL_SECTION();
}
typedef PyObject* (*_ba_bytes_op)(const char *buf, Py_ssize_t len,
PyObject *sub, Py_ssize_t start,
Py_ssize_t end);
static PyObject *
_bytearray_with_buffer(PyByteArrayObject *self, _ba_bytes_op op, PyObject *sub,
Py_ssize_t start, Py_ssize_t end)
{
PyObject *res;
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
/* Increase exports to prevent bytearray storage from changing during op. */
self->ob_exports++;
res = op(PyByteArray_AS_STRING(self), Py_SIZE(self), sub, start, end);
self->ob_exports--;
return res;
}
static int
_canresize(PyByteArrayObject *self)
{
@ -1248,8 +1267,7 @@ bytearray_find_impl(PyByteArrayObject *self, PyObject *sub, Py_ssize_t start,
Py_ssize_t end)
/*[clinic end generated code: output=413e1cab2ae87da0 input=df3aa94840d893a7]*/
{
return _Py_bytes_find(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
sub, start, end);
return _bytearray_with_buffer(self, _Py_bytes_find, sub, start, end);
}
/*[clinic input]
@ -1265,8 +1283,7 @@ bytearray_count_impl(PyByteArrayObject *self, PyObject *sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=a21ee2692e4f1233 input=e8fcdca8272857e0]*/
{
return _Py_bytes_count(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
sub, start, end);
return _bytearray_with_buffer(self, _Py_bytes_count, sub, start, end);
}
/*[clinic input]
@ -1314,8 +1331,7 @@ bytearray_index_impl(PyByteArrayObject *self, PyObject *sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=067a1e78efc672a7 input=c37f177cfee19fe4]*/
{
return _Py_bytes_index(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
sub, start, end);
return _bytearray_with_buffer(self, _Py_bytes_index, sub, start, end);
}
/*[clinic input]
@ -1333,8 +1349,7 @@ bytearray_rfind_impl(PyByteArrayObject *self, PyObject *sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=51bf886f932b283c input=1265b11c437d2750]*/
{
return _Py_bytes_rfind(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
sub, start, end);
return _bytearray_with_buffer(self, _Py_bytes_rfind, sub, start, end);
}
/*[clinic input]
@ -1352,18 +1367,22 @@ bytearray_rindex_impl(PyByteArrayObject *self, PyObject *sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=38e1cf66bafb08b9 input=7d198b3d6b0a62ce]*/
{
return _Py_bytes_rindex(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
sub, start, end);
return _bytearray_with_buffer(self, _Py_bytes_rindex, sub, start, end);
}
static int
bytearray_contains(PyObject *self, PyObject *arg)
{
int ret;
int ret = -1;
Py_BEGIN_CRITICAL_SECTION(self);
ret = _Py_bytes_contains(PyByteArray_AS_STRING(self),
PyByteArrayObject *ba = _PyByteArray_CAST(self);
/* Increase exports to prevent bytearray storage from changing during _Py_bytes_contains(). */
ba->ob_exports++;
ret = _Py_bytes_contains(PyByteArray_AS_STRING(ba),
PyByteArray_GET_SIZE(self),
arg);
ba->ob_exports--;
Py_END_CRITICAL_SECTION();
return ret;
}
@ -1390,8 +1409,7 @@ bytearray_startswith_impl(PyByteArrayObject *self, PyObject *subobj,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=a3d9b6d44d3662a6 input=93f9ffee684f109a]*/
{
return _Py_bytes_startswith(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
subobj, start, end);
return _bytearray_with_buffer(self, _Py_bytes_startswith, subobj, start, end);
}
/*[clinic input]
@ -1416,8 +1434,7 @@ bytearray_endswith_impl(PyByteArrayObject *self, PyObject *subobj,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=e75ea8c227954caa input=d158b030a11d0b06]*/
{
return _Py_bytes_endswith(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
subobj, start, end);
return _bytearray_with_buffer(self, _Py_bytes_endswith, subobj, start, end);
}
/*[clinic input]
@ -1782,26 +1799,32 @@ bytearray_split_impl(PyByteArrayObject *self, PyObject *sep,
Py_ssize_t maxsplit)
/*[clinic end generated code: output=833e2cf385d9a04d input=dd9f6e2910cc3a34]*/
{
Py_ssize_t len = PyByteArray_GET_SIZE(self), n;
const char *s = PyByteArray_AS_STRING(self), *sub;
PyObject *list;
Py_buffer vsub;
PyObject *list = NULL;
/* Increase exports to prevent bytearray storage from changing during _Py_bytes_contains(). */
self->ob_exports++;
const char *sbuf = PyByteArray_AS_STRING(self);
Py_ssize_t slen = PyByteArray_GET_SIZE((PyObject *)self);
if (maxsplit < 0)
maxsplit = PY_SSIZE_T_MAX;
if (sep == Py_None)
return stringlib_split_whitespace((PyObject*) self, s, len, maxsplit);
if (sep == Py_None) {
list = stringlib_split_whitespace((PyObject*)self, sbuf, slen, maxsplit);
goto done;
}
if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0)
return NULL;
sub = vsub.buf;
n = vsub.len;
Py_buffer vsub;
if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) {
goto done;
}
list = stringlib_split(
(PyObject*) self, s, len, sub, n, maxsplit
);
list = stringlib_split((PyObject*)self, sbuf, slen,
(const char *)vsub.buf, vsub.len, maxsplit);
PyBuffer_Release(&vsub);
done:
self->ob_exports--;
return list;
}
@ -1900,26 +1923,32 @@ bytearray_rsplit_impl(PyByteArrayObject *self, PyObject *sep,
Py_ssize_t maxsplit)
/*[clinic end generated code: output=a55e0b5a03cb6190 input=60e9abf305128ff4]*/
{
Py_ssize_t len = PyByteArray_GET_SIZE(self), n;
const char *s = PyByteArray_AS_STRING(self), *sub;
PyObject *list;
Py_buffer vsub;
PyObject *list = NULL;
/* Increase exports to prevent bytearray storage from changing during _Py_bytes_contains(). */
self->ob_exports++;
const char *sbuf = PyByteArray_AS_STRING(self);
Py_ssize_t slen = PyByteArray_GET_SIZE((PyObject *)self);
if (maxsplit < 0)
maxsplit = PY_SSIZE_T_MAX;
if (sep == Py_None)
return stringlib_rsplit_whitespace((PyObject*) self, s, len, maxsplit);
if (sep == Py_None) {
list = stringlib_rsplit_whitespace((PyObject*)self, sbuf, slen, maxsplit);
goto done;
}
if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0)
return NULL;
sub = vsub.buf;
n = vsub.len;
Py_buffer vsub;
if (PyObject_GetBuffer(sep, &vsub, PyBUF_SIMPLE) != 0) {
goto done;
}
list = stringlib_rsplit(
(PyObject*) self, s, len, sub, n, maxsplit
);
list = stringlib_rsplit((PyObject*)self, sbuf, slen,
(const char *)vsub.buf, vsub.len, maxsplit);
PyBuffer_Release(&vsub);
done:
self->ob_exports--;
return list;
}

150
Objects/clinic/rangeobject.c.h generated Normal file
View file

@ -0,0 +1,150 @@
/*[clinic input]
preserve
[clinic start generated code]*/
#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
PyDoc_STRVAR(range_iterator___length_hint____doc__,
"__length_hint__($self, /)\n"
"--\n"
"\n"
"Private method returning an estimate of len(list(it)).");
#define RANGE_ITERATOR___LENGTH_HINT___METHODDEF \
{"__length_hint__", (PyCFunction)range_iterator___length_hint__, METH_NOARGS, range_iterator___length_hint____doc__},
static PyObject *
range_iterator___length_hint___impl(_PyRangeIterObject *r);
static PyObject *
range_iterator___length_hint__(PyObject *r, PyObject *Py_UNUSED(ignored))
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(r);
return_value = range_iterator___length_hint___impl((_PyRangeIterObject *)r);
Py_END_CRITICAL_SECTION();
return return_value;
}
PyDoc_STRVAR(range_iterator___reduce____doc__,
"__reduce__($self, /)\n"
"--\n"
"\n"
"Return state information for pickling.");
#define RANGE_ITERATOR___REDUCE___METHODDEF \
{"__reduce__", (PyCFunction)range_iterator___reduce__, METH_NOARGS, range_iterator___reduce____doc__},
static PyObject *
range_iterator___reduce___impl(_PyRangeIterObject *r);
static PyObject *
range_iterator___reduce__(PyObject *r, PyObject *Py_UNUSED(ignored))
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(r);
return_value = range_iterator___reduce___impl((_PyRangeIterObject *)r);
Py_END_CRITICAL_SECTION();
return return_value;
}
PyDoc_STRVAR(range_iterator___setstate____doc__,
"__setstate__($self, state, /)\n"
"--\n"
"\n"
"Set state information for unpickling.");
#define RANGE_ITERATOR___SETSTATE___METHODDEF \
{"__setstate__", (PyCFunction)range_iterator___setstate__, METH_O, range_iterator___setstate____doc__},
static PyObject *
range_iterator___setstate___impl(_PyRangeIterObject *r, PyObject *state);
static PyObject *
range_iterator___setstate__(PyObject *r, PyObject *state)
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(r);
return_value = range_iterator___setstate___impl((_PyRangeIterObject *)r, state);
Py_END_CRITICAL_SECTION();
return return_value;
}
PyDoc_STRVAR(longrange_iterator___length_hint____doc__,
"__length_hint__($self, /)\n"
"--\n"
"\n"
"Private method returning an estimate of len(list(it)).");
#define LONGRANGE_ITERATOR___LENGTH_HINT___METHODDEF \
{"__length_hint__", (PyCFunction)longrange_iterator___length_hint__, METH_NOARGS, longrange_iterator___length_hint____doc__},
static PyObject *
longrange_iterator___length_hint___impl(longrangeiterobject *r);
static PyObject *
longrange_iterator___length_hint__(PyObject *r, PyObject *Py_UNUSED(ignored))
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(r);
return_value = longrange_iterator___length_hint___impl((longrangeiterobject *)r);
Py_END_CRITICAL_SECTION();
return return_value;
}
PyDoc_STRVAR(longrange_iterator___reduce____doc__,
"__reduce__($self, /)\n"
"--\n"
"\n"
"Return state information for pickling.");
#define LONGRANGE_ITERATOR___REDUCE___METHODDEF \
{"__reduce__", (PyCFunction)longrange_iterator___reduce__, METH_NOARGS, longrange_iterator___reduce____doc__},
static PyObject *
longrange_iterator___reduce___impl(longrangeiterobject *r);
static PyObject *
longrange_iterator___reduce__(PyObject *r, PyObject *Py_UNUSED(ignored))
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(r);
return_value = longrange_iterator___reduce___impl((longrangeiterobject *)r);
Py_END_CRITICAL_SECTION();
return return_value;
}
PyDoc_STRVAR(longrange_iterator___setstate____doc__,
"__setstate__($self, state, /)\n"
"--\n"
"\n"
"Set state information for unpickling.");
#define LONGRANGE_ITERATOR___SETSTATE___METHODDEF \
{"__setstate__", (PyCFunction)longrange_iterator___setstate__, METH_O, longrange_iterator___setstate____doc__},
static PyObject *
longrange_iterator___setstate___impl(longrangeiterobject *r, PyObject *state);
static PyObject *
longrange_iterator___setstate__(PyObject *r, PyObject *state)
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(r);
return_value = longrange_iterator___setstate___impl((longrangeiterobject *)r, state);
Py_END_CRITICAL_SECTION();
return return_value;
}
/*[clinic end generated code: output=719c0e4c81fe0f4a input=a9049054013a1b77]*/

View file

@ -1771,7 +1771,7 @@ insertion_resize(PyDictObject *mp, int unicode)
}
static inline int
insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
insert_combined_dict(PyDictObject *mp,
Py_hash_t hash, PyObject *key, PyObject *value)
{
// gh-140551: If dict was cleared in _Py_dict_lookup,
@ -1789,7 +1789,7 @@ insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
}
}
_PyDict_NotifyEvent(interp, PyDict_EVENT_ADDED, mp, key, value);
_PyDict_NotifyEvent(PyDict_EVENT_ADDED, mp, key, value);
FT_ATOMIC_STORE_UINT32_RELAXED(mp->ma_keys->dk_version, 0);
Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
@ -1846,19 +1846,19 @@ insert_split_key(PyDictKeysObject *keys, PyObject *key, Py_hash_t hash)
}
static void
insert_split_value(PyInterpreterState *interp, PyDictObject *mp, PyObject *key, PyObject *value, Py_ssize_t ix)
insert_split_value(PyDictObject *mp, PyObject *key, PyObject *value, Py_ssize_t ix)
{
assert(PyUnicode_CheckExact(key));
ASSERT_DICT_LOCKED(mp);
PyObject *old_value = mp->ma_values->values[ix];
if (old_value == NULL) {
_PyDict_NotifyEvent(interp, PyDict_EVENT_ADDED, mp, key, value);
_PyDict_NotifyEvent(PyDict_EVENT_ADDED, mp, key, value);
STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value));
_PyDictValues_AddToInsertionOrder(mp->ma_values, ix);
STORE_USED(mp, mp->ma_used + 1);
}
else {
_PyDict_NotifyEvent(interp, PyDict_EVENT_MODIFIED, mp, key, value);
_PyDict_NotifyEvent(PyDict_EVENT_MODIFIED, mp, key, value);
STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value));
// old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
// when dict only holds the strong reference to value in ep->me_value.
@ -1874,7 +1874,7 @@ Returns -1 if an error occurred, or 0 on success.
Consumes key and value references.
*/
static int
insertdict(PyInterpreterState *interp, PyDictObject *mp,
insertdict(PyDictObject *mp,
PyObject *key, Py_hash_t hash, PyObject *value)
{
PyObject *old_value;
@ -1885,7 +1885,7 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) {
ix = insert_split_key(mp->ma_keys, key, hash);
if (ix != DKIX_EMPTY) {
insert_split_value(interp, mp, key, value, ix);
insert_split_value(mp, key, value, ix);
Py_DECREF(key);
Py_DECREF(value);
return 0;
@ -1903,7 +1903,7 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
// into DICT_KEYS_GENERAL table if key is not Unicode.
// We don't convert it before _Py_dict_lookup because non-Unicode key
// may change generic table into Unicode table.
if (insert_combined_dict(interp, mp, hash, key, value) < 0) {
if (insert_combined_dict(mp, hash, key, value) < 0) {
goto Fail;
}
STORE_USED(mp, mp->ma_used + 1);
@ -1912,7 +1912,7 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
}
if (old_value != value) {
_PyDict_NotifyEvent(interp, PyDict_EVENT_MODIFIED, mp, key, value);
_PyDict_NotifyEvent(PyDict_EVENT_MODIFIED, mp, key, value);
assert(old_value != NULL);
if (DK_IS_UNICODE(mp->ma_keys)) {
if (_PyDict_HasSplitTable(mp)) {
@ -1942,7 +1942,7 @@ Fail:
// Same as insertdict but specialized for ma_keys == Py_EMPTY_KEYS.
// Consumes key and value references.
static int
insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp,
insert_to_emptydict(PyDictObject *mp,
PyObject *key, Py_hash_t hash, PyObject *value)
{
assert(mp->ma_keys == Py_EMPTY_KEYS);
@ -1955,7 +1955,7 @@ insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp,
Py_DECREF(value);
return -1;
}
_PyDict_NotifyEvent(interp, PyDict_EVENT_ADDED, mp, key, value);
_PyDict_NotifyEvent(PyDict_EVENT_ADDED, mp, key, value);
/* We don't decref Py_EMPTY_KEYS here because it is immortal. */
assert(mp->ma_values == NULL);
@ -2658,13 +2658,11 @@ setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
return -1;
}
PyInterpreterState *interp = _PyInterpreterState_GET();
if (mp->ma_keys == Py_EMPTY_KEYS) {
return insert_to_emptydict(interp, mp, key, hash, value);
return insert_to_emptydict(mp, key, hash, value);
}
/* insertdict() handles any resizing that might be necessary */
return insertdict(interp, mp, key, hash, value);
return insertdict(mp, key, hash, value);
}
int
@ -2710,12 +2708,11 @@ int
_PyDict_SetItem_KnownHash_LockHeld(PyDictObject *mp, PyObject *key, PyObject *value,
Py_hash_t hash)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
if (mp->ma_keys == Py_EMPTY_KEYS) {
return insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
return insert_to_emptydict(mp, Py_NewRef(key), hash, Py_NewRef(value));
}
/* insertdict() handles any resizing that might be necessary */
return insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value));
return insertdict(mp, Py_NewRef(key), hash, Py_NewRef(value));
}
int
@ -2836,8 +2833,7 @@ _PyDict_DelItem_KnownHash_LockHeld(PyObject *op, PyObject *key, Py_hash_t hash)
return -1;
}
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
_PyDict_NotifyEvent(PyDict_EVENT_DELETED, mp, key, NULL);
delitem_common(mp, hash, ix, old_value);
return 0;
}
@ -2883,8 +2879,7 @@ delitemif_lock_held(PyObject *op, PyObject *key,
return -1;
if (res > 0) {
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
_PyDict_NotifyEvent(PyDict_EVENT_DELETED, mp, key, NULL);
delitem_common(mp, hash, ix, old_value);
return 1;
} else {
@ -2928,8 +2923,7 @@ clear_lock_held(PyObject *op)
return;
}
/* Empty the dict... */
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyDict_NotifyEvent(interp, PyDict_EVENT_CLEARED, mp, NULL, NULL);
_PyDict_NotifyEvent(PyDict_EVENT_CLEARED, mp, NULL, NULL);
// We don't inc ref empty keys because they're immortal
ensure_shared_on_resize(mp);
STORE_USED(mp, 0);
@ -3095,8 +3089,7 @@ _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash,
}
assert(old_value != NULL);
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
_PyDict_NotifyEvent(PyDict_EVENT_DELETED, mp, key, NULL);
delitem_common(mp, hash, ix, Py_NewRef(old_value));
ASSERT_CONSISTENT(mp);
@ -3191,8 +3184,7 @@ _PyDict_Pop(PyObject *dict, PyObject *key, PyObject *default_value)
}
static PyDictObject *
dict_dict_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
PyObject *iterable, PyObject *value)
dict_dict_fromkeys(PyDictObject *mp, PyObject *iterable, PyObject *value)
{
PyObject *oldvalue;
Py_ssize_t pos = 0;
@ -3208,8 +3200,7 @@ dict_dict_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
}
while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) {
if (insertdict(interp, mp,
Py_NewRef(key), hash, Py_NewRef(value))) {
if (insertdict(mp, Py_NewRef(key), hash, Py_NewRef(value))) {
Py_DECREF(mp);
return NULL;
}
@ -3218,8 +3209,7 @@ dict_dict_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
}
static PyDictObject *
dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
PyObject *iterable, PyObject *value)
dict_set_fromkeys(PyDictObject *mp, PyObject *iterable, PyObject *value)
{
Py_ssize_t pos = 0;
PyObject *key;
@ -3234,7 +3224,7 @@ dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(iterable);
while (_PySet_NextEntryRef(iterable, &pos, &key, &hash)) {
if (insertdict(interp, mp, key, hash, Py_NewRef(value))) {
if (insertdict(mp, key, hash, Py_NewRef(value))) {
Py_DECREF(mp);
return NULL;
}
@ -3250,7 +3240,6 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
PyObject *key;
PyObject *d;
int status;
PyInterpreterState *interp = _PyInterpreterState_GET();
d = _PyObject_CallNoArgs(cls);
if (d == NULL)
@ -3262,7 +3251,7 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
PyDictObject *mp = (PyDictObject *)d;
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
d = (PyObject *)dict_dict_fromkeys(interp, mp, iterable, value);
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
Py_END_CRITICAL_SECTION2();
return d;
}
@ -3270,7 +3259,7 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
PyDictObject *mp = (PyDictObject *)d;
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
d = (PyObject *)dict_set_fromkeys(interp, mp, iterable, value);
d = (PyObject *)dict_set_fromkeys(mp, iterable, value);
Py_END_CRITICAL_SECTION2();
return d;
}
@ -3320,9 +3309,8 @@ static void
dict_dealloc(PyObject *self)
{
PyDictObject *mp = (PyDictObject *)self;
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyObject_ResurrectStart(self);
_PyDict_NotifyEvent(interp, PyDict_EVENT_DEALLOCATED, mp, NULL, NULL);
_PyDict_NotifyEvent(PyDict_EVENT_DEALLOCATED, mp, NULL, NULL);
if (_PyObject_ResurrectEnd(self)) {
return;
}
@ -3844,7 +3832,7 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
}
static int
dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *other, int override)
dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override)
{
ASSERT_DICT_LOCKED(mp);
ASSERT_DICT_LOCKED(other);
@ -3867,7 +3855,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
(DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE ||
USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used)
) {
_PyDict_NotifyEvent(interp, PyDict_EVENT_CLONED, mp, (PyObject *)other, NULL);
_PyDict_NotifyEvent(PyDict_EVENT_CLONED, mp, (PyObject *)other, NULL);
PyDictKeysObject *keys = clone_combined_dict_keys(other);
if (keys == NULL)
return -1;
@ -3908,14 +3896,12 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
Py_INCREF(key);
Py_INCREF(value);
if (override == 1) {
err = insertdict(interp, mp,
Py_NewRef(key), hash, Py_NewRef(value));
err = insertdict(mp, Py_NewRef(key), hash, Py_NewRef(value));
}
else {
err = _PyDict_Contains_KnownHash((PyObject *)mp, key, hash);
if (err == 0) {
err = insertdict(interp, mp,
Py_NewRef(key), hash, Py_NewRef(value));
err = insertdict(mp, Py_NewRef(key), hash, Py_NewRef(value));
}
else if (err > 0) {
if (override != 0) {
@ -3942,7 +3928,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
}
static int
dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override)
dict_merge(PyObject *a, PyObject *b, int override)
{
PyDictObject *mp, *other;
@ -3963,7 +3949,7 @@ dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override)
other = (PyDictObject*)b;
int res;
Py_BEGIN_CRITICAL_SECTION2(a, b);
res = dict_dict_merge(interp, (PyDictObject *)a, other, override);
res = dict_dict_merge((PyDictObject *)a, other, override);
ASSERT_CONSISTENT(a);
Py_END_CRITICAL_SECTION2();
return res;
@ -4044,23 +4030,20 @@ slow_exit:
int
PyDict_Update(PyObject *a, PyObject *b)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
return dict_merge(interp, a, b, 1);
return dict_merge(a, b, 1);
}
int
PyDict_Merge(PyObject *a, PyObject *b, int override)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
/* XXX Deprecate override not in (0, 1). */
return dict_merge(interp, a, b, override != 0);
return dict_merge(a, b, override != 0);
}
int
_PyDict_MergeEx(PyObject *a, PyObject *b, int override)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
return dict_merge(interp, a, b, override);
return dict_merge(a, b, override);
}
/*[clinic input]
@ -4102,7 +4085,6 @@ copy_lock_held(PyObject *o)
{
PyObject *copy;
PyDictObject *mp;
PyInterpreterState *interp = _PyInterpreterState_GET();
ASSERT_DICT_LOCKED(o);
@ -4172,7 +4154,7 @@ copy_lock_held(PyObject *o)
copy = PyDict_New();
if (copy == NULL)
return NULL;
if (dict_merge(interp, copy, o, 1) == 0)
if (dict_merge(copy, o, 1) == 0)
return copy;
Py_DECREF(copy);
return NULL;
@ -4367,7 +4349,6 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
PyObject *value;
Py_hash_t hash;
Py_ssize_t ix;
PyInterpreterState *interp = _PyInterpreterState_GET();
ASSERT_DICT_LOCKED(d);
@ -4389,7 +4370,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
}
if (mp->ma_keys == Py_EMPTY_KEYS) {
if (insert_to_emptydict(interp, mp, Py_NewRef(key), hash,
if (insert_to_emptydict(mp, Py_NewRef(key), hash,
Py_NewRef(default_value)) < 0) {
if (result) {
*result = NULL;
@ -4408,7 +4389,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
PyObject *value = mp->ma_values->values[ix];
int already_present = value != NULL;
if (!already_present) {
insert_split_value(interp, mp, key, default_value, ix);
insert_split_value(mp, key, default_value, ix);
value = default_value;
}
if (result) {
@ -4432,7 +4413,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
value = default_value;
// See comment to this function in insertdict.
if (insert_combined_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) {
if (insert_combined_dict(mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) {
Py_DECREF(key);
Py_DECREF(value);
if (result) {
@ -4554,7 +4535,6 @@ dict_popitem_impl(PyDictObject *self)
{
Py_ssize_t i, j;
PyObject *res;
PyInterpreterState *interp = _PyInterpreterState_GET();
ASSERT_DICT_LOCKED(self);
@ -4596,7 +4576,7 @@ dict_popitem_impl(PyDictObject *self)
assert(i >= 0);
key = ep0[i].me_key;
_PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, self, key, NULL);
_PyDict_NotifyEvent(PyDict_EVENT_DELETED, self, key, NULL);
hash = unicode_get_hash(key);
value = ep0[i].me_value;
STORE_KEY(&ep0[i], NULL);
@ -4611,7 +4591,7 @@ dict_popitem_impl(PyDictObject *self)
assert(i >= 0);
key = ep0[i].me_key;
_PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, self, key, NULL);
_PyDict_NotifyEvent(PyDict_EVENT_DELETED, self, key, NULL);
hash = ep0[i].me_hash;
value = ep0[i].me_value;
STORE_KEY(&ep0[i], NULL);
@ -6925,11 +6905,10 @@ store_instance_attr_lock_held(PyObject *obj, PyDictValues *values,
}
if (dict) {
PyInterpreterState *interp = _PyInterpreterState_GET();
PyDict_WatchEvent event = (old_value == NULL ? PyDict_EVENT_ADDED :
value == NULL ? PyDict_EVENT_DELETED :
PyDict_EVENT_MODIFIED);
_PyDict_NotifyEvent(interp, event, dict, name, value);
_PyDict_NotifyEvent(event, dict, name, value);
}
FT_ATOMIC_STORE_PTR_RELEASE(values->values[ix], Py_XNewRef(value));

View file

@ -36,6 +36,14 @@ static PyObject* async_gen_athrow_new(PyAsyncGenObject *, PyObject *);
#define _PyAsyncGenObject_CAST(op) \
_Py_CAST(PyAsyncGenObject*, (op))
#ifdef Py_GIL_DISABLED
# define _Py_GEN_TRY_SET_FRAME_STATE(gen, expected, state) \
_Py_atomic_compare_exchange_int8(&(gen)->gi_frame_state, &expected, (state))
#else
# define _Py_GEN_TRY_SET_FRAME_STATE(gen, expected, state) \
((gen)->gi_frame_state = (state), true)
#endif
static const char *NON_INIT_CORO_MSG = "can't send non-None value to a "
"just-started coroutine";
@ -145,10 +153,7 @@ _PyGen_Finalize(PyObject *self)
static void
gen_clear_frame(PyGenObject *gen)
{
if (gen->gi_frame_state == FRAME_CLEARED)
return;
gen->gi_frame_state = FRAME_CLEARED;
assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_CLEARED);
_PyInterpreterFrame *frame = &gen->gi_iframe;
frame->previous = NULL;
_PyFrame_ClearExceptCode(frame);
@ -179,7 +184,10 @@ gen_dealloc(PyObject *self)
if (PyCoro_CheckExact(gen)) {
Py_CLEAR(((PyCoroObject *)gen)->cr_origin_or_finalizer);
}
gen_clear_frame(gen);
if (gen->gi_frame_state != FRAME_CLEARED) {
gen->gi_frame_state = FRAME_CLEARED;
gen_clear_frame(gen);
}
assert(gen->gi_exc_state.exc_value == NULL);
PyStackRef_CLEAR(gen->gi_iframe.f_executable);
Py_CLEAR(gen->gi_name);
@ -188,59 +196,32 @@ gen_dealloc(PyObject *self)
PyObject_GC_Del(gen);
}
static PySendResult
gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
int exc, int closing)
static void
gen_raise_already_executing_error(PyGenObject *gen)
{
const char *msg = "generator already executing";
if (PyCoro_CheckExact(gen)) {
msg = "coroutine already executing";
}
else if (PyAsyncGen_CheckExact(gen)) {
msg = "async generator already executing";
}
PyErr_SetString(PyExc_ValueError, msg);
}
// Send 'arg' into 'gen'. On success, return PYGEN_NEXT or PYGEN_RETURN.
// Returns PYGEN_ERROR on failure. 'presult' is set to the yielded or
// returned value.
// The generator must be in the FRAME_EXECUTING state when this function
// is called.
static PySendResult
gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc)
{
assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_EXECUTING);
PyThreadState *tstate = _PyThreadState_GET();
_PyInterpreterFrame *frame = &gen->gi_iframe;
*presult = NULL;
if (gen->gi_frame_state == FRAME_CREATED && arg && arg != Py_None) {
const char *msg = "can't send non-None value to a "
"just-started generator";
if (PyCoro_CheckExact(gen)) {
msg = NON_INIT_CORO_MSG;
}
else if (PyAsyncGen_CheckExact(gen)) {
msg = "can't send non-None value to a "
"just-started async generator";
}
PyErr_SetString(PyExc_TypeError, msg);
return PYGEN_ERROR;
}
if (gen->gi_frame_state == FRAME_EXECUTING) {
const char *msg = "generator already executing";
if (PyCoro_CheckExact(gen)) {
msg = "coroutine already executing";
}
else if (PyAsyncGen_CheckExact(gen)) {
msg = "async generator already executing";
}
PyErr_SetString(PyExc_ValueError, msg);
return PYGEN_ERROR;
}
if (FRAME_STATE_FINISHED(gen->gi_frame_state)) {
if (PyCoro_CheckExact(gen) && !closing) {
/* `gen` is an exhausted coroutine: raise an error,
except when called from gen_close(), which should
always be a silent method. */
PyErr_SetString(
PyExc_RuntimeError,
"cannot reuse already awaited coroutine");
}
else if (arg && !exc) {
/* `gen` is an exhausted generator:
only return value if called from send(). */
*presult = Py_NewRef(Py_None);
return PYGEN_RETURN;
}
return PYGEN_ERROR;
}
assert((gen->gi_frame_state == FRAME_CREATED) ||
FRAME_STATE_SUSPENDED(gen->gi_frame_state));
/* Push arg onto the frame's value stack */
PyObject *arg_obj = arg ? arg : Py_None;
_PyFrame_StackPush(frame, PyStackRef_FromPyObjectNew(arg_obj));
@ -254,21 +235,34 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
_PyErr_ChainStackItem();
}
gen->gi_frame_state = FRAME_EXECUTING;
EVAL_CALL_STAT_INC(EVAL_CALL_GENERATOR);
PyObject *result = _PyEval_EvalFrame(tstate, frame, exc);
assert(tstate->exc_info == prev_exc_info);
#ifndef Py_GIL_DISABLED
assert(gen->gi_exc_state.previous_item == NULL);
assert(gen->gi_frame_state != FRAME_EXECUTING);
assert(frame->previous == NULL);
assert(gen->gi_frame_state != FRAME_EXECUTING);
#endif
// The generator_return_kind field is used to distinguish between a
// yield and a return from within _PyEval_EvalFrame(). Earlier versions
// of CPython (prior to 3.15) used gi_frame_state for this purpose, but
// that requires the GIL for thread-safety.
int return_kind = ((_PyThreadStateImpl *)tstate)->generator_return_kind;
if (return_kind == GENERATOR_YIELD) {
assert(result != NULL && !_PyErr_Occurred(tstate));
*presult = result;
return PYGEN_NEXT;
}
assert(return_kind == GENERATOR_RETURN);
assert(gen->gi_exc_state.exc_value == NULL);
assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_CLEARED);
/* If the generator just returned (as opposed to yielding), signal
* that the generator is exhausted. */
if (result) {
if (FRAME_STATE_SUSPENDED(gen->gi_frame_state)) {
*presult = result;
return PYGEN_NEXT;
}
assert(result == Py_None || !PyAsyncGen_CheckExact(gen));
if (result == Py_None && !PyAsyncGen_CheckExact(gen) && !arg) {
/* Return NULL if called by gen_iternext() */
@ -281,37 +275,82 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
!PyErr_ExceptionMatches(PyExc_StopAsyncIteration));
}
assert(gen->gi_exc_state.exc_value == NULL);
assert(gen->gi_frame_state == FRAME_CLEARED);
*presult = result;
return result ? PYGEN_RETURN : PYGEN_ERROR;
}
// Set the generator 'gen' to the executing state and send 'arg' into it.
// See gen_send_ex2() for details.
static PySendResult
gen_send_ex(PyGenObject *gen, PyObject *arg, PyObject **presult)
{
*presult = NULL;
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
do {
if (frame_state == FRAME_CREATED && arg && arg != Py_None) {
const char *msg = "can't send non-None value to a "
"just-started generator";
if (PyCoro_CheckExact(gen)) {
msg = NON_INIT_CORO_MSG;
}
else if (PyAsyncGen_CheckExact(gen)) {
msg = "can't send non-None value to a "
"just-started async generator";
}
PyErr_SetString(PyExc_TypeError, msg);
return PYGEN_ERROR;
}
if (frame_state == FRAME_EXECUTING) {
gen_raise_already_executing_error(gen);
return PYGEN_ERROR;
}
if (FRAME_STATE_FINISHED(frame_state)) {
if (PyCoro_CheckExact(gen)) {
/* `gen` is an exhausted coroutine: raise an error,
except when called from gen_close(), which should
always be a silent method. */
PyErr_SetString(
PyExc_RuntimeError,
"cannot reuse already awaited coroutine");
}
else if (arg) {
/* `gen` is an exhausted generator:
only return value if called from send(). */
*presult = Py_None;
return PYGEN_RETURN;
}
return PYGEN_ERROR;
}
assert((frame_state == FRAME_CREATED) ||
FRAME_STATE_SUSPENDED(frame_state));
} while (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_EXECUTING));
return gen_send_ex2(gen, arg, presult, 0);
}
static PySendResult
PyGen_am_send(PyObject *self, PyObject *arg, PyObject **result)
{
PyGenObject *gen = _PyGen_CAST(self);
return gen_send_ex2(gen, arg, result, 0, 0);
return gen_send_ex(gen, arg, result);
}
static PyObject *
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
gen_set_stop_iteration(PyGenObject *gen, PyObject *result)
{
PyObject *result;
if (gen_send_ex2(gen, arg, &result, exc, closing) == PYGEN_RETURN) {
if (PyAsyncGen_CheckExact(gen)) {
assert(result == Py_None);
PyErr_SetNone(PyExc_StopAsyncIteration);
}
else if (result == Py_None) {
PyErr_SetNone(PyExc_StopIteration);
}
else {
_PyGen_SetStopIterationValue(result);
}
Py_CLEAR(result);
if (PyAsyncGen_CheckExact(gen)) {
assert(result == Py_None);
PyErr_SetNone(PyExc_StopAsyncIteration);
}
return result;
else if (result == Py_None) {
PyErr_SetNone(PyExc_StopIteration);
}
else {
_PyGen_SetStopIterationValue(result);
}
Py_DECREF(result);
return NULL;
}
PyDoc_STRVAR(send_doc,
@ -319,9 +358,14 @@ PyDoc_STRVAR(send_doc,
return next yielded value or raise StopIteration.");
static PyObject *
gen_send(PyObject *gen, PyObject *arg)
gen_send(PyObject *op, PyObject *arg)
{
return gen_send_ex((PyGenObject*)gen, arg, 0, 0);
PyObject *result;
PyGenObject *gen = _PyGen_CAST(op);
if (gen_send_ex(gen, arg, &result) == PYGEN_RETURN) {
return gen_set_stop_iteration(gen, result);
}
return result;
}
PyDoc_STRVAR(close_doc,
@ -370,42 +414,44 @@ is_resume(_Py_CODEUNIT *instr)
);
}
PyObject *
_PyGen_yf(PyGenObject *gen)
{
if (gen->gi_frame_state == FRAME_SUSPENDED_YIELD_FROM) {
_PyInterpreterFrame *frame = &gen->gi_iframe;
// GH-122390: These asserts are wrong in the presence of ENTER_EXECUTOR!
// assert(is_resume(frame->instr_ptr));
// assert((frame->instr_ptr->op.arg & RESUME_OPARG_LOCATION_MASK) >= RESUME_AFTER_YIELD_FROM);
return PyStackRef_AsPyObjectNew(_PyFrame_StackPeek(frame));
}
return NULL;
}
static PyObject *
gen_close(PyObject *self, PyObject *args)
{
PyGenObject *gen = _PyGen_CAST(self);
if (gen->gi_frame_state == FRAME_CREATED) {
gen->gi_frame_state = FRAME_COMPLETED;
Py_RETURN_NONE;
}
if (FRAME_STATE_FINISHED(gen->gi_frame_state)) {
Py_RETURN_NONE;
}
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
do {
if (frame_state == FRAME_CREATED) {
// && (1) to avoid -Wunreachable-code warning on Clang
if (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_CLEARED) && (1)) {
continue;
}
gen_clear_frame(gen);
Py_RETURN_NONE;
}
if (FRAME_STATE_FINISHED(frame_state)) {
Py_RETURN_NONE;
}
if (frame_state == FRAME_EXECUTING) {
gen_raise_already_executing_error(gen);
return NULL;
}
assert(frame_state == FRAME_SUSPENDED_YIELD_FROM ||
frame_state == FRAME_SUSPENDED);
} while (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_EXECUTING));
PyObject *yf = _PyGen_yf(gen);
int err = 0;
if (yf) {
PyFrameState state = gen->gi_frame_state;
gen->gi_frame_state = FRAME_EXECUTING;
_PyInterpreterFrame *frame = &gen->gi_iframe;
if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
PyObject *yf = PyStackRef_AsPyObjectNew(_PyFrame_StackPeek(frame));
err = gen_close_iter(yf);
gen->gi_frame_state = state;
Py_DECREF(yf);
}
_PyInterpreterFrame *frame = &gen->gi_iframe;
if (is_resume(frame->instr_ptr)) {
bool no_unwind_tools = _PyEval_NoToolsForUnwind(_PyThreadState_GET());
/* We can safely ignore the outermost try block
@ -415,7 +461,7 @@ gen_close(PyObject *self, PyObject *args)
if (oparg & RESUME_OPARG_DEPTH1_MASK && no_unwind_tools) {
// RESUME after YIELD_VALUE and exception depth is 1
assert((oparg & RESUME_OPARG_LOCATION_MASK) != RESUME_AT_FUNC_START);
gen->gi_frame_state = FRAME_COMPLETED;
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_CLEARED);
gen_clear_frame(gen);
Py_RETURN_NONE;
}
@ -424,8 +470,13 @@ gen_close(PyObject *self, PyObject *args)
PyErr_SetNone(PyExc_GeneratorExit);
}
PyObject *retval = gen_send_ex(gen, Py_None, 1, 1);
if (retval) {
PyObject *retval;
if (gen_send_ex2(gen, Py_None, &retval, 1) == PYGEN_RETURN) {
// the generator returned a value while closing, return the value here
assert(!PyErr_Occurred());
return retval;
}
else if (retval) {
const char *msg = "generator ignored GeneratorExit";
if (PyCoro_CheckExact(gen)) {
msg = "coroutine ignored GeneratorExit";
@ -442,102 +493,14 @@ gen_close(PyObject *self, PyObject *args)
PyErr_Clear(); /* ignore this error */
Py_RETURN_NONE;
}
/* if the generator returned a value while closing, StopIteration was
* raised in gen_send_ex() above; retrieve and return the value here */
if (_PyGen_FetchStopIterationValue(&retval) == 0) {
return retval;
}
return NULL;
}
PyDoc_STRVAR(throw_doc,
"throw(value)\n\
throw(type[,value[,tb]])\n\
\n\
Raise exception in generator, return next yielded value or raise\n\
StopIteration.\n\
the (type, val, tb) signature is deprecated, \n\
and may be removed in a future version of Python.");
static PyObject *
_gen_throw(PyGenObject *gen, int close_on_genexit,
PyObject *typ, PyObject *val, PyObject *tb)
// Set an exception for a gen.throw() call.
// Return 0 on success, -1 on failure.
static int
gen_set_exception(PyObject *typ, PyObject *val, PyObject *tb)
{
PyObject *yf = _PyGen_yf(gen);
if (yf) {
_PyInterpreterFrame *frame = &gen->gi_iframe;
PyObject *ret;
int err;
if (PyErr_GivenExceptionMatches(typ, PyExc_GeneratorExit) &&
close_on_genexit
) {
/* Asynchronous generators *should not* be closed right away.
We have to allow some awaits to work it through, hence the
`close_on_genexit` parameter here.
*/
PyFrameState state = gen->gi_frame_state;
gen->gi_frame_state = FRAME_EXECUTING;
err = gen_close_iter(yf);
gen->gi_frame_state = state;
Py_DECREF(yf);
if (err < 0)
return gen_send_ex(gen, Py_None, 1, 0);
goto throw_here;
}
PyThreadState *tstate = _PyThreadState_GET();
assert(tstate != NULL);
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
/* `yf` is a generator or a coroutine. */
/* Link frame into the stack to enable complete backtraces. */
/* XXX We should probably be updating the current frame somewhere in
ceval.c. */
_PyInterpreterFrame *prev = tstate->current_frame;
frame->previous = prev;
tstate->current_frame = frame;
/* Close the generator that we are currently iterating with
'yield from' or awaiting on with 'await'. */
PyFrameState state = gen->gi_frame_state;
gen->gi_frame_state = FRAME_EXECUTING;
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
typ, val, tb);
gen->gi_frame_state = state;
tstate->current_frame = prev;
frame->previous = NULL;
} else {
/* `yf` is an iterator or a coroutine-like object. */
PyObject *meth;
if (PyObject_GetOptionalAttr(yf, &_Py_ID(throw), &meth) < 0) {
Py_DECREF(yf);
return NULL;
}
if (meth == NULL) {
Py_DECREF(yf);
goto throw_here;
}
_PyInterpreterFrame *prev = tstate->current_frame;
frame->previous = prev;
tstate->current_frame = frame;
PyFrameState state = gen->gi_frame_state;
gen->gi_frame_state = FRAME_EXECUTING;
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
gen->gi_frame_state = state;
tstate->current_frame = prev;
frame->previous = NULL;
Py_DECREF(meth);
}
Py_DECREF(yf);
if (!ret) {
ret = gen_send_ex(gen, Py_None, 1, 0);
}
return ret;
}
throw_here:
/* First, check the traceback argument, replacing None with
NULL. */
if (tb == Py_None) {
@ -546,16 +509,16 @@ throw_here:
else if (tb != NULL && !PyTraceBack_Check(tb)) {
PyErr_SetString(PyExc_TypeError,
"throw() third argument must be a traceback object");
return NULL;
return -1;
}
Py_INCREF(typ);
Py_XINCREF(val);
Py_XINCREF(tb);
if (PyExceptionClass_Check(typ))
if (PyExceptionClass_Check(typ)) {
PyErr_NormalizeException(&typ, &val, &tb);
}
else if (PyExceptionInstance_Check(typ)) {
/* Raising an instance. The value should be a dummy. */
if (val && val != Py_None) {
@ -583,14 +546,137 @@ throw_here:
}
PyErr_Restore(typ, val, tb);
return gen_send_ex(gen, Py_None, 1, 0);
return 0;
failed_throw:
/* Didn't use our arguments, so restore their original refcounts */
Py_DECREF(typ);
Py_XDECREF(val);
Py_XDECREF(tb);
return NULL;
return -1;
}
static PyObject *
gen_throw_current_exception(PyGenObject *gen)
{
assert(gen->gi_frame_state == FRAME_EXECUTING);
PyObject *result;
if (gen_send_ex2(gen, Py_None, &result, 1) == PYGEN_RETURN) {
return gen_set_stop_iteration(gen, result);
}
return result;
}
PyDoc_STRVAR(throw_doc,
"throw(value)\n\
throw(type[,value[,tb]])\n\
\n\
Raise exception in generator, return next yielded value or raise\n\
StopIteration.\n\
the (type, val, tb) signature is deprecated, \n\
and may be removed in a future version of Python.");
static PyObject *
_gen_throw(PyGenObject *gen, int close_on_genexit,
PyObject *typ, PyObject *val, PyObject *tb)
{
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
do {
if (frame_state == FRAME_EXECUTING) {
gen_raise_already_executing_error(gen);
return NULL;
}
if (FRAME_STATE_FINISHED(frame_state)) {
if (PyCoro_CheckExact(gen)) {
/* `gen` is an exhausted coroutine: raise an error */
PyErr_SetString(
PyExc_RuntimeError,
"cannot reuse already awaited coroutine");
return NULL;
}
gen_set_exception(typ, val, tb);
return NULL;
}
assert((frame_state == FRAME_CREATED) ||
FRAME_STATE_SUSPENDED(frame_state));
} while (!_Py_GEN_TRY_SET_FRAME_STATE(gen, frame_state, FRAME_EXECUTING));
if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
_PyInterpreterFrame *frame = &gen->gi_iframe;
PyObject *yf = PyStackRef_AsPyObjectNew(_PyFrame_StackPeek(frame));
PyObject *ret;
int err;
if (PyErr_GivenExceptionMatches(typ, PyExc_GeneratorExit) &&
close_on_genexit
) {
/* Asynchronous generators *should not* be closed right away.
We have to allow some awaits to work it through, hence the
`close_on_genexit` parameter here.
*/
err = gen_close_iter(yf);
Py_DECREF(yf);
if (err < 0) {
return gen_throw_current_exception(gen);
}
goto throw_here;
}
PyThreadState *tstate = _PyThreadState_GET();
assert(tstate != NULL);
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
/* `yf` is a generator or a coroutine. */
/* Link frame into the stack to enable complete backtraces. */
/* XXX We should probably be updating the current frame somewhere in
ceval.c. */
_PyInterpreterFrame *prev = tstate->current_frame;
frame->previous = prev;
tstate->current_frame = frame;
/* Close the generator that we are currently iterating with
'yield from' or awaiting on with 'await'. */
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
typ, val, tb);
tstate->current_frame = prev;
frame->previous = NULL;
}
else {
/* `yf` is an iterator or a coroutine-like object. */
PyObject *meth;
if (PyObject_GetOptionalAttr(yf, &_Py_ID(throw), &meth) < 0) {
Py_DECREF(yf);
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, frame_state);
return NULL;
}
if (meth == NULL) {
Py_DECREF(yf);
goto throw_here;
}
_PyInterpreterFrame *prev = tstate->current_frame;
frame->previous = prev;
tstate->current_frame = frame;
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
tstate->current_frame = prev;
frame->previous = NULL;
Py_DECREF(meth);
}
Py_DECREF(yf);
if (!ret) {
return gen_throw_current_exception(gen);
}
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, frame_state);
return ret;
}
throw_here:
assert(FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state) == FRAME_EXECUTING);
if (gen_set_exception(typ, val, tb) < 0) {
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, frame_state);
return NULL;
}
return gen_throw_current_exception(gen);
}
@ -632,7 +718,7 @@ gen_iternext(PyObject *self)
PyGenObject *gen = _PyGen_CAST(self);
PyObject *result;
if (gen_send_ex2(gen, NULL, &result, 0, 0) == PYGEN_RETURN) {
if (gen_send_ex(gen, NULL, &result) == PYGEN_RETURN) {
if (result != Py_None) {
_PyGen_SetStopIterationValue(result);
}
@ -756,13 +842,15 @@ gen_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
}
static PyObject *
gen_getyieldfrom(PyObject *gen, void *Py_UNUSED(ignored))
gen_getyieldfrom(PyObject *self, void *Py_UNUSED(ignored))
{
PyObject *yf = _PyGen_yf(_PyGen_CAST(gen));
if (yf == NULL) {
PyGenObject *gen = _PyGen_CAST(self);
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
if (frame_state != FRAME_SUSPENDED_YIELD_FROM) {
Py_RETURN_NONE;
}
return yf;
// TODO: still not thread-safe with free threading
return PyStackRef_AsPyObjectNew(_PyFrame_StackPeek(&gen->gi_iframe));
}
@ -770,17 +858,16 @@ static PyObject *
gen_getrunning(PyObject *self, void *Py_UNUSED(ignored))
{
PyGenObject *gen = _PyGen_CAST(self);
if (gen->gi_frame_state == FRAME_EXECUTING) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
return frame_state == FRAME_EXECUTING ? Py_True : Py_False;
}
static PyObject *
gen_getsuspended(PyObject *self, void *Py_UNUSED(ignored))
{
PyGenObject *gen = _PyGen_CAST(self);
return PyBool_FromLong(FRAME_STATE_SUSPENDED(gen->gi_frame_state));
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
return FRAME_STATE_SUSPENDED(frame_state) ? Py_True : Py_False;
}
static PyObject *
@ -789,9 +876,11 @@ _gen_getframe(PyGenObject *gen, const char *const name)
if (PySys_Audit("object.__getattr__", "Os", gen, name) < 0) {
return NULL;
}
if (FRAME_STATE_FINISHED(gen->gi_frame_state)) {
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
if (FRAME_STATE_FINISHED(frame_state)) {
Py_RETURN_NONE;
}
// TODO: still not thread-safe with free threading
return _Py_XNewRef((PyObject *)_PyFrame_GetFrameObject(&gen->gi_iframe));
}
@ -1134,35 +1223,6 @@ coro_await(PyObject *coro)
return (PyObject *)cw;
}
static PyObject *
coro_get_cr_await(PyObject *coro, void *Py_UNUSED(ignored))
{
PyObject *yf = _PyGen_yf((PyGenObject *) coro);
if (yf == NULL)
Py_RETURN_NONE;
return yf;
}
static PyObject *
cr_getsuspended(PyObject *self, void *Py_UNUSED(ignored))
{
PyCoroObject *coro = _PyCoroObject_CAST(self);
if (FRAME_STATE_SUSPENDED(coro->cr_frame_state)) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
static PyObject *
cr_getrunning(PyObject *self, void *Py_UNUSED(ignored))
{
PyCoroObject *coro = _PyCoroObject_CAST(self);
if (coro->cr_frame_state == FRAME_EXECUTING) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
static PyObject *
cr_getframe(PyObject *coro, void *Py_UNUSED(ignored))
{
@ -1180,12 +1240,12 @@ static PyGetSetDef coro_getsetlist[] = {
PyDoc_STR("name of the coroutine")},
{"__qualname__", gen_get_qualname, gen_set_qualname,
PyDoc_STR("qualified name of the coroutine")},
{"cr_await", coro_get_cr_await, NULL,
{"cr_await", gen_getyieldfrom, NULL,
PyDoc_STR("object being awaited on, or None")},
{"cr_running", cr_getrunning, NULL, NULL},
{"cr_running", gen_getrunning, NULL, NULL},
{"cr_frame", cr_getframe, NULL, NULL},
{"cr_code", cr_getcode, NULL, NULL},
{"cr_suspended", cr_getsuspended, NULL, NULL},
{"cr_suspended", gen_getsuspended, NULL, NULL},
{NULL} /* Sentinel */
};
@ -1601,26 +1661,16 @@ ag_getcode(PyObject *gen, void *Py_UNUSED(ignored))
return _gen_getcode((PyGenObject*)gen, "ag_code");
}
static PyObject *
ag_getsuspended(PyObject *self, void *Py_UNUSED(ignored))
{
PyAsyncGenObject *ag = _PyAsyncGenObject_CAST(self);
if (FRAME_STATE_SUSPENDED(ag->ag_frame_state)) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
static PyGetSetDef async_gen_getsetlist[] = {
{"__name__", gen_get_name, gen_set_name,
PyDoc_STR("name of the async generator")},
{"__qualname__", gen_get_qualname, gen_set_qualname,
PyDoc_STR("qualified name of the async generator")},
{"ag_await", coro_get_cr_await, NULL,
{"ag_await", gen_getyieldfrom, NULL,
PyDoc_STR("object being awaited on, or None")},
{"ag_frame", ag_getframe, NULL, NULL},
{"ag_code", ag_getcode, NULL, NULL},
{"ag_suspended", ag_getsuspended, NULL, NULL},
{"ag_suspended", gen_getsuspended, NULL, NULL},
{NULL} /* Sentinel */
};

View file

@ -96,11 +96,7 @@ ensure_shared_on_resize(PyListObject *self)
* of the new slots at exit is undefined heap trash; it's the caller's
* responsibility to overwrite them with sane values.
* The number of allocated elements may grow, shrink, or stay the same.
* Failure is impossible if newsize <= self.allocated on entry, although
* that partly relies on an assumption that the system realloc() never
* fails when passed a number of bytes <= the number of bytes last
* allocated (the C standard doesn't guarantee this, but it's hard to
* imagine a realloc implementation where it wouldn't be true).
* Failure is impossible if newsize <= self.allocated on entry.
* Note that self->ob_item may change, and even if newsize is less
* than ob_size on entry.
*/
@ -145,6 +141,11 @@ list_resize(PyListObject *self, Py_ssize_t newsize)
#ifdef Py_GIL_DISABLED
_PyListArray *array = list_allocate_array(new_allocated);
if (array == NULL) {
if (newsize < allocated) {
// Never fail when shrinking allocations
Py_SET_SIZE(self, newsize);
return 0;
}
PyErr_NoMemory();
return -1;
}
@ -178,6 +179,11 @@ list_resize(PyListObject *self, Py_ssize_t newsize)
items = NULL;
}
if (items == NULL) {
if (newsize < allocated) {
// Never fail when shrinking allocations
Py_SET_SIZE(self, newsize);
return 0;
}
PyErr_NoMemory();
return -1;
}
@ -818,8 +824,8 @@ list_repeat_lock_held(PyListObject *a, Py_ssize_t n)
_Py_RefcntAdd(*src, n);
*dest++ = *src++;
}
// TODO: _Py_memory_repeat calls are not safe for shared lists in
// GIL_DISABLED builds. (See issue #129069)
// This list is not yet visible to other threads, so atomic repeat
// is not necessary even in Py_GIL_DISABLED builds.
_Py_memory_repeat((char *)np->ob_item, sizeof(PyObject *)*output_size,
sizeof(PyObject *)*input_size);
}
@ -882,6 +888,34 @@ list_clear_slot(PyObject *self)
return 0;
}
// Pointer-by-pointer memmove for PyObject** arrays that is safe
// for shared lists in Py_GIL_DISABLED builds.
static void
ptr_wise_atomic_memmove(PyListObject *a, PyObject **dest, PyObject **src, Py_ssize_t n)
{
#ifndef Py_GIL_DISABLED
memmove(dest, src, n * sizeof(PyObject *));
#else
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(a);
if (_Py_IsOwnedByCurrentThread((PyObject *)a) && !_PyObject_GC_IS_SHARED(a)) {
// No other threads can read this list concurrently
memmove(dest, src, n * sizeof(PyObject *));
return;
}
if (dest < src) {
for (Py_ssize_t i = 0; i != n; i++) {
_Py_atomic_store_ptr_release(&dest[i], src[i]);
}
}
else {
// copy backwards to avoid overwriting src before it's read
for (Py_ssize_t i = n; i != 0; i--) {
_Py_atomic_store_ptr_release(&dest[i - 1], src[i - 1]);
}
}
#endif
}
/* a[ilow:ihigh] = v if v != NULL.
* del a[ilow:ihigh] if v == NULL.
*
@ -952,16 +986,9 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO
}
if (d < 0) { /* Delete -d items */
Py_ssize_t tail;
tail = (Py_SIZE(a) - ihigh) * sizeof(PyObject *);
// TODO: these memmove/memcpy calls are not safe for shared lists in
// GIL_DISABLED builds. (See issue #129069)
memmove(&item[ihigh+d], &item[ihigh], tail);
if (list_resize(a, Py_SIZE(a) + d) < 0) {
memmove(&item[ihigh], &item[ihigh+d], tail);
memcpy(&item[ilow], recycle, s);
goto Error;
}
Py_ssize_t tail = Py_SIZE(a) - ihigh;
ptr_wise_atomic_memmove(a, &item[ihigh+d], &item[ihigh], tail);
(void)list_resize(a, Py_SIZE(a) + d); // NB: shrinking a list can't fail
item = a->ob_item;
}
else if (d > 0) { /* Insert d items */
@ -969,10 +996,7 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO
if (list_resize(a, k+d) < 0)
goto Error;
item = a->ob_item;
// TODO: these memmove/memcpy calls are not safe for shared lists in
// GIL_DISABLED builds. (See issue #129069)
memmove(&item[ihigh+d], &item[ihigh],
(k - ihigh)*sizeof(PyObject *));
ptr_wise_atomic_memmove(a, &item[ihigh+d], &item[ihigh], k - ihigh);
}
for (k = 0; k < n; k++, ilow++) {
PyObject *w = vitem[k];
@ -1056,10 +1080,17 @@ list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n)
for (Py_ssize_t j = 0; j < input_size; j++) {
_Py_RefcntAdd(items[j], n-1);
}
// TODO: _Py_memory_repeat calls are not safe for shared lists in
// GIL_DISABLED builds. (See issue #129069)
#ifndef Py_GIL_DISABLED
_Py_memory_repeat((char *)items, sizeof(PyObject *)*output_size,
sizeof(PyObject *)*input_size);
#else
Py_ssize_t copied = input_size;
while (copied < output_size) {
Py_ssize_t items_to_copy = Py_MIN(copied, output_size - copied);
ptr_wise_atomic_memmove(self, items + copied, items, items_to_copy);
copied += items_to_copy;
}
#endif
return 0;
}
@ -1532,7 +1563,6 @@ list_pop_impl(PyListObject *self, Py_ssize_t index)
/*[clinic end generated code: output=6bd69dcb3f17eca8 input=c269141068ae4b8f]*/
{
PyObject *v;
int status;
if (Py_SIZE(self) == 0) {
/* Special-case most common failure cause */
@ -1548,27 +1578,18 @@ list_pop_impl(PyListObject *self, Py_ssize_t index)
PyObject **items = self->ob_item;
v = items[index];
const Py_ssize_t size_after_pop = Py_SIZE(self) - 1;
if (size_after_pop == 0) {
if (Py_SIZE(self) == 1) {
Py_INCREF(v);
list_clear(self);
status = 0;
return v;
}
else {
if ((size_after_pop - index) > 0) {
memmove(&items[index], &items[index+1], (size_after_pop - index) * sizeof(PyObject *));
}
status = list_resize(self, size_after_pop);
}
if (status >= 0) {
return v; // and v now owns the reference the list had
}
else {
// list resize failed, need to restore
memmove(&items[index+1], &items[index], (size_after_pop - index)* sizeof(PyObject *));
items[index] = v;
return NULL;
Py_ssize_t size_after_pop = Py_SIZE(self) - 1;
if (index < size_after_pop) {
ptr_wise_atomic_memmove(self, &items[index], &items[index+1],
size_after_pop - index);
}
list_resize(self, size_after_pop); // NB: shrinking a list can't fail
return v;
}
/* Reverse a slice of a list in place, from lo up to (exclusive) hi. */

View file

@ -468,7 +468,7 @@ _PyMem_ArenaAlloc(void *Py_UNUSED(ctx), size_t size)
if (ptr == MAP_FAILED)
return NULL;
assert(ptr != NULL);
_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc");
(void)_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc");
return ptr;
#else
return malloc(size);

View file

@ -9,6 +9,21 @@
#include "pycore_range.h"
#include "pycore_tuple.h" // _PyTuple_ITEMS()
typedef struct {
PyObject_HEAD
PyObject *start;
PyObject *step;
PyObject *len;
} longrangeiterobject;
/*[clinic input]
class range_iterator "_PyRangeIterObject *" "&PyRangeIter_Type"
class longrange_iterator "longrangeiterobject *" "&PyLongRangeIter_Type"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=c7d97a63d1cfa6b3]*/
#include "clinic/rangeobject.c.h"
/* Support objects whose length is > PY_SSIZE_T_MAX.
@ -830,30 +845,46 @@ PyTypeObject PyRange_Type = {
static PyObject *
rangeiter_next(PyObject *op)
{
PyObject *ret = NULL;
Py_BEGIN_CRITICAL_SECTION(op);
_PyRangeIterObject *r = (_PyRangeIterObject*)op;
if (r->len > 0) {
long result = r->start;
r->start = result + r->step;
r->len--;
return PyLong_FromLong(result);
ret = PyLong_FromLong(result);
}
return NULL;
Py_END_CRITICAL_SECTION();
return ret;
}
/*[clinic input]
@critical_section
range_iterator.__length_hint__
self as r: self(type="_PyRangeIterObject *")
Private method returning an estimate of len(list(it)).
[clinic start generated code]*/
static PyObject *
rangeiter_len(PyObject *op, PyObject *Py_UNUSED(ignored))
range_iterator___length_hint___impl(_PyRangeIterObject *r)
/*[clinic end generated code: output=9ba6f22b1fc23dcc input=e3eb311e99d76e43]*/
{
_PyRangeIterObject *r = (_PyRangeIterObject*)op;
return PyLong_FromLong(r->len);
}
PyDoc_STRVAR(length_hint_doc,
"Private method returning an estimate of len(list(it)).");
/*[clinic input]
@critical_section
range_iterator.__reduce__
self as r: self(type="_PyRangeIterObject *")
Return state information for pickling.
[clinic start generated code]*/
static PyObject *
rangeiter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
range_iterator___reduce___impl(_PyRangeIterObject *r)
/*[clinic end generated code: output=c44d53750c388415 input=75a25b7076dc2c54]*/
{
_PyRangeIterObject *r = (_PyRangeIterObject*)op;
PyObject *start=NULL, *stop=NULL, *step=NULL;
PyObject *range;
@ -881,10 +912,20 @@ err:
return NULL;
}
/*[clinic input]
@critical_section
range_iterator.__setstate__
self as r: self(type="_PyRangeIterObject *")
state: object
/
Set state information for unpickling.
[clinic start generated code]*/
static PyObject *
rangeiter_setstate(PyObject *op, PyObject *state)
range_iterator___setstate___impl(_PyRangeIterObject *r, PyObject *state)
/*[clinic end generated code: output=464b3cbafc2e3562 input=c8c84fab2519d200]*/
{
_PyRangeIterObject *r = (_PyRangeIterObject*)op;
long index = PyLong_AsLong(state);
if (index == -1 && PyErr_Occurred())
return NULL;
@ -904,13 +945,10 @@ rangeiter_dealloc(PyObject *self)
_Py_FREELIST_FREE(range_iters, (_PyRangeIterObject *)self, PyObject_Free);
}
PyDoc_STRVAR(reduce_doc, "Return state information for pickling.");
PyDoc_STRVAR(setstate_doc, "Set state information for unpickling.");
static PyMethodDef rangeiter_methods[] = {
{"__length_hint__", rangeiter_len, METH_NOARGS, length_hint_doc},
{"__reduce__", rangeiter_reduce, METH_NOARGS, reduce_doc},
{"__setstate__", rangeiter_setstate, METH_O, setstate_doc},
RANGE_ITERATOR___LENGTH_HINT___METHODDEF
RANGE_ITERATOR___REDUCE___METHODDEF
RANGE_ITERATOR___SETSTATE___METHODDEF
{NULL, NULL} /* sentinel */
};
@ -995,25 +1033,34 @@ fast_range_iter(long start, long stop, long step, long len)
return (PyObject *)it;
}
typedef struct {
PyObject_HEAD
PyObject *start;
PyObject *step;
PyObject *len;
} longrangeiterobject;
/*[clinic input]
@critical_section
longrange_iterator.__length_hint__
self as r: self(type="longrangeiterobject *")
Private method returning an estimate of len(list(it)).
[clinic start generated code]*/
static PyObject *
longrangeiter_len(PyObject *op, PyObject *Py_UNUSED(ignored))
longrange_iterator___length_hint___impl(longrangeiterobject *r)
/*[clinic end generated code: output=e1bce24da7e8bfde input=ba94b050d940411e]*/
{
longrangeiterobject *r = (longrangeiterobject*)op;
Py_INCREF(r->len);
return r->len;
}
/*[clinic input]
@critical_section
longrange_iterator.__reduce__
self as r: self(type="longrangeiterobject *")
Return state information for pickling.
[clinic start generated code]*/
static PyObject *
longrangeiter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
longrange_iterator___reduce___impl(longrangeiterobject *r)
/*[clinic end generated code: output=0077f94ae2a4e99a input=2e8930e897ace086]*/
{
longrangeiterobject *r = (longrangeiterobject*)op;
PyObject *product, *stop=NULL;
PyObject *range;
@ -1039,15 +1086,25 @@ longrangeiter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
range, Py_None);
}
/*[clinic input]
@critical_section
longrange_iterator.__setstate__
self as r: self(type="longrangeiterobject *")
state: object
/
Set state information for unpickling.
[clinic start generated code]*/
static PyObject *
longrangeiter_setstate(PyObject *op, PyObject *state)
longrange_iterator___setstate___impl(longrangeiterobject *r, PyObject *state)
/*[clinic end generated code: output=870787f0574f0da4 input=8b116de3018de824]*/
{
if (!PyLong_CheckExact(state)) {
PyErr_Format(PyExc_TypeError, "state must be an int, not %T", state);
return NULL;
}
longrangeiterobject *r = (longrangeiterobject*)op;
PyObject *zero = _PyLong_GetZero(); // borrowed reference
int cmp;
@ -1085,9 +1142,9 @@ longrangeiter_setstate(PyObject *op, PyObject *state)
}
static PyMethodDef longrangeiter_methods[] = {
{"__length_hint__", longrangeiter_len, METH_NOARGS, length_hint_doc},
{"__reduce__", longrangeiter_reduce, METH_NOARGS, reduce_doc},
{"__setstate__", longrangeiter_setstate, METH_O, setstate_doc},
LONGRANGE_ITERATOR___LENGTH_HINT___METHODDEF
LONGRANGE_ITERATOR___REDUCE___METHODDEF
LONGRANGE_ITERATOR___SETSTATE___METHODDEF
{NULL, NULL} /* sentinel */
};
@ -1102,7 +1159,7 @@ longrangeiter_dealloc(PyObject *op)
}
static PyObject *
longrangeiter_next(PyObject *op)
longrangeiter_next_lock_held(PyObject *op)
{
longrangeiterobject *r = (longrangeiterobject*)op;
if (PyObject_RichCompareBool(r->len, _PyLong_GetZero(), Py_GT) != 1)
@ -1123,6 +1180,16 @@ longrangeiter_next(PyObject *op)
return result;
}
static PyObject *
longrangeiter_next(PyObject *op)
{
PyObject *result;
Py_BEGIN_CRITICAL_SECTION(op);
result = longrangeiter_next_lock_held(op);
Py_END_CRITICAL_SECTION();
return result;
}
PyTypeObject PyLongRangeIter_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"longrange_iterator", /* tp_name */

View file

@ -3,9 +3,8 @@
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_parser.h" // _PYPEGEN_NSTATISTICS
#include "pycore_pyerrors.h" // PyExc_IncompleteInputError
#include "pycore_runtime.h" // _PyRuntime
#include "pycore_runtime.h" // _PyRuntime
#include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal
#include "pycore_pyatomic_ft_wrappers.h"
#include <errcode.h>
#include "lexer/lexer.h"
@ -303,11 +302,11 @@ error:
void
_PyPegen_clear_memo_statistics(void)
{
FT_MUTEX_LOCK(&_PyRuntime.parser.mutex);
PyMutex_Lock(&_PyRuntime.parser.mutex);
for (int i = 0; i < NSTATISTICS; i++) {
memo_statistics[i] = 0;
}
FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
PyMutex_Unlock(&_PyRuntime.parser.mutex);
}
PyObject *
@ -318,22 +317,22 @@ _PyPegen_get_memo_statistics(void)
return NULL;
}
FT_MUTEX_LOCK(&_PyRuntime.parser.mutex);
PyMutex_Lock(&_PyRuntime.parser.mutex);
for (int i = 0; i < NSTATISTICS; i++) {
PyObject *value = PyLong_FromLong(memo_statistics[i]);
if (value == NULL) {
FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
PyMutex_Unlock(&_PyRuntime.parser.mutex);
Py_DECREF(ret);
return NULL;
}
// PyList_SetItem borrows a reference to value.
if (PyList_SetItem(ret, i, value) < 0) {
FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
PyMutex_Unlock(&_PyRuntime.parser.mutex);
Py_DECREF(ret);
return NULL;
}
}
FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
PyMutex_Unlock(&_PyRuntime.parser.mutex);
return ret;
}
#endif
@ -359,9 +358,9 @@ _PyPegen_is_memoized(Parser *p, int type, void *pres)
if (count <= 0) {
count = 1;
}
FT_MUTEX_LOCK(&_PyRuntime.parser.mutex);
PyMutex_Lock(&_PyRuntime.parser.mutex);
memo_statistics[type] += count;
FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
PyMutex_Unlock(&_PyRuntime.parser.mutex);
}
#endif
p->mark = m->mark;

View file

@ -742,7 +742,7 @@ dummy_func(
macro(BINARY_OP_SUBTRACT_FLOAT) =
_GUARD_TOS_FLOAT + _GUARD_NOS_FLOAT + unused/5 + _BINARY_OP_SUBTRACT_FLOAT + _POP_TOP_FLOAT + _POP_TOP_FLOAT;
pure op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) {
pure op(_BINARY_OP_ADD_UNICODE, (left, right -- res, l, r)) {
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyUnicode_CheckExact(left_o));
@ -750,15 +750,17 @@ dummy_func(
STAT_INC(BINARY_OP, hit);
PyObject *res_o = PyUnicode_Concat(left_o, right_o);
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
INPUTS_DEAD();
ERROR_IF(res_o == NULL);
res = PyStackRef_FromPyObjectSteal(res_o);
if (PyStackRef_IsNull(res)) {
ERROR_NO_POP();
}
l = left;
r = right;
INPUTS_DEAD();
}
macro(BINARY_OP_ADD_UNICODE) =
_GUARD_TOS_UNICODE + _GUARD_NOS_UNICODE + unused/5 + _BINARY_OP_ADD_UNICODE;
_GUARD_TOS_UNICODE + _GUARD_NOS_UNICODE + unused/5 + _BINARY_OP_ADD_UNICODE + _POP_TOP_UNICODE + _POP_TOP_UNICODE;
// This is a subtle one. It's a super-instruction for
// BINARY_OP_ADD_UNICODE followed by STORE_FAST
@ -891,9 +893,9 @@ dummy_func(
macro(STORE_SLICE) = _SPECIALIZE_STORE_SLICE + _STORE_SLICE;
macro(BINARY_OP_SUBSCR_LIST_INT) =
_GUARD_TOS_INT + _GUARD_NOS_LIST + unused/5 + _BINARY_OP_SUBSCR_LIST_INT;
_GUARD_TOS_INT + _GUARD_NOS_LIST + unused/5 + _BINARY_OP_SUBSCR_LIST_INT + _POP_TOP_INT + POP_TOP;
op(_BINARY_OP_SUBSCR_LIST_INT, (list_st, sub_st -- res)) {
op(_BINARY_OP_SUBSCR_LIST_INT, (list_st, sub_st -- res, ls, ss)) {
PyObject *sub = PyStackRef_AsPyObjectBorrow(sub_st);
PyObject *list = PyStackRef_AsPyObjectBorrow(list_st);
@ -916,7 +918,9 @@ dummy_func(
res = PyStackRef_FromPyObjectNew(res_o);
#endif
STAT_INC(BINARY_OP, hit);
DECREF_INPUTS();
ls = list_st;
ss = sub_st;
INPUTS_DEAD();
}
macro(BINARY_OP_SUBSCR_LIST_SLICE) =
@ -937,9 +941,9 @@ dummy_func(
}
macro(BINARY_OP_SUBSCR_STR_INT) =
_GUARD_TOS_INT + _GUARD_NOS_UNICODE + unused/5 + _BINARY_OP_SUBSCR_STR_INT;
_GUARD_TOS_INT + _GUARD_NOS_UNICODE + unused/5 + _BINARY_OP_SUBSCR_STR_INT + _POP_TOP_INT + POP_TOP;
op(_BINARY_OP_SUBSCR_STR_INT, (str_st, sub_st -- res)) {
op(_BINARY_OP_SUBSCR_STR_INT, (str_st, sub_st -- res, s, i)) {
PyObject *sub = PyStackRef_AsPyObjectBorrow(sub_st);
PyObject *str = PyStackRef_AsPyObjectBorrow(str_st);
@ -954,9 +958,9 @@ dummy_func(
assert(c < 128);
STAT_INC(BINARY_OP, hit);
PyObject *res_o = (PyObject*)&_Py_SINGLETON(strings).ascii[c];
PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
DEAD(sub_st);
PyStackRef_CLOSE(str_st);
INPUTS_DEAD();
s = str_st;
i = sub_st;
res = PyStackRef_FromPyObjectBorrow(res_o);
}
@ -1308,14 +1312,13 @@ dummy_func(
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
if ((tstate->interp->eval_frame == NULL) &&
(Py_TYPE(receiver_o) == &PyGen_Type || Py_TYPE(receiver_o) == &PyCoro_Type) &&
((PyGenObject *)receiver_o)->gi_frame_state < FRAME_EXECUTING)
gen_try_set_executing((PyGenObject *)receiver_o))
{
PyGenObject *gen = (PyGenObject *)receiver_o;
_PyInterpreterFrame *gen_frame = &gen->gi_iframe;
_PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v));
DEAD(v);
SYNC_SP();
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
assert(INSTRUCTION_SIZE + oparg <= UINT16_MAX);
@ -1356,12 +1359,11 @@ dummy_func(
op(_SEND_GEN_FRAME, (receiver, v -- receiver, gen_frame)) {
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(receiver);
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type && Py_TYPE(gen) != &PyCoro_Type);
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING);
DEOPT_IF(!gen_try_set_executing((PyGenObject *)gen));
STAT_INC(SEND, hit);
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
_PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
DEAD(v);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
assert(INSTRUCTION_SIZE + oparg <= UINT16_MAX);
@ -1385,7 +1387,6 @@ dummy_func(
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
assert(oparg == 0 || oparg == 1);
gen->gi_frame_state = FRAME_SUSPENDED + oparg;
_PyStackRef temp = retval;
DEAD(retval);
SAVE_STACK();
@ -1395,6 +1396,8 @@ dummy_func(
_PyInterpreterFrame *gen_frame = frame;
frame = tstate->current_frame = frame->previous;
gen_frame->previous = NULL;
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_SUSPENDED + oparg);
/* We don't know which of these is relevant here, so keep them equal */
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
#if TIER_ONE
@ -2626,7 +2629,7 @@ dummy_func(
UNLOCK_OBJECT(dict);
DEOPT_IF(true);
}
_PyDict_NotifyEvent(tstate->interp, PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
_PyDict_NotifyEvent(PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value));
UNLOCK_OBJECT(dict);
@ -3401,18 +3404,10 @@ dummy_func(
op(_FOR_ITER_GEN_FRAME, (iter, null -- iter, null, gen_frame)) {
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type);
#ifdef Py_GIL_DISABLED
// Since generators can't be used by multiple threads anyway we
// don't need to deopt here, but this lets us work on making
// generators thread-safe without necessarily having to
// specialize them thread-safely as well.
DEOPT_IF(!_PyObject_IsUniquelyReferenced((PyObject *)gen));
#endif
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING);
DEOPT_IF(!gen_try_set_executing((PyGenObject *)gen));
STAT_INC(FOR_ITER, hit);
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
pushed_frame->previous = frame;
@ -5281,6 +5276,13 @@ dummy_func(
value = PyStackRef_FromPyObjectBorrow(ptr);
}
tier2 op(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, arg -- res, a, c)) {
res = PyStackRef_FromPyObjectBorrow(ptr);
a = arg;
c = callable;
INPUTS_DEAD();
}
tier2 op(_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, pop1, pop2 -- value)) {
PyStackRef_CLOSE(pop2);
PyStackRef_CLOSE(pop1);

View file

@ -1601,7 +1601,7 @@ early_exit:
}
#ifdef _Py_TIER2
#ifdef _Py_JIT
_PyJitEntryFuncPtr _Py_jit_entry = _Py_LazyJitTrampoline;
_PyJitEntryFuncPtr _Py_jit_entry = _Py_LazyJitShim;
#else
_PyJitEntryFuncPtr _Py_jit_entry = _PyTier2Interpreter;
#endif
@ -1617,7 +1617,7 @@ _PyTier2Interpreter(
const _PyUOpInstruction *next_uop;
int oparg;
/* Set up "jit" state after entry from tier 1.
* This mimics what the jit trampoline function does. */
* This mimics what the jit shim function does. */
tstate->jit_exit = NULL;
_PyStackRef _tos_cache0 = PyStackRef_ZERO_BITS;
_PyStackRef _tos_cache1 = PyStackRef_ZERO_BITS;
@ -2304,14 +2304,15 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
{
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
gen->gi_frame_state = FRAME_CLEARED;
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_CLEARED);
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_RETURN;
assert(tstate->exc_info == &gen->gi_exc_state);
tstate->exc_info = gen->gi_exc_state.previous_item;
gen->gi_exc_state.previous_item = NULL;
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
frame->previous = NULL;
_PyFrame_ClearExceptCode(frame);
_PyErr_ClearExcState(&gen->gi_exc_state);
frame->previous = NULL;
}
void
@ -3979,15 +3980,13 @@ _PyEval_GetAwaitable(PyObject *iterable, int oparg)
Py_TYPE(iterable), oparg);
}
else if (PyCoro_CheckExact(iter)) {
PyObject *yf = _PyGen_yf((PyGenObject*)iter);
if (yf != NULL) {
/* `iter` is a coroutine object that is being
awaited, `yf` is a pointer to the current awaitable
being awaited on. */
Py_DECREF(yf);
PyCoroObject *coro = (PyCoroObject *)iter;
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(coro->cr_frame_state);
if (frame_state == FRAME_SUSPENDED_YIELD_FROM) {
/* `iter` is a coroutine object that is being awaited. */
Py_CLEAR(iter);
_PyErr_SetString(PyThreadState_GET(), PyExc_RuntimeError,
"coroutine is being awaited already");
"coroutine is being awaited already");
}
}
return iter;

View file

@ -1397,13 +1397,19 @@ _Py_HandlePending(PyThreadState *tstate)
if ((breaker & _PY_GC_SCHEDULED_BIT) != 0) {
_Py_unset_eval_breaker_bit(tstate, _PY_GC_SCHEDULED_BIT);
_Py_RunGC(tstate);
#ifdef _Py_TIER2
_Py_ClearExecutorDeletionList(tstate->interp);
#endif
}
#ifdef _Py_TIER2
if ((breaker & _PY_EVAL_JIT_INVALIDATE_COLD_BIT) != 0) {
_Py_unset_eval_breaker_bit(tstate, _PY_EVAL_JIT_INVALIDATE_COLD_BIT);
_Py_Executors_InvalidateCold(tstate->interp);
tstate->interp->executor_creation_counter = JIT_CLEANUP_THRESHOLD;
_Py_ClearExecutorDeletionList(tstate->interp);
}
#endif
/* GIL drop request */
if ((breaker & _PY_GIL_DROP_REQUEST_BIT) != 0) {

View file

@ -496,3 +496,28 @@ check_periodics(PyThreadState *tstate) {
return 0;
}
// Mark the generator as executing. Returns true if the state was changed,
// false if it was already executing or finished.
static inline bool
gen_try_set_executing(PyGenObject *gen)
{
#ifdef Py_GIL_DISABLED
if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
int8_t frame_state = _Py_atomic_load_int8_relaxed(&gen->gi_frame_state);
while (frame_state < FRAME_EXECUTING) {
if (_Py_atomic_compare_exchange_int8(&gen->gi_frame_state,
&frame_state,
FRAME_EXECUTING)) {
return true;
}
}
}
#endif
// Use faster non-atomic modifications in the GIL-enabled build and when
// the object is uniquely referenced in the free-threaded build.
if (gen->gi_frame_state < FRAME_EXECUTING) {
gen->gi_frame_state = FRAME_EXECUTING;
return true;
}
return false;
}

View file

@ -343,12 +343,6 @@ PyContextVar_Set(PyObject *ovar, PyObject *val)
ENSURE_ContextVar(ovar, NULL)
PyContextVar *var = (PyContextVar *)ovar;
if (!PyContextVar_CheckExact(var)) {
PyErr_SetString(
PyExc_TypeError, "an instance of ContextVar was expected");
return NULL;
}
PyContext *ctx = context_get();
if (ctx == NULL) {
return NULL;
@ -1025,12 +1019,6 @@ static PyObject *
_contextvars_ContextVar_get_impl(PyContextVar *self, PyObject *default_value)
/*[clinic end generated code: output=0746bd0aa2ced7bf input=da66664d5d0af4ad]*/
{
if (!PyContextVar_CheckExact(self)) {
PyErr_SetString(
PyExc_TypeError, "an instance of ContextVar was expected");
return NULL;
}
PyObject *val;
if (PyContextVar_Get((PyObject *)self, default_value, &val) < 0) {
return NULL;

View file

@ -4184,12 +4184,14 @@
break;
}
case _BINARY_OP_ADD_UNICODE_r01: {
case _BINARY_OP_ADD_UNICODE_r03: {
CHECK_CURRENT_CACHED_VALUES(0);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef right;
_PyStackRef left;
_PyStackRef res;
_PyStackRef l;
_PyStackRef r;
right = stack_pointer[-1];
left = stack_pointer[-2];
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
@ -4198,29 +4200,31 @@
assert(PyUnicode_CheckExact(right_o));
STAT_INC(BINARY_OP, hit);
PyObject *res_o = PyUnicode_Concat(left_o, right_o);
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
if (res_o == NULL) {
stack_pointer += -2;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
res = PyStackRef_FromPyObjectSteal(res_o);
if (PyStackRef_IsNull(res)) {
SET_CURRENT_CACHED_VALUES(0);
JUMP_TO_ERROR();
}
res = PyStackRef_FromPyObjectSteal(res_o);
l = left;
r = right;
_tos_cache2 = r;
_tos_cache1 = l;
_tos_cache0 = res;
SET_CURRENT_CACHED_VALUES(1);
SET_CURRENT_CACHED_VALUES(3);
stack_pointer += -2;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _BINARY_OP_ADD_UNICODE_r11: {
case _BINARY_OP_ADD_UNICODE_r13: {
CHECK_CURRENT_CACHED_VALUES(1);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef right;
_PyStackRef left;
_PyStackRef res;
_PyStackRef l;
_PyStackRef r;
_PyStackRef _stack_item_0 = _tos_cache0;
right = _stack_item_0;
left = stack_pointer[-1];
@ -4230,29 +4234,34 @@
assert(PyUnicode_CheckExact(right_o));
STAT_INC(BINARY_OP, hit);
PyObject *res_o = PyUnicode_Concat(left_o, right_o);
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
if (res_o == NULL) {
stack_pointer += -1;
res = PyStackRef_FromPyObjectSteal(res_o);
if (PyStackRef_IsNull(res)) {
stack_pointer[0] = right;
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
SET_CURRENT_CACHED_VALUES(0);
JUMP_TO_ERROR();
}
res = PyStackRef_FromPyObjectSteal(res_o);
l = left;
r = right;
_tos_cache2 = r;
_tos_cache1 = l;
_tos_cache0 = res;
SET_CURRENT_CACHED_VALUES(1);
SET_CURRENT_CACHED_VALUES(3);
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _BINARY_OP_ADD_UNICODE_r21: {
case _BINARY_OP_ADD_UNICODE_r23: {
CHECK_CURRENT_CACHED_VALUES(2);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef right;
_PyStackRef left;
_PyStackRef res;
_PyStackRef l;
_PyStackRef r;
_PyStackRef _stack_item_0 = _tos_cache0;
_PyStackRef _stack_item_1 = _tos_cache1;
right = _stack_item_1;
@ -4263,49 +4272,21 @@
assert(PyUnicode_CheckExact(right_o));
STAT_INC(BINARY_OP, hit);
PyObject *res_o = PyUnicode_Concat(left_o, right_o);
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
if (res_o == NULL) {
SET_CURRENT_CACHED_VALUES(0);
JUMP_TO_ERROR();
}
res = PyStackRef_FromPyObjectSteal(res_o);
_tos_cache0 = res;
SET_CURRENT_CACHED_VALUES(1);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _BINARY_OP_ADD_UNICODE_r32: {
CHECK_CURRENT_CACHED_VALUES(3);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef right;
_PyStackRef left;
_PyStackRef res;
_PyStackRef _stack_item_0 = _tos_cache0;
_PyStackRef _stack_item_1 = _tos_cache1;
_PyStackRef _stack_item_2 = _tos_cache2;
right = _stack_item_2;
left = _stack_item_1;
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyUnicode_CheckExact(left_o));
assert(PyUnicode_CheckExact(right_o));
STAT_INC(BINARY_OP, hit);
PyObject *res_o = PyUnicode_Concat(left_o, right_o);
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
if (res_o == NULL) {
stack_pointer[0] = _stack_item_0;
stack_pointer += 1;
if (PyStackRef_IsNull(res)) {
stack_pointer[0] = left;
stack_pointer[1] = right;
stack_pointer += 2;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
SET_CURRENT_CACHED_VALUES(0);
JUMP_TO_ERROR();
}
res = PyStackRef_FromPyObjectSteal(res_o);
_tos_cache1 = res;
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(2);
l = left;
r = right;
_tos_cache2 = r;
_tos_cache1 = l;
_tos_cache0 = res;
SET_CURRENT_CACHED_VALUES(3);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
@ -4563,12 +4544,14 @@
break;
}
case _BINARY_OP_SUBSCR_LIST_INT_r21: {
case _BINARY_OP_SUBSCR_LIST_INT_r23: {
CHECK_CURRENT_CACHED_VALUES(2);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef sub_st;
_PyStackRef list_st;
_PyStackRef res;
_PyStackRef ls;
_PyStackRef ss;
_PyStackRef _stack_item_0 = _tos_cache0;
_PyStackRef _stack_item_1 = _tos_cache1;
sub_st = _stack_item_1;
@ -4619,23 +4602,13 @@
stack_pointer += 2;
#endif
STAT_INC(BINARY_OP, hit);
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyStackRef tmp = list_st;
list_st = res;
stack_pointer[-2] = list_st;
PyStackRef_CLOSE(tmp);
tmp = sub_st;
sub_st = PyStackRef_NULL;
stack_pointer[-1] = sub_st;
PyStackRef_CLOSE(tmp);
stack_pointer = _PyFrame_GetStackPointer(frame);
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
ls = list_st;
ss = sub_st;
_tos_cache2 = ss;
_tos_cache1 = ls;
_tos_cache0 = res;
_tos_cache1 = PyStackRef_ZERO_BITS;
_tos_cache2 = PyStackRef_ZERO_BITS;
SET_CURRENT_CACHED_VALUES(1);
stack_pointer += -1;
SET_CURRENT_CACHED_VALUES(3);
stack_pointer += -2;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
@ -4688,12 +4661,14 @@
break;
}
case _BINARY_OP_SUBSCR_STR_INT_r21: {
case _BINARY_OP_SUBSCR_STR_INT_r23: {
CHECK_CURRENT_CACHED_VALUES(2);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef sub_st;
_PyStackRef str_st;
_PyStackRef res;
_PyStackRef s;
_PyStackRef i;
_PyStackRef _stack_item_0 = _tos_cache0;
_PyStackRef _stack_item_1 = _tos_cache1;
sub_st = _stack_item_1;
@ -4728,15 +4703,13 @@
assert(c < 128);
STAT_INC(BINARY_OP, hit);
PyObject *res_o = (PyObject*)&_Py_SINGLETON(strings).ascii[c];
PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(str_st);
stack_pointer = _PyFrame_GetStackPointer(frame);
s = str_st;
i = sub_st;
res = PyStackRef_FromPyObjectBorrow(res_o);
_tos_cache2 = i;
_tos_cache1 = s;
_tos_cache0 = res;
_tos_cache1 = PyStackRef_ZERO_BITS;
_tos_cache2 = PyStackRef_ZERO_BITS;
SET_CURRENT_CACHED_VALUES(1);
SET_CURRENT_CACHED_VALUES(3);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
@ -5850,7 +5823,7 @@
SET_CURRENT_CACHED_VALUES(2);
JUMP_TO_JUMP_TARGET();
}
if (gen->gi_frame_state >= FRAME_EXECUTING) {
if (!gen_try_set_executing((PyGenObject *)gen)) {
UOP_STAT_INC(uopcode, miss);
_tos_cache1 = v;
_tos_cache0 = receiver;
@ -5860,7 +5833,6 @@
STAT_INC(SEND, hit);
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
_PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
assert( 2u + oparg <= UINT16_MAX);
@ -5888,7 +5860,6 @@
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
assert(oparg == 0 || oparg == 1);
gen->gi_frame_state = FRAME_SUSPENDED + oparg;
_PyStackRef temp = retval;
_PyFrame_SetStackPointer(frame, stack_pointer);
tstate->exc_info = gen->gi_exc_state.previous_item;
@ -5897,6 +5868,8 @@
_PyInterpreterFrame *gen_frame = frame;
frame = tstate->current_frame = frame->previous;
gen_frame->previous = NULL;
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_SUSPENDED + oparg);
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
#if TIER_ONE
assert(frame->instr_ptr->op.code == INSTRUMENTED_LINE ||
@ -8844,7 +8817,7 @@
stack_pointer += 2;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyDict_NotifyEvent(tstate->interp, PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
_PyDict_NotifyEvent(PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
stack_pointer = _PyFrame_GetStackPointer(frame);
FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value));
UNLOCK_OBJECT(dict);
@ -10886,6 +10859,81 @@
break;
}
case _FOR_ITER_GEN_FRAME_r03: {
CHECK_CURRENT_CACHED_VALUES(0);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef iter;
_PyStackRef gen_frame;
oparg = CURRENT_OPARG();
iter = stack_pointer[-2];
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
if (Py_TYPE(gen) != &PyGen_Type) {
UOP_STAT_INC(uopcode, miss);
SET_CURRENT_CACHED_VALUES(0);
JUMP_TO_JUMP_TARGET();
}
if (!gen_try_set_executing((PyGenObject *)gen)) {
UOP_STAT_INC(uopcode, miss);
SET_CURRENT_CACHED_VALUES(0);
JUMP_TO_JUMP_TARGET();
}
STAT_INC(FOR_ITER, hit);
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
pushed_frame->previous = frame;
frame->return_offset = (uint16_t)( 2u + oparg);
gen_frame = PyStackRef_Wrap(pushed_frame);
_tos_cache2 = gen_frame;
_tos_cache1 = stack_pointer[-1];
_tos_cache0 = iter;
SET_CURRENT_CACHED_VALUES(3);
stack_pointer += -2;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _FOR_ITER_GEN_FRAME_r13: {
CHECK_CURRENT_CACHED_VALUES(1);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef iter;
_PyStackRef gen_frame;
_PyStackRef _stack_item_0 = _tos_cache0;
oparg = CURRENT_OPARG();
iter = stack_pointer[-1];
PyGenObject *gen = (PyGenObject *)PyStackRef_AsPyObjectBorrow(iter);
if (Py_TYPE(gen) != &PyGen_Type) {
UOP_STAT_INC(uopcode, miss);
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(1);
JUMP_TO_JUMP_TARGET();
}
if (!gen_try_set_executing((PyGenObject *)gen)) {
UOP_STAT_INC(uopcode, miss);
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(1);
JUMP_TO_JUMP_TARGET();
}
STAT_INC(FOR_ITER, hit);
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
pushed_frame->previous = frame;
frame->return_offset = (uint16_t)( 2u + oparg);
gen_frame = PyStackRef_Wrap(pushed_frame);
_tos_cache2 = gen_frame;
_tos_cache1 = _stack_item_0;
_tos_cache0 = iter;
SET_CURRENT_CACHED_VALUES(3);
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _FOR_ITER_GEN_FRAME_r23: {
CHECK_CURRENT_CACHED_VALUES(2);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
@ -10903,17 +10951,7 @@
SET_CURRENT_CACHED_VALUES(2);
JUMP_TO_JUMP_TARGET();
}
#ifdef Py_GIL_DISABLED
if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
UOP_STAT_INC(uopcode, miss);
_tos_cache1 = _stack_item_1;
_tos_cache0 = iter;
SET_CURRENT_CACHED_VALUES(2);
JUMP_TO_JUMP_TARGET();
}
#endif
if (gen->gi_frame_state >= FRAME_EXECUTING) {
if (!gen_try_set_executing((PyGenObject *)gen)) {
UOP_STAT_INC(uopcode, miss);
_tos_cache1 = _stack_item_1;
_tos_cache0 = iter;
@ -10923,7 +10961,6 @@
STAT_INC(FOR_ITER, hit);
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
pushed_frame->previous = frame;
@ -16559,6 +16596,106 @@
break;
}
case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03: {
CHECK_CURRENT_CACHED_VALUES(0);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef arg;
_PyStackRef callable;
_PyStackRef res;
_PyStackRef a;
_PyStackRef c;
arg = stack_pointer[-1];
callable = stack_pointer[-3];
PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64();
res = PyStackRef_FromPyObjectBorrow(ptr);
a = arg;
c = callable;
_tos_cache2 = c;
_tos_cache1 = a;
_tos_cache0 = res;
SET_CURRENT_CACHED_VALUES(3);
stack_pointer += -3;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13: {
CHECK_CURRENT_CACHED_VALUES(1);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef arg;
_PyStackRef callable;
_PyStackRef res;
_PyStackRef a;
_PyStackRef c;
_PyStackRef _stack_item_0 = _tos_cache0;
arg = _stack_item_0;
callable = stack_pointer[-2];
PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64();
res = PyStackRef_FromPyObjectBorrow(ptr);
a = arg;
c = callable;
_tos_cache2 = c;
_tos_cache1 = a;
_tos_cache0 = res;
SET_CURRENT_CACHED_VALUES(3);
stack_pointer += -2;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23: {
CHECK_CURRENT_CACHED_VALUES(2);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef arg;
_PyStackRef callable;
_PyStackRef res;
_PyStackRef a;
_PyStackRef c;
_PyStackRef _stack_item_0 = _tos_cache0;
_PyStackRef _stack_item_1 = _tos_cache1;
arg = _stack_item_1;
callable = stack_pointer[-1];
PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64();
res = PyStackRef_FromPyObjectBorrow(ptr);
a = arg;
c = callable;
_tos_cache2 = c;
_tos_cache1 = a;
_tos_cache0 = res;
SET_CURRENT_CACHED_VALUES(3);
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33: {
CHECK_CURRENT_CACHED_VALUES(3);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
_PyStackRef arg;
_PyStackRef callable;
_PyStackRef res;
_PyStackRef a;
_PyStackRef c;
_PyStackRef _stack_item_0 = _tos_cache0;
_PyStackRef _stack_item_1 = _tos_cache1;
_PyStackRef _stack_item_2 = _tos_cache2;
arg = _stack_item_2;
callable = _stack_item_0;
PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64();
res = PyStackRef_FromPyObjectBorrow(ptr);
a = arg;
c = callable;
_tos_cache2 = c;
_tos_cache1 = a;
_tos_cache0 = res;
SET_CURRENT_CACHED_VALUES(3);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
break;
}
case _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW_r31: {
CHECK_CURRENT_CACHED_VALUES(3);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());

View file

@ -245,6 +245,8 @@
_PyStackRef left;
_PyStackRef right;
_PyStackRef res;
_PyStackRef l;
_PyStackRef r;
// _GUARD_TOS_UNICODE
{
value = stack_pointer[-1];
@ -276,12 +278,24 @@
assert(PyUnicode_CheckExact(right_o));
STAT_INC(BINARY_OP, hit);
PyObject *res_o = PyUnicode_Concat(left_o, right_o);
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc);
if (res_o == NULL) {
JUMP_TO_LABEL(pop_2_error);
}
res = PyStackRef_FromPyObjectSteal(res_o);
if (PyStackRef_IsNull(res)) {
JUMP_TO_LABEL(error);
}
l = left;
r = right;
}
// _POP_TOP_UNICODE
{
value = r;
assert(PyUnicode_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
PyStackRef_CLOSE_SPECIALIZED(value, _PyUnicode_ExactDealloc);
}
// _POP_TOP_UNICODE
{
value = l;
assert(PyUnicode_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
PyStackRef_CLOSE_SPECIALIZED(value, _PyUnicode_ExactDealloc);
}
stack_pointer[-2] = res;
stack_pointer += -1;
@ -750,6 +764,8 @@
_PyStackRef list_st;
_PyStackRef sub_st;
_PyStackRef res;
_PyStackRef ls;
_PyStackRef ss;
// _GUARD_TOS_INT
{
value = stack_pointer[-1];
@ -808,18 +824,24 @@
res = PyStackRef_FromPyObjectNew(res_o);
#endif
STAT_INC(BINARY_OP, hit);
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyStackRef tmp = list_st;
list_st = res;
stack_pointer[-2] = list_st;
PyStackRef_CLOSE(tmp);
tmp = sub_st;
sub_st = PyStackRef_NULL;
stack_pointer[-1] = sub_st;
PyStackRef_CLOSE(tmp);
stack_pointer = _PyFrame_GetStackPointer(frame);
ls = list_st;
ss = sub_st;
}
// _POP_TOP_INT
{
value = ss;
assert(PyLong_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
PyStackRef_CLOSE_SPECIALIZED(value, _PyLong_ExactDealloc);
}
// _POP_TOP
{
value = ls;
stack_pointer[-2] = res;
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_XCLOSE(value);
stack_pointer = _PyFrame_GetStackPointer(frame);
}
DISPATCH();
}
@ -912,6 +934,8 @@
_PyStackRef str_st;
_PyStackRef sub_st;
_PyStackRef res;
_PyStackRef s;
_PyStackRef i;
// _GUARD_TOS_INT
{
value = stack_pointer[-1];
@ -961,17 +985,26 @@
assert(c < 128);
STAT_INC(BINARY_OP, hit);
PyObject *res_o = (PyObject*)&_Py_SINGLETON(strings).ascii[c];
PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);
stack_pointer += -2;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_CLOSE(str_st);
stack_pointer = _PyFrame_GetStackPointer(frame);
s = str_st;
i = sub_st;
res = PyStackRef_FromPyObjectBorrow(res_o);
}
stack_pointer[0] = res;
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
// _POP_TOP_INT
{
value = i;
assert(PyLong_CheckExact(PyStackRef_AsPyObjectBorrow(value)));
PyStackRef_CLOSE_SPECIALIZED(value, _PyLong_ExactDealloc);
}
// _POP_TOP
{
value = s;
stack_pointer[-2] = res;
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
_PyFrame_SetStackPointer(frame, stack_pointer);
PyStackRef_XCLOSE(value);
stack_pointer = _PyFrame_GetStackPointer(frame);
}
DISPATCH();
}
@ -5515,14 +5548,7 @@
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
}
#ifdef Py_GIL_DISABLED
if (!_PyObject_IsUniquelyReferenced((PyObject *)gen)) {
UPDATE_MISS_STATS(FOR_ITER);
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
}
#endif
if (gen->gi_frame_state >= FRAME_EXECUTING) {
if (!gen_try_set_executing((PyGenObject *)gen)) {
UPDATE_MISS_STATS(FOR_ITER);
assert(_PyOpcode_Deopt[opcode] == (FOR_ITER));
JUMP_TO_PREDICTED(FOR_ITER);
@ -5530,7 +5556,6 @@
STAT_INC(FOR_ITER, hit);
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
_PyFrame_StackPush(pushed_frame, PyStackRef_None);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
pushed_frame->previous = frame;
@ -7294,7 +7319,6 @@
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
assert(oparg == 0 || oparg == 1);
gen->gi_frame_state = FRAME_SUSPENDED + oparg;
_PyStackRef temp = retval;
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
@ -7305,6 +7329,8 @@
_PyInterpreterFrame *gen_frame = frame;
frame = tstate->current_frame = frame->previous;
gen_frame->previous = NULL;
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_SUSPENDED + oparg);
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
#if TIER_ONE
assert(frame->instr_ptr->op.code == INSTRUMENTED_LINE ||
@ -10268,14 +10294,13 @@
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
if ((tstate->interp->eval_frame == NULL) &&
(Py_TYPE(receiver_o) == &PyGen_Type || Py_TYPE(receiver_o) == &PyCoro_Type) &&
((PyGenObject *)receiver_o)->gi_frame_state < FRAME_EXECUTING)
gen_try_set_executing((PyGenObject *)receiver_o))
{
PyGenObject *gen = (PyGenObject *)receiver_o;
_PyInterpreterFrame *gen_frame = &gen->gi_iframe;
_PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v));
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
assert( 2u + oparg <= UINT16_MAX);
@ -10368,7 +10393,7 @@
assert(_PyOpcode_Deopt[opcode] == (SEND));
JUMP_TO_PREDICTED(SEND);
}
if (gen->gi_frame_state >= FRAME_EXECUTING) {
if (!gen_try_set_executing((PyGenObject *)gen)) {
UPDATE_MISS_STATS(SEND);
assert(_PyOpcode_Deopt[opcode] == (SEND));
JUMP_TO_PREDICTED(SEND);
@ -10376,7 +10401,6 @@
STAT_INC(SEND, hit);
_PyInterpreterFrame *pushed_frame = &gen->gi_iframe;
_PyFrame_StackPush(pushed_frame, PyStackRef_MakeHeapSafe(v));
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
assert( 2u + oparg <= UINT16_MAX);
@ -10808,7 +10832,7 @@
}
}
_PyFrame_SetStackPointer(frame, stack_pointer);
_PyDict_NotifyEvent(tstate->interp, PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
_PyDict_NotifyEvent(PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
stack_pointer = _PyFrame_GetStackPointer(frame);
FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value));
UNLOCK_OBJECT(dict);
@ -12012,7 +12036,6 @@
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1);
assert(oparg == 0 || oparg == 1);
gen->gi_frame_state = FRAME_SUSPENDED + oparg;
_PyStackRef temp = retval;
stack_pointer += -1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
@ -12023,6 +12046,8 @@
_PyInterpreterFrame *gen_frame = frame;
frame = tstate->current_frame = frame->previous;
gen_frame->previous = NULL;
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_YIELD;
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_SUSPENDED + oparg);
assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER);
#if TIER_ONE
assert(frame->instr_ptr->op.code == INSTRUMENTED_LINE ||

View file

@ -4762,6 +4762,7 @@ static PyObject *
_imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
/*[clinic end generated code: output=83249b827a4fde77 input=c31b954f4cf4e09d]*/
{
FILE *fp = NULL;
PyObject *mod = NULL;
PyThreadState *tstate = _PyThreadState_GET();
@ -4804,16 +4805,12 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
/* We would move this (and the fclose() below) into
* _PyImport_GetModuleExportHooks(), but it isn't clear if the intervening
* code relies on fp still being open. */
FILE *fp;
if (file != NULL) {
fp = Py_fopen(info.filename, "r");
if (fp == NULL) {
goto finally;
}
}
else {
fp = NULL;
}
PyModInitFunction p0 = NULL;
PyModExportFunction ex0 = NULL;
@ -4822,7 +4819,7 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
mod = import_run_modexport(tstate, ex0, &info, spec);
// Modules created from slots handle GIL enablement (Py_mod_gil slot)
// when they're created.
goto cleanup;
goto finally;
}
if (p0 == NULL) {
goto finally;
@ -4845,13 +4842,10 @@ _imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
}
#endif
cleanup:
// XXX Shouldn't this happen in the error cases too (i.e. in "finally")?
if (fp) {
finally:
if (fp != NULL) {
fclose(fp);
}
finally:
_Py_ext_module_loader_info_clear(&info);
return mod;
}

View file

@ -77,7 +77,7 @@ jit_alloc(size_t size)
unsigned char *memory = mmap(NULL, size, prot, flags, -1, 0);
int failed = memory == MAP_FAILED;
if (!failed) {
_PyAnnotateMemoryMap(memory, size, "cpython:jit");
(void)_PyAnnotateMemoryMap(memory, size, "cpython:jit");
}
#endif
if (failed) {
@ -672,20 +672,20 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz
return 0;
}
/* One-off compilation of the jit entry trampoline
/* One-off compilation of the jit entry shim
* We compile this once only as it effectively a normal
* function, but we need to use the JIT because it needs
* to understand the jit-specific calling convention.
*/
static _PyJitEntryFuncPtr
compile_trampoline(void)
compile_shim(void)
{
_PyExecutorObject dummy;
const StencilGroup *group;
size_t code_size = 0;
size_t data_size = 0;
jit_state state = {0};
group = &trampoline;
group = &shim;
code_size += group->code_size;
data_size += group->data_size;
combine_symbol_mask(group->trampoline_mask, state.trampolines.mask);
@ -707,7 +707,7 @@ compile_trampoline(void)
// Compile the shim, which handles converting between the native
// calling convention and the calling convention used by jitted code
// (which may be different for efficiency reasons).
group = &trampoline;
group = &shim;
group->emit(code, data, &dummy, NULL, &state);
code += group->code_size;
data += group->data_size;
@ -723,17 +723,17 @@ compile_trampoline(void)
static PyMutex lazy_jit_mutex = { 0 };
_Py_CODEUNIT *
_Py_LazyJitTrampoline(
_Py_LazyJitShim(
_PyExecutorObject *executor, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate
) {
PyMutex_Lock(&lazy_jit_mutex);
if (_Py_jit_entry == _Py_LazyJitTrampoline) {
_PyJitEntryFuncPtr trampoline = compile_trampoline();
if (trampoline == NULL) {
if (_Py_jit_entry == _Py_LazyJitShim) {
_PyJitEntryFuncPtr shim = compile_shim();
if (shim == NULL) {
PyMutex_Unlock(&lazy_jit_mutex);
Py_FatalError("Cannot allocate core JIT code");
}
_Py_jit_entry = trampoline;
_Py_jit_entry = shim;
}
PyMutex_Unlock(&lazy_jit_mutex);
return _Py_jit_entry(executor, frame, stack_pointer, tstate);

View file

@ -185,12 +185,17 @@ _PyOptimizer_Optimize(
else {
executor->vm_data.code = NULL;
}
executor->vm_data.chain_depth = chain_depth;
assert(executor->vm_data.valid);
_PyExitData *exit = _tstate->jit_tracer_state.initial_state.exit;
if (exit != NULL) {
exit->executor = executor;
}
executor->vm_data.chain_depth = chain_depth;
assert(executor->vm_data.valid);
else {
// An executor inserted into the code object now has a strong reference
// to it from the code object. Thus, we don't need this reference anymore.
Py_DECREF(executor);
}
interp->compiling = false;
return 1;
#else
@ -246,8 +251,6 @@ get_oparg(PyObject *self, PyObject *Py_UNUSED(ignored))
///////////////////// Experimental UOp Optimizer /////////////////////
static int executor_clear(PyObject *executor);
static void unlink_executor(_PyExecutorObject *executor);
void
_PyExecutor_Free(_PyExecutorObject *self)
@ -258,63 +261,76 @@ _PyExecutor_Free(_PyExecutorObject *self)
PyObject_GC_Del(self);
}
static void executor_invalidate(PyObject *op);
static void
executor_clear_exits(_PyExecutorObject *executor)
{
_PyExecutorObject *cold = _PyExecutor_GetColdExecutor();
_PyExecutorObject *cold_dynamic = _PyExecutor_GetColdDynamicExecutor();
for (uint32_t i = 0; i < executor->exit_count; i++) {
_PyExitData *exit = &executor->exits[i];
exit->temperature = initial_unreachable_backoff_counter();
_PyExecutorObject *old = executor->exits[i].executor;
exit->executor = exit->is_dynamic ? cold_dynamic : cold;
Py_DECREF(old);
}
}
void
_Py_ClearExecutorDeletionList(PyInterpreterState *interp)
{
if (interp->executor_deletion_list_head == NULL) {
return;
}
_PyRuntimeState *runtime = &_PyRuntime;
HEAD_LOCK(runtime);
PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
while (ts) {
_PyExecutorObject *current = (_PyExecutorObject *)ts->current_executor;
Py_XINCREF(current);
ts = ts->next;
}
HEAD_UNLOCK(runtime);
_PyExecutorObject *keep_list = NULL;
do {
_PyExecutorObject *exec = interp->executor_deletion_list_head;
interp->executor_deletion_list_head = exec->vm_data.links.next;
if (Py_REFCNT(exec) == 0) {
_PyExecutor_Free(exec);
} else {
exec->vm_data.links.next = keep_list;
keep_list = exec;
}
} while (interp->executor_deletion_list_head != NULL);
interp->executor_deletion_list_head = keep_list;
HEAD_LOCK(runtime);
ts = PyInterpreterState_ThreadHead(interp);
while (ts) {
_PyExecutorObject *current = (_PyExecutorObject *)ts->current_executor;
if (current != NULL) {
/* Anything in this list will be unlinked, so we can reuse the
* linked field as a reachability marker. */
current->vm_data.linked = 1;
_Py_DECREF_NO_DEALLOC((PyObject *)current);
}
HEAD_LOCK(runtime);
ts = PyThreadState_Next(ts);
HEAD_UNLOCK(runtime);
ts = ts->next;
}
_PyExecutorObject **prev_to_next_ptr = &interp->executor_deletion_list_head;
_PyExecutorObject *exec = *prev_to_next_ptr;
while (exec != NULL) {
if (exec->vm_data.linked) {
// This executor is currently executing
exec->vm_data.linked = 0;
prev_to_next_ptr = &exec->vm_data.links.next;
}
else {
*prev_to_next_ptr = exec->vm_data.links.next;
_PyExecutor_Free(exec);
}
exec = *prev_to_next_ptr;
}
interp->executor_deletion_list_remaining_capacity = EXECUTOR_DELETE_LIST_MAX;
HEAD_UNLOCK(runtime);
}
static void
add_to_pending_deletion_list(_PyExecutorObject *self)
{
PyInterpreterState *interp = PyInterpreterState_Get();
self->vm_data.links.previous = NULL;
self->vm_data.links.next = interp->executor_deletion_list_head;
interp->executor_deletion_list_head = self;
if (interp->executor_deletion_list_remaining_capacity > 0) {
interp->executor_deletion_list_remaining_capacity--;
}
else {
_Py_ClearExecutorDeletionList(interp);
}
}
static void
uop_dealloc(PyObject *op) {
_PyExecutorObject *self = _PyExecutorObject_CAST(op);
_PyObject_GC_UNTRACK(self);
executor_invalidate(op);
assert(self->vm_data.code == NULL);
unlink_executor(self);
// Once unlinked it becomes impossible to invalidate an executor, so do it here.
self->vm_data.valid = 0;
add_to_pending_deletion_list(self);
}
@ -1621,7 +1637,6 @@ link_executor(_PyExecutorObject *executor)
head->vm_data.links.previous = executor;
interp->executor_list_head = executor;
}
executor->vm_data.linked = true;
/* executor_list_head must be first in list */
assert(interp->executor_list_head->vm_data.links.previous == NULL);
}
@ -1629,11 +1644,7 @@ link_executor(_PyExecutorObject *executor)
static void
unlink_executor(_PyExecutorObject *executor)
{
if (!executor->vm_data.linked) {
return;
}
_PyExecutorLinkListNode *links = &executor->vm_data.links;
assert(executor->vm_data.valid);
_PyExecutorObject *next = links->next;
_PyExecutorObject *prev = links->previous;
if (next != NULL) {
@ -1648,7 +1659,6 @@ unlink_executor(_PyExecutorObject *executor)
assert(interp->executor_list_head == executor);
interp->executor_list_head = next;
}
executor->vm_data.linked = false;
}
/* This must be called by optimizers before using the executor */
@ -1662,61 +1672,47 @@ _Py_ExecutorInit(_PyExecutorObject *executor, const _PyBloomFilter *dependency_s
link_executor(executor);
}
_PyExecutorObject *
_PyExecutor_GetColdExecutor(void)
static _PyExecutorObject *
make_cold_executor(uint16_t opcode)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->cold_executor != NULL) {
return interp->cold_executor;
}
_PyExecutorObject *cold = allocate_executor(0, 1);
if (cold == NULL) {
Py_FatalError("Cannot allocate core JIT code");
}
((_PyUOpInstruction *)cold->trace)->opcode = _COLD_EXIT_r00;
#ifdef _Py_JIT
cold->jit_code = NULL;
cold->jit_size = 0;
((_PyUOpInstruction *)cold->trace)->opcode = opcode;
// This is initialized to true so we can prevent the executor
// from being immediately detected as cold and invalidated.
cold->vm_data.warm = true;
#ifdef _Py_JIT
cold->jit_code = NULL;
cold->jit_size = 0;
if (_PyJIT_Compile(cold, cold->trace, 1)) {
Py_DECREF(cold);
Py_FatalError("Cannot allocate core JIT code");
}
#endif
_Py_SetImmortal((PyObject *)cold);
interp->cold_executor = cold;
return cold;
}
_PyExecutorObject *
_PyExecutor_GetColdExecutor(void)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->cold_executor == NULL) {
return interp->cold_executor = make_cold_executor(_COLD_EXIT_r00);;
}
return interp->cold_executor;
}
_PyExecutorObject *
_PyExecutor_GetColdDynamicExecutor(void)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->cold_dynamic_executor != NULL) {
assert(interp->cold_dynamic_executor->trace[0].opcode == _COLD_DYNAMIC_EXIT_r00);
return interp->cold_dynamic_executor;
if (interp->cold_dynamic_executor == NULL) {
interp->cold_dynamic_executor = make_cold_executor(_COLD_DYNAMIC_EXIT_r00);
}
_PyExecutorObject *cold = allocate_executor(0, 1);
if (cold == NULL) {
Py_FatalError("Cannot allocate core JIT code");
}
((_PyUOpInstruction *)cold->trace)->opcode = _COLD_DYNAMIC_EXIT_r00;
#ifdef _Py_JIT
cold->jit_code = NULL;
cold->jit_size = 0;
// This is initialized to true so we can prevent the executor
// from being immediately detected as cold and invalidated.
cold->vm_data.warm = true;
if (_PyJIT_Compile(cold, cold->trace, 1)) {
Py_DECREF(cold);
Py_FatalError("Cannot allocate core JIT code");
}
#endif
_Py_SetImmortal((PyObject *)cold);
interp->cold_dynamic_executor = cold;
return cold;
return interp->cold_dynamic_executor;
}
void
@ -1755,32 +1751,28 @@ _Py_ExecutorDetach(_PyExecutorObject *executor)
Py_DECREF(executor);
}
static int
executor_clear(PyObject *op)
/* Executors can be invalidated at any time,
even with a stop-the-world lock held.
Consequently it must not run arbitrary code,
including Py_DECREF with a non-executor. */
static void
executor_invalidate(PyObject *op)
{
_PyExecutorObject *executor = _PyExecutorObject_CAST(op);
if (!executor->vm_data.valid) {
return 0;
return;
}
assert(executor->vm_data.valid == 1);
unlink_executor(executor);
executor->vm_data.valid = 0;
/* It is possible for an executor to form a reference
* cycle with itself, so decref'ing a side exit could
* free the executor unless we hold a strong reference to it
*/
_PyExecutorObject *cold = _PyExecutor_GetColdExecutor();
Py_INCREF(executor);
for (uint32_t i = 0; i < executor->exit_count; i++) {
executor->exits[i].temperature = initial_unreachable_backoff_counter();
_PyExecutorObject *e = executor->exits[i].executor;
executor->exits[i].executor = cold;
Py_DECREF(e);
}
unlink_executor(executor);
executor_clear_exits(executor);
_Py_ExecutorDetach(executor);
Py_DECREF(executor);
return 0;
_PyObject_GC_UNTRACK(op);
}
static int
executor_clear(PyObject *op)
{
executor_invalidate(op);
}
void
@ -1805,7 +1797,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is
if (invalidate == NULL) {
goto error;
}
/* Clearing an executor can deallocate others, so we need to make a list of
/* Clearing an executor can clear others, so we need to make a list of
* executors to invalidate first */
for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) {
assert(exec->vm_data.valid);
@ -1819,7 +1811,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is
}
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) {
PyObject *exec = PyList_GET_ITEM(invalidate, i);
executor_clear(exec);
executor_invalidate(exec);
if (is_invalidation) {
OPT_STAT_INC(executors_invalidated);
}
@ -1851,13 +1843,13 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
{
while (interp->executor_list_head) {
_PyExecutorObject *executor = interp->executor_list_head;
assert(executor->vm_data.valid == 1 && executor->vm_data.linked == 1);
assert(executor->vm_data.valid);
if (executor->vm_data.code) {
// Clear the entire code object so its co_executors array be freed:
_PyCode_Clear_Executors(executor->vm_data.code);
}
else {
executor_clear((PyObject *)executor);
executor_invalidate((PyObject *)executor);
}
if (is_invalidation) {
OPT_STAT_INC(executors_invalidated);
@ -1892,7 +1884,7 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp)
}
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) {
PyObject *exec = PyList_GET_ITEM(invalidate, i);
executor_clear(exec);
executor_invalidate(exec);
}
Py_DECREF(invalidate);
return;
@ -1977,7 +1969,7 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
#else
fprintf(out, " <tr><td port=\"i%d\" border=\"1\" >%s op0=%" PRIu64 "</td></tr>\n", i, opname, inst->operand0);
#endif
if (inst->opcode == _EXIT_TRACE || inst->opcode == _JUMP_TO_TOP) {
if (base_opcode == _EXIT_TRACE || base_opcode == _JUMP_TO_TOP) {
break;
}
}
@ -1990,7 +1982,7 @@ executor_to_gv(_PyExecutorObject *executor, FILE *out)
for (uint32_t i = 0; i < executor->code_size; i++) {
_PyUOpInstruction const *inst = &executor->trace[i];
uint16_t base_opcode = _PyUop_Uncached[inst->opcode];
uint16_t flags = _PyUop_Flags[inst->opcode];
uint16_t flags = _PyUop_Flags[base_opcode];
_PyExitData *exit = NULL;
if (base_opcode == _EXIT_TRACE) {
exit = (_PyExitData *)inst->operand0;

View file

@ -298,12 +298,14 @@ dummy_func(void) {
r = right;
}
op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) {
op(_BINARY_OP_ADD_UNICODE, (left, right -- res, l, r)) {
REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
res = sym_new_type(ctx, &PyUnicode_Type);
l = left;
r = right;
}
op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- )) {
op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right --)) {
JitOptRef res;
if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) {
assert(PyUnicode_CheckExact(sym_get_const(ctx, left)));
@ -327,8 +329,10 @@ dummy_func(void) {
ctx->done = true;
}
op(_BINARY_OP_SUBSCR_STR_INT, (str_st, sub_st -- res)) {
op(_BINARY_OP_SUBSCR_STR_INT, (str_st, sub_st -- res, s, i)) {
res = sym_new_type(ctx, &PyUnicode_Type);
s = str_st;
i = sub_st;
}
op(_BINARY_OP_SUBSCR_TUPLE_INT, (tuple_st, sub_st -- res)) {
@ -525,10 +529,6 @@ dummy_func(void) {
value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
}
op(_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW, (ptr/4, unused, unused, unused -- value)) {
value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
}
op(_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, unused, unused, unused, unused -- value)) {
value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
}
@ -563,6 +563,12 @@ dummy_func(void) {
}
}
op(_POP_TOP_UNICODE, (value --)) {
if (PyJitRef_IsBorrowed(value)) {
REPLACE_OP(this_instr, _POP_TOP_NOP, 0, 0);
}
}
op(_COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) {
assert(oparg > 0);
top = bottom;
@ -1253,7 +1259,7 @@ dummy_func(void) {
goto error;
}
if (_Py_IsImmortal(temp)) {
REPLACE_OP(this_instr, _POP_CALL_ONE_LOAD_CONST_INLINE_BORROW,
REPLACE_OP(this_instr, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW,
0, (uintptr_t)temp);
}
res = sym_new_const(ctx, temp);
@ -1370,7 +1376,12 @@ dummy_func(void) {
res = sym_new_not_null(ctx);
}
else {
res = sym_new_const(ctx, cnst);
if (_Py_IsImmortal(cnst)) {
res = PyJitRef_Borrow(sym_new_const(ctx, cnst));
}
else {
res = sym_new_const(ctx, cnst);
}
}
}
@ -1405,10 +1416,21 @@ dummy_func(void) {
res = sym_new_not_null(ctx);
}
else {
res = sym_new_const(ctx, cnst);
if (_Py_IsImmortal(cnst)) {
res = PyJitRef_Borrow(sym_new_const(ctx, cnst));
}
else {
res = sym_new_const(ctx, cnst);
}
}
}
op(_BINARY_OP_SUBSCR_LIST_INT, (list_st, sub_st -- res, ls, ss)) {
res = sym_new_unknown(ctx);
ls = list_st;
ss = sub_st;
}
// END BYTECODES //

Some files were not shown because too many files have changed in this diff Show more