mirror of
https://github.com/python/cpython.git
synced 2025-12-23 09:19:18 +00:00
Merge branch 'main' into gh-141805
This commit is contained in:
commit
de4dbb4b7b
110 changed files with 3925 additions and 2149 deletions
|
|
@ -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
|
||||
=============
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
667
Doc/extending/first-extension-module.rst
Normal file
667
Doc/extending/first-extension-module.rst
Normal 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``.
|
||||
|
|
@ -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`
|
||||
|
|
|
|||
55
Doc/includes/capi-extension/spammodule-01.c
Normal file
55
Doc/includes/capi-extension/spammodule-01.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`.)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
8
Include/internal/pycore_opcode_metadata.h
generated
8
Include/internal/pycore_opcode_metadata.h
generated
|
|
@ -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 } } },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
*/
|
||||
|
|
|
|||
1218
Include/internal/pycore_uop_ids.h
generated
1218
Include/internal/pycore_uop_ids.h
generated
File diff suppressed because it is too large
Load diff
71
Include/internal/pycore_uop_metadata.h
generated
71
Include/internal/pycore_uop_metadata.h
generated
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -240,4 +240,5 @@ if __name__ == '__main__':
|
|||
break
|
||||
|
||||
console.write('exiting asyncio REPL...\n')
|
||||
loop.close()
|
||||
sys.exit(return_code)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
28
Lib/pdb.py
28
Lib/pdb.py
|
|
@ -391,17 +391,22 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
# Read ~/.pdbrc and ./.pdbrc
|
||||
self.rcLines = []
|
||||
if readrc:
|
||||
home_rcfile = os.path.expanduser("~/.pdbrc")
|
||||
local_rcfile = os.path.abspath(".pdbrc")
|
||||
|
||||
try:
|
||||
with open(os.path.expanduser('~/.pdbrc'), encoding='utf-8') as rcFile:
|
||||
self.rcLines.extend(rcFile)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
with open(".pdbrc", encoding='utf-8') as rcFile:
|
||||
self.rcLines.extend(rcFile)
|
||||
with open(home_rcfile, encoding='utf-8') as rcfile:
|
||||
self.rcLines.extend(rcfile)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if local_rcfile != home_rcfile:
|
||||
try:
|
||||
with open(local_rcfile, encoding='utf-8') as rcfile:
|
||||
self.rcLines.extend(rcfile)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.commands = {} # associates a command list to breakpoint numbers
|
||||
self.commands_defining = False # True while in the process of defining
|
||||
# a command list
|
||||
|
|
@ -1315,7 +1320,14 @@ class Pdb(bdb.Bdb, cmd.Cmd):
|
|||
reached.
|
||||
"""
|
||||
if not arg:
|
||||
bnum = len(bdb.Breakpoint.bpbynumber) - 1
|
||||
for bp in reversed(bdb.Breakpoint.bpbynumber):
|
||||
if bp is None:
|
||||
continue
|
||||
bnum = bp.number
|
||||
break
|
||||
else:
|
||||
self.error('cannot set commands: no existing breakpoint')
|
||||
return
|
||||
else:
|
||||
try:
|
||||
bnum = int(arg)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -15,37 +15,13 @@ let coldCodeHidden = false;
|
|||
// ============================================================================
|
||||
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const current = html.getAttribute('data-theme') || 'light';
|
||||
const next = current === 'light' ? 'dark' : 'light';
|
||||
html.setAttribute('data-theme', next);
|
||||
localStorage.setItem('heatmap-theme', next);
|
||||
|
||||
// Update theme button icon
|
||||
const btn = document.getElementById('theme-btn');
|
||||
if (btn) {
|
||||
btn.querySelector('.icon-moon').style.display = next === 'dark' ? 'none' : '';
|
||||
btn.querySelector('.icon-sun').style.display = next === 'dark' ? '' : 'none';
|
||||
}
|
||||
toggleAndSaveTheme();
|
||||
applyLineColors();
|
||||
|
||||
// Rebuild scroll marker with new theme colors
|
||||
buildScrollMarker();
|
||||
}
|
||||
|
||||
function restoreUIState() {
|
||||
// Restore theme
|
||||
const savedTheme = localStorage.getItem('heatmap-theme');
|
||||
if (savedTheme) {
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
const btn = document.getElementById('theme-btn');
|
||||
if (btn) {
|
||||
btn.querySelector('.icon-moon').style.display = savedTheme === 'dark' ? 'none' : '';
|
||||
btn.querySelector('.icon-sun').style.display = savedTheme === 'dark' ? '' : 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Functions
|
||||
// ============================================================================
|
||||
|
|
@ -542,20 +518,23 @@ function toggleBytecode(button) {
|
|||
const lineId = lineDiv.id;
|
||||
const lineNum = lineId.replace('line-', '');
|
||||
const panel = document.getElementById(`bytecode-${lineNum}`);
|
||||
const wrapper = document.getElementById(`bytecode-wrapper-${lineNum}`);
|
||||
|
||||
if (!panel) return;
|
||||
if (!panel || !wrapper) return;
|
||||
|
||||
const isExpanded = panel.style.display !== 'none';
|
||||
const isExpanded = panel.classList.contains('expanded');
|
||||
|
||||
if (isExpanded) {
|
||||
panel.style.display = 'none';
|
||||
panel.classList.remove('expanded');
|
||||
wrapper.classList.remove('expanded');
|
||||
button.classList.remove('expanded');
|
||||
button.innerHTML = '▶'; // Right arrow
|
||||
} else {
|
||||
if (!panel.dataset.populated) {
|
||||
populateBytecodePanel(panel, button);
|
||||
}
|
||||
panel.style.display = 'block';
|
||||
panel.classList.add('expanded');
|
||||
wrapper.classList.add('expanded');
|
||||
button.classList.add('expanded');
|
||||
button.innerHTML = '▼'; // Down arrow
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -978,7 +978,17 @@ class HeatmapCollector(StackTraceCollector):
|
|||
f'data-spec-pct="{spec_pct}" '
|
||||
f'onclick="toggleBytecode(this)" title="Show bytecode">▶</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>'
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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,))
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -415,6 +415,8 @@ class OpenerDirector:
|
|||
continue
|
||||
|
||||
i = meth.find("_")
|
||||
if i < 1:
|
||||
continue
|
||||
protocol = meth[:i]
|
||||
condition = meth[i+1:]
|
||||
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Fix reference cycle in exhausted generator frames. Patch by Savannah Ostrowski.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Fix a memory leak in the experimental Tier 2 optimizer when creating
|
||||
executors. Patched by Shamil Abdulaev.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Clear the frame of a generator when :meth:`generator.close` is called.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Make concurrent iteration over the same range iterator thread-safe in the
|
||||
free threading build.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Fix building JIT stencils on free-threaded builds.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Fix a file descriptor leak in import.c
|
||||
|
|
@ -0,0 +1 @@
|
|||
Fix a segfault in the JIT when constant folding ``len(tuple)``.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Add support for :const:`~configparser.UNNAMED_SECTION` when creating a
|
||||
section via the mapping protocol access
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Fix issue where ``pdb`` would read a ``.pdbrc`` twice if launched from the home directory
|
||||
|
|
@ -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);
|
||||
|
||||
/* ============================================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, ¤t_frame, frame_addr,
|
||||
int parse_result = parse_frame_object(unwinder, ¤t_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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
38
Modules/clinic/mmapmodule.c.h
generated
38
Modules/clinic/mmapmodule.c.h
generated
|
|
@ -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]*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
150
Objects/clinic/rangeobject.c.h
generated
Normal 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]*/
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
327
Python/executor_cases.c.h
generated
327
Python/executor_cases.c.h
generated
|
|
@ -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());
|
||||
|
|
|
|||
105
Python/generated_cases.c.h
generated
105
Python/generated_cases.c.h
generated
|
|
@ -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 ||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
20
Python/jit.c
20
Python/jit.c
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue