diff --git a/.github/workflows/posix-deps-apt.sh b/.github/workflows/posix-deps-apt.sh index 7994a01ee46..6201e719ca8 100755 --- a/.github/workflows/posix-deps-apt.sh +++ b/.github/workflows/posix-deps-apt.sh @@ -26,9 +26,16 @@ apt-get -yq --no-install-recommends install \ xvfb \ zlib1g-dev -# Workaround missing libmpdec-dev on ubuntu 24.04: -# https://launchpad.net/~ondrej/+archive/ubuntu/php -# https://deb.sury.org/ -sudo add-apt-repository ppa:ondrej/php -apt-get update -apt-get -yq --no-install-recommends install libmpdec-dev +# Workaround missing libmpdec-dev on ubuntu 24.04 by building mpdecimal +# from source. ppa:ondrej/php (launchpad.net) are unreliable +# (https://status.canonical.com) so fetch the tarball directly +# from the upstream host. +# https://www.bytereef.org/mpdecimal/ +MPDECIMAL_VERSION=4.0.1 +curl -fsSL "https://www.bytereef.org/software/mpdecimal/releases/mpdecimal-${MPDECIMAL_VERSION}.tar.gz" \ + | tar -xz -C /tmp +(cd "/tmp/mpdecimal-${MPDECIMAL_VERSION}" \ + && ./configure --prefix=/usr/local \ + && make -j"$(nproc)" \ + && make install) +ldconfig diff --git a/Doc/c-api/perfmaps.rst b/Doc/c-api/perfmaps.rst index 76a1e9f528d..bd05e628faa 100644 --- a/Doc/c-api/perfmaps.rst +++ b/Doc/c-api/perfmaps.rst @@ -31,7 +31,7 @@ Note that holding an :term:`attached thread state` is not required for these API or ``-2`` on failure to create a lock. Check ``errno`` for more information about the cause of a failure. -.. c:function:: int PyUnstable_WritePerfMapEntry(const void *code_addr, unsigned int code_size, const char *entry_name) +.. c:function:: int PyUnstable_WritePerfMapEntry(const void *code_addr, size_t code_size, const char *entry_name) Write one single entry to the ``/tmp/perf-$pid.map`` file. This function is thread safe. Here is what an example entry looks like:: diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 176e8f3f9f6..12d7acf5ce0 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -38,3 +38,8 @@ Pending removal in Python 3.20 - :mod:`zlib` (Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.) + +* :mod:`ast`: + + * Creating instances of abstract AST nodes (such as :class:`ast.AST` + or :class:`!ast.expr`) is deprecated and will raise an error in Python 3.20. diff --git a/Doc/faq/programming.rst b/Doc/faq/programming.rst index ff34bb5d71c..591565cbc01 100644 --- a/Doc/faq/programming.rst +++ b/Doc/faq/programming.rst @@ -1924,7 +1924,7 @@ correctly using identity tests: .. code-block:: python - _sentinel = object() + _sentinel = sentinel('_sentinel') def pop(self, key, default=_sentinel): if key in self: diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 6151143a97b..56bc799d945 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -39,10 +39,11 @@ Glossary ABCs with the :mod:`abc` module. annotate function - A function that can be called to retrieve the :term:`annotations ` - of an object. This function is accessible as the :attr:`~object.__annotate__` - attribute of functions, classes, and modules. Annotate functions are a - subset of :term:`evaluate functions `. + A callable that can be called to retrieve the :term:`annotations ` of + an object. Annotate functions are usually :term:`functions `, + automatically generated as the :attr:`~object.__annotate__` attribute of functions, + classes, and modules. Annotate functions are a subset of + :term:`evaluate functions `. annotation A label associated with a variable, a class diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index 9d5a9ac8b71..a7a68281860 100644 --- a/Doc/howto/descriptor.rst +++ b/Doc/howto/descriptor.rst @@ -594,7 +594,7 @@ a pure Python equivalent: def object_getattribute(obj, name): "Emulate PyObject_GenericGetAttr() in Objects/object.c" - null = object() + null = sentinel('null') objtype = type(obj) cls_var = find_name_in_mro(objtype, name, null) descr_get = getattr(type(cls_var), '__get__', null) @@ -1635,12 +1635,12 @@ by member descriptors: .. testcode:: - null = object() + null = sentinel('null') class Member: def __init__(self, name, clsname, offset): - 'Emulate PyMemberDef in Include/structmember.h' + 'Emulate PyMemberDef in Include/descrobject.h' # Also see descr_new() in Objects/descrobject.c self.name = name self.clsname = clsname diff --git a/Doc/howto/perf_profiling.rst b/Doc/howto/perf_profiling.rst index fc4772bbcca..653f28ddbab 100644 --- a/Doc/howto/perf_profiling.rst +++ b/Doc/howto/perf_profiling.rst @@ -217,8 +217,9 @@ Example, using the :mod:`sys` APIs in file :file:`example.py`: How to obtain the best results ------------------------------ -For best results, Python should be compiled with -``CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"`` as this allows +For best results, keep frame pointers enabled. On supported GCC-compatible +toolchains, CPython builds itself with ``-fno-omit-frame-pointer`` and, when +available, ``-mno-omit-leaf-frame-pointer`` by default. These flags allow profilers to unwind using only the frame pointer and not on DWARF debug information. This is because as the code that is interposed to allow ``perf`` support is dynamically generated it doesn't have any DWARF debugging information diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst index 40f2a6dc304..af28fe0e2fd 100644 --- a/Doc/library/annotationlib.rst +++ b/Doc/library/annotationlib.rst @@ -510,6 +510,81 @@ annotations from the class and puts them in a separate attribute: return typ +Creating a custom callable annotate function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Custom :term:`annotate functions ` may be literal functions like those +automatically generated for functions, classes, and modules. Or, they may wish to utilise +the encapsulation provided by classes, in which case any :term:`callable` can be used as +an :term:`annotate function`. + +To provide the :attr:`~Format.VALUE`, :attr:`~Format.STRING`, or +:attr:`~Format.FORWARDREF` formats directly, an :term:`annotate function` must provide +the following attribute: + +* A callable ``__call__`` with signature ``__call__(format, /) -> dict``, that does not + raise a :exc:`NotImplementedError` when called with a supported format. + +To provide the :attr:`~Format.VALUE_WITH_FAKE_GLOBALS` format, which is used to +automatically generate :attr:`~Format.STRING` or :attr:`~Format.FORWARDREF` if they are +not supported directly, :term:`annotate functions ` must provide the +following attributes: + +* A callable ``__call__`` with signature ``__call__(format, /) -> dict``, that does not + raise a :exc:`NotImplementedError` when called with + :attr:`~Format.VALUE_WITH_FAKE_GLOBALS`. +* A :ref:`code object ` ``__code__`` containing the compiled code for the + annotate function. +* Optional: A tuple of the function's positional defaults ``__kwdefaults__``, if the + function represented by ``__code__`` uses any positional defaults. +* Optional: A dict of the function's keyword defaults ``__defaults__``, if the function + represented by ``__code__`` uses any keyword defaults. +* Optional: All other :ref:`function attributes `. + +.. code-block:: python + + class Annotate: + called_formats = [] + + def __call__(self, format=None, /, *, _self=None): + # When called with fake globals, `_self` will be the + # actual self value, and `self` will be the format. + if _self is not None: + self, format = _self, self + + self.called_formats.append(format) + if format <= 2: # VALUE or VALUE_WITH_FAKE_GLOBALS + return {"x": MyType} + raise NotImplementedError + + __code__ = __call__.__code__ + __defaults__ = (None,) + __kwdefaults__ = property(lambda self: dict(_self=self)) + + __globals__ = {} + __builtins__ = {} + __closure__ = None + +This can then be called with: + +.. code-block:: pycon + + >>> from annotationlib import call_annotate_function, Format + >>> call_annotate_function(Annotate(), format=Format.STRING) + {'x': 'MyType'} + +Or used as the annotate function for an object: + +.. code-block:: pycon + + >>> from annotationlib import get_annotations, Format + >>> class C: + ... pass + >>> C.__annotate__ = Annotate() + >>> get_annotations(Annotate(), format=Format.STRING) + {'x': 'MyType'} + + Limitations of the ``STRING`` format ------------------------------------ diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 3c6e8745474..e10b8e22df0 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -42,7 +42,7 @@ Node classes .. class:: AST - This is the base of all AST node classes. The actual node classes are + This is the abstract base of all AST node classes. The actual node classes are derived from the :file:`Parser/Python.asdl` file, which is reproduced :ref:`above `. They are defined in the :mod:`!_ast` C module and re-exported in :mod:`!ast`. @@ -168,6 +168,15 @@ Node classes arguments that were set as attributes of the AST node, even if they did not match any of the fields of the AST node. These cases now raise a :exc:`TypeError`. +.. deprecated-removed:: next 3.20 + + In the :ref:`grammar above `, the AST node classes that + correspond to production rules with variants (aka "sums") are abstract + classes. Previous versions of Python allowed for the creation of direct + instances of these abstract node classes. This behavior is deprecated and + will be removed in Python 3.20. + + .. note:: The descriptions of the specific node classes displayed here were initially adapted from the fantastic `Green Tree @@ -271,18 +280,25 @@ Root nodes Literals ^^^^^^^^ -.. class:: Constant(value) +.. class:: Constant(value, kind) A constant value. The ``value`` attribute of the ``Constant`` literal contains the Python object it represents. The values represented can be instances of :class:`str`, :class:`bytes`, :class:`int`, :class:`float`, :class:`complex`, and :class:`bool`, and the constants :data:`None` and :data:`Ellipsis`. + The ``kind`` attribute is an optional string. For string literals with a + ``u`` prefix, ``kind`` is set to ``'u'``. For all other + constants, ``kind`` is ``None``. + .. doctest:: >>> print(ast.dump(ast.parse('123', mode='eval'), indent=4)) Expression( body=Constant(value=123)) + >>> print(ast.dump(ast.parse("u'hello'", mode='eval'), indent=4)) + Expression( + body=Constant(value='hello', kind='u')) .. class:: FormattedValue(value, conversion, format_spec) @@ -2536,6 +2552,20 @@ and classes for traversing abstract syntax trees: Added the *color* parameter. +.. function:: compare(a, b, /, *, compare_attributes=False) + + Recursively compares two ASTs. + + *compare_attributes* affects whether AST attributes are considered + in the comparison. If *compare_attributes* is ``False`` (default), then + attributes are ignored. Otherwise they must all be equal. This + option is useful to check whether the ASTs are structurally equal but + differ in whitespace or similar details. Attributes include line numbers + and column offsets. + + .. versionadded:: 3.14 + + .. _ast-compiler-flags: Compiler flags @@ -2571,20 +2601,6 @@ effects on the compilation of a program: .. versionadded:: 3.8 -.. function:: compare(a, b, /, *, compare_attributes=False) - - Recursively compares two ASTs. - - *compare_attributes* affects whether AST attributes are considered - in the comparison. If *compare_attributes* is ``False`` (default), then - attributes are ignored. Otherwise they must all be equal. This - option is useful to check whether the ASTs are structurally equal but - differ in whitespace or similar details. Attributes include line numbers - and column offsets. - - .. versionadded:: 3.14 - - .. _ast-cli: Command-line usage diff --git a/Doc/library/compression.zstd.rst b/Doc/library/compression.zstd.rst index 7ca843f27f5..6d99e36e1e5 100644 --- a/Doc/library/compression.zstd.rst +++ b/Doc/library/compression.zstd.rst @@ -331,10 +331,14 @@ Compressing and decompressing data in memory If *max_length* is non-negative, the method returns at most *max_length* bytes of decompressed data. If this limit is reached and further - output can be produced, the :attr:`~.needs_input` attribute will - be set to ``False``. In this case, the next call to + output can be produced (or EOF is reached), the :attr:`~.needs_input` + attribute will be set to ``False``. In this case, the next call to :meth:`~.decompress` may provide *data* as ``b''`` to obtain - more of the output. + more of the output. The full content can thus be read like:: + + process_output(d.decompress(data, max_length)) + while not d.eof and not d.needs_input: + process_output(d.decompress(b"", max_length)) If all of the input data was decompressed and returned (either because this was less than *max_length* bytes, or because diff --git a/Doc/library/email.policy.rst b/Doc/library/email.policy.rst index 8f6e4218c97..816d02d86f4 100644 --- a/Doc/library/email.policy.rst +++ b/Doc/library/email.policy.rst @@ -403,11 +403,26 @@ added matters. To illustrate:: .. attribute:: utf8 If ``False``, follow :rfc:`5322`, supporting non-ASCII characters in - headers by encoding them as "encoded words". If ``True``, follow - :rfc:`6532` and use ``utf-8`` encoding for headers. Messages + headers by encoding them as :rfc:`2047` "encoded words". If ``True``, + follow :rfc:`6532` and use ``utf-8`` encoding for headers. Messages formatted in this way may be passed to SMTP servers that support the ``SMTPUTF8`` extension (:rfc:`6531`). + When ``False``, the generator will raise + :exc:`~email.errors.HeaderWriteError` if any header includes non-ASCII + characters in a context where :rfc:`2047` does not permit encoded words. + This particularly applies to mailboxes ("addr-spec") with non-ASCII + characters, which can be created via + :class:`~email.headerregistry.Address`. To use a mailbox with a non-ASCII + domain name with ``utf8=False``, first encode the domain using the + third-party :pypi:`idna` or :pypi:`uts46` module or with + :mod:`encodings.idna`. It is not possible to use a non-ASCII username + ("local-part") in a mailbox when ``utf8=False``. + + .. versionchanged:: 3.15 + Can trigger the raising of :exc:`~email.errors.HeaderWriteError`. + (Earlier versions incorrectly applied :rfc:`2047` in certain contexts, + mostly notably in addr-specs.) .. attribute:: refold_source diff --git a/Doc/library/statistics.rst b/Doc/library/statistics.rst index cbb131855dc..dba0e26787d 100644 --- a/Doc/library/statistics.rst +++ b/Doc/library/statistics.rst @@ -713,7 +713,7 @@ However, for reading convenience, most of the examples show sorted sequences. .. function:: covariance(x, y, /) - Return the sample covariance of two inputs *x* and *y*. Covariance + Return the sample covariance of two sequence inputs *x* and *y*. Covariance is a measure of the joint variability of two inputs. Both inputs must be of the same length (no less than two), otherwise @@ -739,7 +739,7 @@ However, for reading convenience, most of the examples show sorted sequences. Return the `Pearson's correlation coefficient `_ - for two inputs. Pearson's correlation coefficient *r* takes values + for two sequence inputs. Pearson's correlation coefficient *r* takes values between -1 and +1. It measures the strength and direction of a linear relationship. @@ -802,7 +802,7 @@ However, for reading convenience, most of the examples show sorted sequences. (it is equal to the difference between predicted and actual values of the dependent variable). - Both inputs must be of the same length (no less than two), and + Both inputs must be sequences of the same length (no less than two), and the independent variable *x* cannot be constant; otherwise a :exc:`StatisticsError` is raised. diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index a86469bb9ad..6f1e01cf5aa 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -142,6 +142,10 @@ Some facts and figures: a Zstandard dictionary used to improve compression of smaller amounts of data. + For modes ``'w:gz'`` and ``'w|gz'``, :func:`tarfile.open` accepts the + keyword argument *mtime* to create a gzip archive header with that mtime. By + default, the mtime is set to the time of creation of the archive. + For special purposes, there is a second format for *mode*: ``'filemode|[compression]'``. :func:`tarfile.open` will return a :class:`TarFile` object that processes its data as a stream of blocks. No random seeking will diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 19cc4f191df..fbe3951e034 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -1436,3 +1436,159 @@ is equivalent to:: Currently, :class:`Lock`, :class:`RLock`, :class:`Condition`, :class:`Semaphore`, and :class:`BoundedSemaphore` objects may be used as :keyword:`with` statement context managers. + + +Iterator synchronization +------------------------ + +By default, Python iterators do not support concurrent access. Most iterators make +no guarantees when accessed simultaneously from multiple threads. Generator +iterators, for example, raise :exc:`ValueError` if one of their iterator methods +is called while the generator is already executing. The tools in this section +allow reliable concurrency support to be added to ordinary iterators and +iterator-producing callables. + +The :class:`serialize_iterator` wrapper lets multiple threads share a single iterator and +take turns consuming from it. While one thread is running ``__next__()``, the +others block until the iterator becomes available. Each value produced by the +underlying iterator is delivered to exactly one caller. + +The :func:`concurrent_tee` function lets multiple threads each receive the full +stream of values from one underlying iterator. It creates independent iterators +that all draw from the same source. Values are buffered until consumed by all +of the derived iterators. + +.. class:: serialize_iterator(iterable) + + Return an iterator wrapper that serializes concurrent calls to + :meth:`~iterator.__next__` using a lock. + + If the wrapped iterator also defines :meth:`~generator.send`, + :meth:`~generator.throw`, or :meth:`~generator.close`, those calls + are serialized as well. + + This makes it possible to share a single iterator, including a generator + iterator, between multiple threads. A lock ensures that calls are handled + one at a time. No values are duplicated or skipped by the wrapper itself. + Each item from the underlying iterator is given to exactly one caller. + + This wrapper does not copy or buffer values. Threads that call + :func:`next` while another thread is already advancing the iterator will + block until the active call completes. + + Example: + + .. code-block:: python + + import threading + + def squares(n): + for x in range(n): + yield x * x + + def consume(name, iterable): + for item in iterable: + print(name, item) + + source = threading.serialize_iterator(squares(5)) + + t1 = threading.Thread(target=consume, args=("left", source)) + t2 = threading.Thread(target=consume, args=("right", source)) + t1.start() + t2.start() + t1.join() + t2.join() + + In this example, each number is printed exactly once, but the work is shared + between the two threads. + + .. versionadded:: next + + +.. function:: synchronized_iterator(func) + + Wrap an iterator-producing callable so that each iterator it returns is + automatically passed through :class:`serialize_iterator`. + + This is especially useful as a :term:`decorator` for generator functions, + allowing their generator-iterators to be consumed from multiple threads. + + Example: + + .. code-block:: python + + import threading + + @threading.synchronized_iterator + def squares(n): + for x in range(n): + yield x * x + + def consume(name, iterable): + for item in iterable: + print(name, item) + + source = squares(5) + + t1 = threading.Thread(target=consume, args=("left", source)) + t2 = threading.Thread(target=consume, args=("right", source)) + t1.start() + t2.start() + t1.join() + t2.join() + + The returned wrapper preserves the metadata of *func*, such as its name and + wrapped function reference. + + .. versionadded:: next + + +.. function:: concurrent_tee(iterable, n=2) + + Return *n* independent iterators from a single input *iterable*, with + guaranteed behavior when the derived iterators are consumed concurrently. + + This function is similar to :func:`itertools.tee`, but is intended for cases + where the source iterator may feed consumers running in different threads. + Each returned iterator yields every value from the underlying iterable, in + the same order. + + Internally, values are buffered until every derived iterator has consumed + them. + + The returned iterators share the same underlying synchronization lock. Each + individual derived iterator is intended to be consumed by one thread at a + time. If a single derived iterator must itself be shared by multiple + threads, wrap it with :class:`serialize_iterator`. + + If *n* is ``0``, return an empty tuple. If *n* is negative, raise + :exc:`ValueError`. + + Example: + + .. code-block:: python + + import threading + + def squares(n): + for x in range(n): + yield x * x + + def consume(name, iterable): + for item in iterable: + print(name, item) + + source = squares(5) + left, right = threading.concurrent_tee(source) + + t1 = threading.Thread(target=consume, args=("left", left)) + t2 = threading.Thread(target=consume, args=("right", right)) + t1.start() + t2.start() + t1.join() + t2.join() + + In this example, both consumer threads see the full sequence of squares + from a single generator expression. + + .. versionadded:: next diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 1957cadcbb1..17cf57dd00b 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2361,6 +2361,12 @@ without the dedicated syntax, as documented below. >>> Alias.__module__ '__main__' + This attribute is writable. + + .. versionchanged:: 3.15 + + The attribute is now writable. + .. attribute:: __type_params__ The type parameters of the type alias, or an empty tuple if the alias is diff --git a/Doc/library/zlib.rst b/Doc/library/zlib.rst index ce0a22b9456..f043915c0f4 100644 --- a/Doc/library/zlib.rst +++ b/Doc/library/zlib.rst @@ -308,6 +308,11 @@ Decompression objects support the following methods and attributes: :attr:`unconsumed_tail`. This bytestring must be passed to a subsequent call to :meth:`decompress` if decompression is to continue. If *max_length* is zero then the whole input is decompressed, and :attr:`unconsumed_tail` is empty. + For example, the full content could be read like:: + + process_output(d.decompress(data, max_length)) + while chunk := d.decompress(d.unconsumed_tail, max_length): + process_output(chunk) .. versionchanged:: 3.6 *max_length* can be used as a keyword argument. diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index d5c17560b66..086f6bfa22a 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -780,6 +780,24 @@ also be used to improve performance. .. versionadded:: 3.14 +.. option:: --without-frame-pointers + + Disable frame pointers, which are enabled by default (see :pep:`831`). + + By default, the build appends ``-fno-omit-frame-pointer`` (and + ``-mno-omit-leaf-frame-pointer`` when the compiler supports it) to + ``BASECFLAGS`` so profilers, debuggers, and system tracing tools + (``perf``, ``eBPF``, ``dtrace``, ``gdb``) can walk the C call stack + without DWARF metadata. The flags propagate to third-party C + extensions through :mod:`sysconfig`. On compilers that do not + understand them, the build silently skips them. + + Downstream packagers and authors of native libraries built with + custom build systems should set the same flags so the unwind chain + stays unbroken across all native frames. + + .. versionadded:: 3.15 + .. option:: --without-mimalloc Disable the fast :ref:`mimalloc ` allocator diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index dfdfe66be7e..0bb8858aea1 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -953,10 +953,24 @@ when a module is imported) will still emit the syntax warning. (Contributed by Irit Katriel in :gh:`130080`.) +.. _incremental-garbage-collection: .. _whatsnew314-incremental-gc: -Incremental garbage collection ------------------------------- +Garbage collection +------------------ + +**From Python 3.14.5 onwards:** + +The garbage collector (GC) has changed in Python 3.14.5. + +Python 3.14.0-3.14.4 shipped with a new incremental GC. +However, due to a number of `reports +`__ +of significant memory pressure in production environments, +it has been reverted back to the generational GC from 3.13. +This is the GC now used in Python 3.14.5 and later. + +**Previously in Python 3.14.0-3.14.4:** The cycle garbage collector is now incremental. This means that maximum pause times are reduced @@ -2203,7 +2217,18 @@ difflib gc -- -* The new :ref:`incremental garbage collector ` +* **From Python 3.14.5 onwards:** + + Python 3.14.0-3.14.4 shipped with a new incremental garbage collector. + However, due to a number of `reports + `__ + of significant memory pressure in production environments, + it has been reverted back to the generational GC from 3.13. + This is the GC now used in Python 3.14.5 and later. + +* **Previously in Python 3.14.0-3.14.4:** + + The new :ref:`incremental garbage collector ` means that maximum pause times are reduced by an order of magnitude or more for larger heaps. @@ -3447,3 +3472,17 @@ Changes in the C API functions on Python 3.13 and older. .. _pythoncapi-compat project: https://github.com/python/pythoncapi-compat/ + + +Notable changes in 3.14.5 +========================= + +gc +-- + +* The incremental garbage collector shipped in Python 3.14.0-3.14.4 has been + reverted back to the generational garbage collector from 3.13, + due to a number of `reports + `__ + of significant memory pressure in production environments. + See :ref:`whatsnew314-incremental-gc` for details. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index a687ee5115b..78e464f2a5a 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -86,6 +86,7 @@ Summary -- Release highlights * :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object ` * :pep:`803`: :ref:`Stable ABI for Free-Threaded Builds ` +* :pep:`831`: :ref:`Frame pointers everywhere ` * :ref:`The JIT compiler has been significantly upgraded ` * :ref:`Improved error messages ` * :ref:`The official Windows 64-bit binaries now use the tail-calling interpreter @@ -914,6 +915,16 @@ faulthandler (Contributed by Eric Froemling in :gh:`149085`.) +email +----- + +* Email generators now raise an error when an :class:`.EmailMessage` cannot be + accurately flattened due to a non-ASCII email address (mailbox) in an address + header. Options for supporting Email Address Internationalization (EAI) are + discussed in :attr:`.EmailPolicy.utf8`. + (Contributed by R David Murray and Mike Edmunds in :gh:`122540`.) + + functools --------- @@ -1268,6 +1279,16 @@ tarfile (Contributed by Christoph Walcher in :gh:`57911`.) +threading +--------- + +* Added :class:`~threading.serialize_iterator`, + :func:`~threading.synchronized_iterator`, + and :func:`~threading.concurrent_tee` to support concurrent access to + generators and iterators. + (Contributed by Raymond Hettinger in :gh:`124397`.) + + timeit ------ @@ -1579,11 +1600,11 @@ Upgraded JIT compiler Results from the `pyperformance `__ benchmark suite report -`6-7% `__ +`8-9% `__ geometric mean performance improvement for the JIT over the standard CPython interpreter built with all optimizations enabled on x86-64 Linux. On AArch64 macOS, the JIT has a -`12-13% `__ +`12-13% `__ speedup over the :ref:`tail calling interpreter ` with all optimizations enabled. The speedups for JIT builds versus no JIT builds range from roughly 15% slowdown to over @@ -1825,6 +1846,13 @@ Deprecated New deprecations ---------------- +* :mod:`ast` + + * Creating instances of abstract AST nodes (such as :class:`ast.AST` + or :class:`!ast.expr`) is deprecated and will raise an error in Python 3.20. + + (Contributed by Brian Schubert in :gh:`116021`.) + * :mod:`base64`: * Accepting the ``+`` and ``/`` characters with an alternative alphabet in @@ -2252,6 +2280,16 @@ Build changes and :option:`-X dev <-X>` is passed to the Python or Python is built in :ref:`debug mode `. (Contributed by Donghee Na in :gh:`141770`.) +.. _whatsnew315-frame-pointers: + +* CPython is now built with frame pointers enabled by default + (:pep:`831`). Pass :option:`--without-frame-pointers` to opt out. + Authors of C extensions and native libraries built with custom build + systems should add ``-fno-omit-frame-pointer`` and + ``-mno-omit-leaf-frame-pointer`` to their own ``CFLAGS`` to keep the + unwind chain intact. + (Contributed by Pablo Galindo Salgado and Savannah Ostrowski in :gh:`149201`.) + .. _whatsnew315-windows-tail-calling-interpreter: * 64-bit builds using Visual Studio 2026 (MSVC 18) may now use the new diff --git a/Include/cpython/ceval.h b/Include/cpython/ceval.h index bbab8d35b75..5b66fa1040d 100644 --- a/Include/cpython/ceval.h +++ b/Include/cpython/ceval.h @@ -38,7 +38,7 @@ typedef struct { PyAPI_FUNC(int) PyUnstable_PerfMapState_Init(void); PyAPI_FUNC(int) PyUnstable_WritePerfMapEntry( const void *code_addr, - unsigned int code_size, + size_t code_size, const char *entry_name); PyAPI_FUNC(void) PyUnstable_PerfMapState_Fini(void); PyAPI_FUNC(int) PyUnstable_CopyPerfMapFile(const char* parent_filename); diff --git a/Include/internal/pycore_ast_state.h b/Include/internal/pycore_ast_state.h index 1caf200ee34..32c12fb5875 100644 --- a/Include/internal/pycore_ast_state.h +++ b/Include/internal/pycore_ast_state.h @@ -161,6 +161,7 @@ struct ast_state { PyObject *__module__; PyObject *_attributes; PyObject *_fields; + PyObject *abstract_types; PyObject *alias_type; PyObject *annotation; PyObject *arg; diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index f9507fda160..fd4221f0816 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -94,7 +94,7 @@ typedef struct { void* (*init_state)(void); // Callback to register every trampoline being created void (*write_state)(void* state, const void *code_addr, - unsigned int code_size, PyCodeObject* code); + size_t code_size, PyCodeObject* code); // Callback to free the trampoline state int (*free_state)(void* state); } _PyPerf_Callbacks; @@ -108,6 +108,10 @@ extern PyStatus _PyPerfTrampoline_AfterFork_Child(void); #ifdef PY_HAVE_PERF_TRAMPOLINE extern _PyPerf_Callbacks _Py_perfmap_callbacks; extern _PyPerf_Callbacks _Py_perfmap_jit_callbacks; +extern void _PyPerfJit_WriteNamedCode(const void *code_addr, + size_t code_size, + const char *entry, + const char *filename); #endif static inline PyObject* diff --git a/Include/internal/pycore_debug_offsets.h b/Include/internal/pycore_debug_offsets.h index c166f963da4..1dd10f8d94c 100644 --- a/Include/internal/pycore_debug_offsets.h +++ b/Include/internal/pycore_debug_offsets.h @@ -215,6 +215,7 @@ typedef struct _Py_DebugOffsets { uint64_t state; uint64_t length; uint64_t asciiobject_size; + uint64_t compactunicodeobject_size; } unicode_object; // GC runtime state offset; @@ -370,6 +371,7 @@ typedef struct _Py_DebugOffsets { .state = offsetof(PyUnicodeObject, _base._base.state), \ .length = offsetof(PyUnicodeObject, _base._base.length), \ .asciiobject_size = sizeof(PyASCIIObject), \ + .compactunicodeobject_size = sizeof(PyCompactUnicodeObject), \ }, \ .gc = { \ .size = sizeof(struct _gc_runtime_state), \ diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 4d6d5ce9c5e..f7d3dcd440a 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1582,6 +1582,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(alias)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(align)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all_interpreters)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all_threads)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(allow_code)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(alphabet)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 20dcf81ccf1..22494b1798c 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -305,6 +305,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(alias) STRUCT_FOR_ID(align) STRUCT_FOR_ID(all) + STRUCT_FOR_ID(all_interpreters) STRUCT_FOR_ID(all_threads) STRUCT_FOR_ID(allow_code) STRUCT_FOR_ID(alphabet) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index cccfe3565db..86f018e3286 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -69,7 +69,7 @@ struct code_arena_st; struct trampoline_api_st { void* (*init_state)(void); void (*write_state)(void* state, const void *code_addr, - unsigned int code_size, PyCodeObject* code); + size_t code_size, PyCodeObject* code); int (*free_state)(void* state); void *state; Py_ssize_t code_padding; diff --git a/Include/internal/pycore_jit.h b/Include/internal/pycore_jit.h index b3cadcce824..2f97cc26eaf 100644 --- a/Include/internal/pycore_jit.h +++ b/Include/internal/pycore_jit.h @@ -23,7 +23,7 @@ typedef _Py_CODEUNIT *(*jit_func)( _PyStackRef _tos_cache0, _PyStackRef _tos_cache1, _PyStackRef _tos_cache2 ); -_Py_CODEUNIT *_PyJIT( +_Py_CODEUNIT *_PyJIT_Entry( _PyExecutorObject *executor, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate ); diff --git a/Include/internal/pycore_jit_unwind.h b/Include/internal/pycore_jit_unwind.h new file mode 100644 index 00000000000..2d325ad9562 --- /dev/null +++ b/Include/internal/pycore_jit_unwind.h @@ -0,0 +1,68 @@ +#ifndef Py_INTERNAL_JIT_UNWIND_H +#define Py_INTERNAL_JIT_UNWIND_H + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#include +#include + +#if defined(_Py_JIT) && defined(__linux__) && defined(__ELF__) +# define PY_HAVE_JIT_GDB_UNWIND +#endif + +#if defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND) + +#if defined(PY_HAVE_JIT_GDB_UNWIND) +extern PyMutex _Py_jit_debug_mutex; +#endif + +/* DWARF exception-handling pointer encodings shared by JIT unwind users. */ +enum { + DWRF_EH_PE_absptr = 0x00, + DWRF_EH_PE_omit = 0xff, + + /* Data type encodings */ + DWRF_EH_PE_uleb128 = 0x01, + DWRF_EH_PE_udata2 = 0x02, + DWRF_EH_PE_udata4 = 0x03, + DWRF_EH_PE_udata8 = 0x04, + DWRF_EH_PE_sleb128 = 0x09, + DWRF_EH_PE_sdata2 = 0x0a, + DWRF_EH_PE_sdata4 = 0x0b, + DWRF_EH_PE_sdata8 = 0x0c, + DWRF_EH_PE_signed = 0x08, + + /* Reference type encodings */ + DWRF_EH_PE_pcrel = 0x10, + DWRF_EH_PE_textrel = 0x20, + DWRF_EH_PE_datarel = 0x30, + DWRF_EH_PE_funcrel = 0x40, + DWRF_EH_PE_aligned = 0x50, + DWRF_EH_PE_indirect = 0x80 +}; + +/* Return the size of the generated .eh_frame data for the given encoding. */ +size_t _PyJitUnwind_EhFrameSize(int absolute_addr); + +/* + * Build DWARF .eh_frame data for JIT code; returns size written or 0 on error. + * absolute_addr selects the FDE address encoding: + * - 0: PC-relative offsets (perf jitdump synthesized DSO). + * - nonzero: absolute addresses (GDB JIT in-memory ELF). + */ +size_t _PyJitUnwind_BuildEhFrame(uint8_t *buffer, size_t buffer_size, + const void *code_addr, size_t code_size, + int absolute_addr); + +void *_PyJitUnwind_GdbRegisterCode(const void *code_addr, + size_t code_size, + const char *entry, + const char *filename); + +void _PyJitUnwind_GdbUnregisterCode(void *handle); + +#endif // defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND) + +#endif // Py_INTERNAL_JIT_UNWIND_H diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 230335ead3a..4f8b48db23d 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1236,7 +1236,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [LIST_APPEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, [LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG }, [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG }, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_SYNC_SP_FLAG | HAS_NEEDS_GUARD_IP_FLAG | HAS_RECORDS_VALUE_FLAG }, [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG }, @@ -1460,8 +1460,8 @@ _PyOpcode_macro_expansion[256] = { [LIST_APPEND] = { .nuops = 1, .uops = { { _LIST_APPEND, OPARG_SIMPLE, 0 } } }, [LIST_EXTEND] = { .nuops = 2, .uops = { { _LIST_EXTEND, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 } } }, [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, OPARG_SIMPLE, 8 } } }, - [LOAD_ATTR_CLASS] = { .nuops = 3, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, - [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_CLASS, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, + [LOAD_ATTR_CLASS] = { .nuops = 4, .uops = { { _RECORD_TOS, OPARG_SIMPLE, 1 }, { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, + [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 5, .uops = { { _RECORD_TOS, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_CLASS, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { .nuops = 7, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_PEP_523, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME, 2, 3 }, { _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME, OPERAND1_4, 5 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 9 }, { _PUSH_FRAME, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 6, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 4, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 1, 3 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } }, diff --git a/Include/internal/pycore_opcode_utils.h b/Include/internal/pycore_opcode_utils.h index 1e71784c809..3e2c4ae411c 100644 --- a/Include/internal/pycore_opcode_utils.h +++ b/Include/internal/pycore_opcode_utils.h @@ -75,7 +75,12 @@ extern "C" { #define CONSTANT_BUILTIN_ANY 4 #define CONSTANT_BUILTIN_LIST 5 #define CONSTANT_BUILTIN_SET 6 -#define NUM_COMMON_CONSTANTS 7 +#define CONSTANT_NONE 7 +#define CONSTANT_EMPTY_STR 8 +#define CONSTANT_TRUE 9 +#define CONSTANT_FALSE 10 +#define CONSTANT_MINUS_ONE 11 +#define NUM_COMMON_CONSTANTS 12 /* Values used in the oparg for RESUME */ #define RESUME_AT_FUNC_START 0 diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index f356d60ae5c..a0727c045e5 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -198,6 +198,7 @@ typedef struct _PyExecutorObject { uint32_t code_size; size_t jit_size; void *jit_code; + void *jit_gdb_handle; _PyExitData exits[1]; } _PyExecutorObject; diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 1ce91dc51ea..892c3cdd962 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1580,6 +1580,7 @@ extern "C" { INIT_ID(alias), \ INIT_ID(align), \ INIT_ID(all), \ + INIT_ID(all_interpreters), \ INIT_ID(all_threads), \ INIT_ID(allow_code), \ INIT_ID(alphabet), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index c7c23494845..f0fc3c4f5b0 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1000,6 +1000,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(all_interpreters); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(all_threads); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 9e54cb5f06a..bd1440a89bd 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -375,1040 +375,1045 @@ extern "C" { #define _RESUME_CHECK 601 #define _RETURN_GENERATOR RETURN_GENERATOR #define _RETURN_VALUE 602 -#define _SAVE_RETURN_OFFSET 603 -#define _SEND 604 -#define _SEND_GEN_FRAME 605 +#define _RROT_3 603 +#define _SAVE_RETURN_OFFSET 604 +#define _SEND 605 +#define _SEND_GEN_FRAME 606 #define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS #define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE -#define _SET_UPDATE 606 -#define _SPILL_OR_RELOAD 607 -#define _START_EXECUTOR 608 -#define _STORE_ATTR 609 -#define _STORE_ATTR_INSTANCE_VALUE 610 -#define _STORE_ATTR_SLOT 611 -#define _STORE_ATTR_WITH_HINT 612 +#define _SET_UPDATE 607 +#define _SPILL_OR_RELOAD 608 +#define _START_EXECUTOR 609 +#define _STORE_ATTR 610 +#define _STORE_ATTR_INSTANCE_VALUE 611 +#define _STORE_ATTR_SLOT 612 +#define _STORE_ATTR_WITH_HINT 613 #define _STORE_DEREF STORE_DEREF #define _STORE_GLOBAL STORE_GLOBAL #define _STORE_NAME STORE_NAME -#define _STORE_SLICE 613 -#define _STORE_SUBSCR 614 -#define _STORE_SUBSCR_DICT 615 -#define _STORE_SUBSCR_DICT_KNOWN_HASH 616 -#define _STORE_SUBSCR_LIST_INT 617 -#define _SWAP 618 -#define _SWAP_2 619 -#define _SWAP_3 620 -#define _SWAP_FAST 621 -#define _SWAP_FAST_0 622 -#define _SWAP_FAST_1 623 -#define _SWAP_FAST_2 624 -#define _SWAP_FAST_3 625 -#define _SWAP_FAST_4 626 -#define _SWAP_FAST_5 627 -#define _SWAP_FAST_6 628 -#define _SWAP_FAST_7 629 -#define _TIER2_RESUME_CHECK 630 -#define _TO_BOOL 631 +#define _STORE_SLICE 614 +#define _STORE_SUBSCR 615 +#define _STORE_SUBSCR_DICT 616 +#define _STORE_SUBSCR_DICT_KNOWN_HASH 617 +#define _STORE_SUBSCR_LIST_INT 618 +#define _SWAP 619 +#define _SWAP_2 620 +#define _SWAP_3 621 +#define _SWAP_FAST 622 +#define _SWAP_FAST_0 623 +#define _SWAP_FAST_1 624 +#define _SWAP_FAST_2 625 +#define _SWAP_FAST_3 626 +#define _SWAP_FAST_4 627 +#define _SWAP_FAST_5 628 +#define _SWAP_FAST_6 629 +#define _SWAP_FAST_7 630 +#define _TIER2_RESUME_CHECK 631 +#define _TO_BOOL 632 #define _TO_BOOL_BOOL TO_BOOL_BOOL -#define _TO_BOOL_INT 632 -#define _TO_BOOL_LIST 633 +#define _TO_BOOL_INT 633 +#define _TO_BOOL_LIST 634 #define _TO_BOOL_NONE TO_BOOL_NONE -#define _TO_BOOL_STR 634 +#define _TO_BOOL_STR 635 #define _TRACE_RECORD TRACE_RECORD -#define _UNARY_INVERT 635 -#define _UNARY_NEGATIVE 636 -#define _UNARY_NEGATIVE_FLOAT_INPLACE 637 +#define _UNARY_INVERT 636 +#define _UNARY_NEGATIVE 637 +#define _UNARY_NEGATIVE_FLOAT_INPLACE 638 #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 638 -#define _UNPACK_SEQUENCE_LIST 639 -#define _UNPACK_SEQUENCE_TUPLE 640 -#define _UNPACK_SEQUENCE_TWO_TUPLE 641 -#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE 642 -#define _UNPACK_SEQUENCE_UNIQUE_TUPLE 643 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE 644 +#define _UNPACK_SEQUENCE 639 +#define _UNPACK_SEQUENCE_LIST 640 +#define _UNPACK_SEQUENCE_TUPLE 641 +#define _UNPACK_SEQUENCE_TWO_TUPLE 642 +#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE 643 +#define _UNPACK_SEQUENCE_UNIQUE_TUPLE 644 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE 645 #define _WITH_EXCEPT_START WITH_EXCEPT_START -#define _YIELD_VALUE 645 -#define MAX_UOP_ID 645 -#define _ALLOCATE_OBJECT_r00 646 -#define _BINARY_OP_r23 647 -#define _BINARY_OP_ADD_FLOAT_r03 648 -#define _BINARY_OP_ADD_FLOAT_r13 649 -#define _BINARY_OP_ADD_FLOAT_r23 650 -#define _BINARY_OP_ADD_FLOAT_INPLACE_r03 651 -#define _BINARY_OP_ADD_FLOAT_INPLACE_r13 652 -#define _BINARY_OP_ADD_FLOAT_INPLACE_r23 653 -#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r03 654 -#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r13 655 -#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r23 656 -#define _BINARY_OP_ADD_INT_r03 657 -#define _BINARY_OP_ADD_INT_r13 658 -#define _BINARY_OP_ADD_INT_r23 659 -#define _BINARY_OP_ADD_INT_INPLACE_r03 660 -#define _BINARY_OP_ADD_INT_INPLACE_r13 661 -#define _BINARY_OP_ADD_INT_INPLACE_r23 662 -#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r03 663 -#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r13 664 -#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r23 665 -#define _BINARY_OP_ADD_UNICODE_r03 666 -#define _BINARY_OP_ADD_UNICODE_r13 667 -#define _BINARY_OP_ADD_UNICODE_r23 668 -#define _BINARY_OP_EXTEND_r23 669 -#define _BINARY_OP_INPLACE_ADD_UNICODE_r21 670 -#define _BINARY_OP_MULTIPLY_FLOAT_r03 671 -#define _BINARY_OP_MULTIPLY_FLOAT_r13 672 -#define _BINARY_OP_MULTIPLY_FLOAT_r23 673 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r03 674 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r13 675 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r23 676 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r03 677 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r13 678 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r23 679 -#define _BINARY_OP_MULTIPLY_INT_r03 680 -#define _BINARY_OP_MULTIPLY_INT_r13 681 -#define _BINARY_OP_MULTIPLY_INT_r23 682 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_r03 683 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_r13 684 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_r23 685 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r03 686 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r13 687 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r23 688 -#define _BINARY_OP_SUBSCR_CHECK_FUNC_r23 689 -#define _BINARY_OP_SUBSCR_DICT_r23 690 -#define _BINARY_OP_SUBSCR_DICT_KNOWN_HASH_r23 691 -#define _BINARY_OP_SUBSCR_INIT_CALL_r01 692 -#define _BINARY_OP_SUBSCR_INIT_CALL_r11 693 -#define _BINARY_OP_SUBSCR_INIT_CALL_r21 694 -#define _BINARY_OP_SUBSCR_INIT_CALL_r31 695 -#define _BINARY_OP_SUBSCR_LIST_INT_r23 696 -#define _BINARY_OP_SUBSCR_LIST_SLICE_r23 697 -#define _BINARY_OP_SUBSCR_STR_INT_r23 698 -#define _BINARY_OP_SUBSCR_TUPLE_INT_r03 699 -#define _BINARY_OP_SUBSCR_TUPLE_INT_r13 700 -#define _BINARY_OP_SUBSCR_TUPLE_INT_r23 701 -#define _BINARY_OP_SUBSCR_USTR_INT_r23 702 -#define _BINARY_OP_SUBTRACT_FLOAT_r03 703 -#define _BINARY_OP_SUBTRACT_FLOAT_r13 704 -#define _BINARY_OP_SUBTRACT_FLOAT_r23 705 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r03 706 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r13 707 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r23 708 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r03 709 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r13 710 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r23 711 -#define _BINARY_OP_SUBTRACT_INT_r03 712 -#define _BINARY_OP_SUBTRACT_INT_r13 713 -#define _BINARY_OP_SUBTRACT_INT_r23 714 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_r03 715 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_r13 716 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_r23 717 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r03 718 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r13 719 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r23 720 -#define _BINARY_OP_TRUEDIV_FLOAT_r23 721 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r03 722 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r13 723 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r23 724 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r03 725 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r13 726 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r23 727 -#define _BINARY_SLICE_r31 728 -#define _BUILD_INTERPOLATION_r01 729 -#define _BUILD_LIST_r01 730 -#define _BUILD_MAP_r01 731 -#define _BUILD_SET_r01 732 -#define _BUILD_SLICE_r01 733 -#define _BUILD_STRING_r01 734 -#define _BUILD_TEMPLATE_r21 735 -#define _BUILD_TUPLE_r01 736 -#define _CALL_BUILTIN_CLASS_r00 737 -#define _CALL_BUILTIN_FAST_r00 738 -#define _CALL_BUILTIN_FAST_WITH_KEYWORDS_r00 739 -#define _CALL_BUILTIN_O_r03 740 -#define _CALL_FUNCTION_EX_NON_PY_GENERAL_r31 741 -#define _CALL_INTRINSIC_1_r12 742 -#define _CALL_INTRINSIC_2_r23 743 -#define _CALL_ISINSTANCE_r31 744 -#define _CALL_KW_NON_PY_r11 745 -#define _CALL_LEN_r33 746 -#define _CALL_LIST_APPEND_r03 747 -#define _CALL_LIST_APPEND_r13 748 -#define _CALL_LIST_APPEND_r23 749 -#define _CALL_LIST_APPEND_r33 750 -#define _CALL_METHOD_DESCRIPTOR_FAST_r00 751 -#define _CALL_METHOD_DESCRIPTOR_FAST_INLINE_r00 752 -#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r00 753 -#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_INLINE_r00 754 -#define _CALL_METHOD_DESCRIPTOR_NOARGS_r03 755 -#define _CALL_METHOD_DESCRIPTOR_NOARGS_INLINE_r03 756 -#define _CALL_METHOD_DESCRIPTOR_O_r03 757 -#define _CALL_METHOD_DESCRIPTOR_O_INLINE_r03 758 -#define _CALL_NON_PY_GENERAL_r01 759 -#define _CALL_STR_1_r32 760 -#define _CALL_TUPLE_1_r32 761 -#define _CALL_TYPE_1_r02 762 -#define _CALL_TYPE_1_r12 763 -#define _CALL_TYPE_1_r22 764 -#define _CALL_TYPE_1_r32 765 -#define _CHECK_ATTR_CLASS_r01 766 -#define _CHECK_ATTR_CLASS_r11 767 -#define _CHECK_ATTR_CLASS_r22 768 -#define _CHECK_ATTR_CLASS_r33 769 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r01 770 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r11 771 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r22 772 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r33 773 -#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS_r00 774 -#define _CHECK_EG_MATCH_r22 775 -#define _CHECK_EXC_MATCH_r22 776 -#define _CHECK_FUNCTION_EXACT_ARGS_r00 777 -#define _CHECK_FUNCTION_VERSION_r00 778 -#define _CHECK_FUNCTION_VERSION_INLINE_r00 779 -#define _CHECK_FUNCTION_VERSION_INLINE_r11 780 -#define _CHECK_FUNCTION_VERSION_INLINE_r22 781 -#define _CHECK_FUNCTION_VERSION_INLINE_r33 782 -#define _CHECK_FUNCTION_VERSION_KW_r11 783 -#define _CHECK_IS_NOT_PY_CALLABLE_r00 784 -#define _CHECK_IS_NOT_PY_CALLABLE_EX_r03 785 -#define _CHECK_IS_NOT_PY_CALLABLE_EX_r13 786 -#define _CHECK_IS_NOT_PY_CALLABLE_EX_r23 787 -#define _CHECK_IS_NOT_PY_CALLABLE_EX_r33 788 -#define _CHECK_IS_NOT_PY_CALLABLE_KW_r11 789 -#define _CHECK_IS_PY_CALLABLE_EX_r03 790 -#define _CHECK_IS_PY_CALLABLE_EX_r13 791 -#define _CHECK_IS_PY_CALLABLE_EX_r23 792 -#define _CHECK_IS_PY_CALLABLE_EX_r33 793 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r01 794 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r11 795 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r22 796 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r33 797 -#define _CHECK_METHOD_VERSION_r00 798 -#define _CHECK_METHOD_VERSION_KW_r11 799 -#define _CHECK_OBJECT_r00 800 -#define _CHECK_PEP_523_r00 801 -#define _CHECK_PEP_523_r11 802 -#define _CHECK_PEP_523_r22 803 -#define _CHECK_PEP_523_r33 804 -#define _CHECK_PERIODIC_r00 805 -#define _CHECK_PERIODIC_AT_END_r00 806 -#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM_r00 807 -#define _CHECK_RECURSION_LIMIT_r00 808 -#define _CHECK_RECURSION_LIMIT_r11 809 -#define _CHECK_RECURSION_LIMIT_r22 810 -#define _CHECK_RECURSION_LIMIT_r33 811 -#define _CHECK_RECURSION_REMAINING_r00 812 -#define _CHECK_RECURSION_REMAINING_r11 813 -#define _CHECK_RECURSION_REMAINING_r22 814 -#define _CHECK_RECURSION_REMAINING_r33 815 -#define _CHECK_STACK_SPACE_r00 816 -#define _CHECK_STACK_SPACE_OPERAND_r00 817 -#define _CHECK_STACK_SPACE_OPERAND_r11 818 -#define _CHECK_STACK_SPACE_OPERAND_r22 819 -#define _CHECK_STACK_SPACE_OPERAND_r33 820 -#define _CHECK_VALIDITY_r00 821 -#define _CHECK_VALIDITY_r11 822 -#define _CHECK_VALIDITY_r22 823 -#define _CHECK_VALIDITY_r33 824 -#define _COLD_DYNAMIC_EXIT_r00 825 -#define _COLD_EXIT_r00 826 -#define _COMPARE_OP_r21 827 -#define _COMPARE_OP_FLOAT_r03 828 -#define _COMPARE_OP_FLOAT_r13 829 -#define _COMPARE_OP_FLOAT_r23 830 -#define _COMPARE_OP_INT_r23 831 -#define _COMPARE_OP_STR_r23 832 -#define _CONTAINS_OP_r23 833 -#define _CONTAINS_OP_DICT_r23 834 -#define _CONTAINS_OP_SET_r23 835 -#define _CONVERT_VALUE_r11 836 -#define _COPY_r01 837 -#define _COPY_1_r02 838 -#define _COPY_1_r12 839 -#define _COPY_1_r23 840 -#define _COPY_2_r03 841 -#define _COPY_2_r13 842 -#define _COPY_2_r23 843 -#define _COPY_3_r03 844 -#define _COPY_3_r13 845 -#define _COPY_3_r23 846 -#define _COPY_3_r33 847 -#define _COPY_FREE_VARS_r00 848 -#define _COPY_FREE_VARS_r11 849 -#define _COPY_FREE_VARS_r22 850 -#define _COPY_FREE_VARS_r33 851 -#define _CREATE_INIT_FRAME_r01 852 -#define _DELETE_ATTR_r10 853 -#define _DELETE_DEREF_r00 854 -#define _DELETE_FAST_r00 855 -#define _DELETE_GLOBAL_r00 856 -#define _DELETE_NAME_r00 857 -#define _DELETE_SUBSCR_r20 858 -#define _DEOPT_r00 859 -#define _DEOPT_r10 860 -#define _DEOPT_r20 861 -#define _DEOPT_r30 862 -#define _DICT_MERGE_r11 863 -#define _DICT_UPDATE_r11 864 -#define _DO_CALL_r01 865 -#define _DO_CALL_FUNCTION_EX_r31 866 -#define _DO_CALL_KW_r11 867 -#define _DYNAMIC_EXIT_r00 868 -#define _DYNAMIC_EXIT_r10 869 -#define _DYNAMIC_EXIT_r20 870 -#define _DYNAMIC_EXIT_r30 871 -#define _END_FOR_r10 872 -#define _END_SEND_r31 873 -#define _ERROR_POP_N_r00 874 -#define _EXIT_INIT_CHECK_r10 875 -#define _EXIT_TRACE_r00 876 -#define _EXIT_TRACE_r10 877 -#define _EXIT_TRACE_r20 878 -#define _EXIT_TRACE_r30 879 -#define _EXPAND_METHOD_r00 880 -#define _EXPAND_METHOD_KW_r11 881 -#define _FATAL_ERROR_r00 882 -#define _FATAL_ERROR_r11 883 -#define _FATAL_ERROR_r22 884 -#define _FATAL_ERROR_r33 885 -#define _FORMAT_SIMPLE_r11 886 -#define _FORMAT_WITH_SPEC_r21 887 -#define _FOR_ITER_r23 888 -#define _FOR_ITER_GEN_FRAME_r03 889 -#define _FOR_ITER_GEN_FRAME_r13 890 -#define _FOR_ITER_GEN_FRAME_r23 891 -#define _FOR_ITER_TIER_TWO_r23 892 -#define _FOR_ITER_VIRTUAL_r23 893 -#define _FOR_ITER_VIRTUAL_TIER_TWO_r23 894 -#define _GET_AITER_r11 895 -#define _GET_ANEXT_r12 896 -#define _GET_AWAITABLE_r11 897 -#define _GET_ITER_r12 898 -#define _GET_ITER_TRAD_r12 899 -#define _GET_LEN_r12 900 -#define _GUARD_BINARY_OP_EXTEND_r22 901 -#define _GUARD_BINARY_OP_EXTEND_LHS_r02 902 -#define _GUARD_BINARY_OP_EXTEND_LHS_r12 903 -#define _GUARD_BINARY_OP_EXTEND_LHS_r22 904 -#define _GUARD_BINARY_OP_EXTEND_LHS_r33 905 -#define _GUARD_BINARY_OP_EXTEND_RHS_r02 906 -#define _GUARD_BINARY_OP_EXTEND_RHS_r12 907 -#define _GUARD_BINARY_OP_EXTEND_RHS_r22 908 -#define _GUARD_BINARY_OP_EXTEND_RHS_r33 909 -#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r02 910 -#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r12 911 -#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r22 912 -#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r33 913 -#define _GUARD_BIT_IS_SET_POP_r00 914 -#define _GUARD_BIT_IS_SET_POP_r10 915 -#define _GUARD_BIT_IS_SET_POP_r21 916 -#define _GUARD_BIT_IS_SET_POP_r32 917 -#define _GUARD_BIT_IS_SET_POP_4_r00 918 -#define _GUARD_BIT_IS_SET_POP_4_r10 919 -#define _GUARD_BIT_IS_SET_POP_4_r21 920 -#define _GUARD_BIT_IS_SET_POP_4_r32 921 -#define _GUARD_BIT_IS_SET_POP_5_r00 922 -#define _GUARD_BIT_IS_SET_POP_5_r10 923 -#define _GUARD_BIT_IS_SET_POP_5_r21 924 -#define _GUARD_BIT_IS_SET_POP_5_r32 925 -#define _GUARD_BIT_IS_SET_POP_6_r00 926 -#define _GUARD_BIT_IS_SET_POP_6_r10 927 -#define _GUARD_BIT_IS_SET_POP_6_r21 928 -#define _GUARD_BIT_IS_SET_POP_6_r32 929 -#define _GUARD_BIT_IS_SET_POP_7_r00 930 -#define _GUARD_BIT_IS_SET_POP_7_r10 931 -#define _GUARD_BIT_IS_SET_POP_7_r21 932 -#define _GUARD_BIT_IS_SET_POP_7_r32 933 -#define _GUARD_BIT_IS_UNSET_POP_r00 934 -#define _GUARD_BIT_IS_UNSET_POP_r10 935 -#define _GUARD_BIT_IS_UNSET_POP_r21 936 -#define _GUARD_BIT_IS_UNSET_POP_r32 937 -#define _GUARD_BIT_IS_UNSET_POP_4_r00 938 -#define _GUARD_BIT_IS_UNSET_POP_4_r10 939 -#define _GUARD_BIT_IS_UNSET_POP_4_r21 940 -#define _GUARD_BIT_IS_UNSET_POP_4_r32 941 -#define _GUARD_BIT_IS_UNSET_POP_5_r00 942 -#define _GUARD_BIT_IS_UNSET_POP_5_r10 943 -#define _GUARD_BIT_IS_UNSET_POP_5_r21 944 -#define _GUARD_BIT_IS_UNSET_POP_5_r32 945 -#define _GUARD_BIT_IS_UNSET_POP_6_r00 946 -#define _GUARD_BIT_IS_UNSET_POP_6_r10 947 -#define _GUARD_BIT_IS_UNSET_POP_6_r21 948 -#define _GUARD_BIT_IS_UNSET_POP_6_r32 949 -#define _GUARD_BIT_IS_UNSET_POP_7_r00 950 -#define _GUARD_BIT_IS_UNSET_POP_7_r10 951 -#define _GUARD_BIT_IS_UNSET_POP_7_r21 952 -#define _GUARD_BIT_IS_UNSET_POP_7_r32 953 -#define _GUARD_CALLABLE_BUILTIN_CLASS_r00 954 -#define _GUARD_CALLABLE_BUILTIN_FAST_r00 955 -#define _GUARD_CALLABLE_BUILTIN_FAST_WITH_KEYWORDS_r00 956 -#define _GUARD_CALLABLE_BUILTIN_O_r00 957 -#define _GUARD_CALLABLE_ISINSTANCE_r03 958 -#define _GUARD_CALLABLE_ISINSTANCE_r13 959 -#define _GUARD_CALLABLE_ISINSTANCE_r23 960 -#define _GUARD_CALLABLE_ISINSTANCE_r33 961 -#define _GUARD_CALLABLE_LEN_r03 962 -#define _GUARD_CALLABLE_LEN_r13 963 -#define _GUARD_CALLABLE_LEN_r23 964 -#define _GUARD_CALLABLE_LEN_r33 965 -#define _GUARD_CALLABLE_LIST_APPEND_r03 966 -#define _GUARD_CALLABLE_LIST_APPEND_r13 967 -#define _GUARD_CALLABLE_LIST_APPEND_r23 968 -#define _GUARD_CALLABLE_LIST_APPEND_r33 969 -#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_FAST_r00 970 -#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r00 971 -#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_NOARGS_r00 972 -#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_O_r00 973 -#define _GUARD_CALLABLE_STR_1_r03 974 -#define _GUARD_CALLABLE_STR_1_r13 975 -#define _GUARD_CALLABLE_STR_1_r23 976 -#define _GUARD_CALLABLE_STR_1_r33 977 -#define _GUARD_CALLABLE_TUPLE_1_r03 978 -#define _GUARD_CALLABLE_TUPLE_1_r13 979 -#define _GUARD_CALLABLE_TUPLE_1_r23 980 -#define _GUARD_CALLABLE_TUPLE_1_r33 981 -#define _GUARD_CALLABLE_TYPE_1_r03 982 -#define _GUARD_CALLABLE_TYPE_1_r13 983 -#define _GUARD_CALLABLE_TYPE_1_r23 984 -#define _GUARD_CALLABLE_TYPE_1_r33 985 -#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r00 986 -#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r11 987 -#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r22 988 -#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r33 989 -#define _GUARD_CODE_VERSION_RETURN_VALUE_r00 990 -#define _GUARD_CODE_VERSION_RETURN_VALUE_r11 991 -#define _GUARD_CODE_VERSION_RETURN_VALUE_r22 992 -#define _GUARD_CODE_VERSION_RETURN_VALUE_r33 993 -#define _GUARD_CODE_VERSION_YIELD_VALUE_r00 994 -#define _GUARD_CODE_VERSION_YIELD_VALUE_r11 995 -#define _GUARD_CODE_VERSION_YIELD_VALUE_r22 996 -#define _GUARD_CODE_VERSION_YIELD_VALUE_r33 997 -#define _GUARD_CODE_VERSION__PUSH_FRAME_r00 998 -#define _GUARD_CODE_VERSION__PUSH_FRAME_r11 999 -#define _GUARD_CODE_VERSION__PUSH_FRAME_r22 1000 -#define _GUARD_CODE_VERSION__PUSH_FRAME_r33 1001 -#define _GUARD_DORV_NO_DICT_r01 1002 -#define _GUARD_DORV_NO_DICT_r11 1003 -#define _GUARD_DORV_NO_DICT_r22 1004 -#define _GUARD_DORV_NO_DICT_r33 1005 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 1006 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 1007 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 1008 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 1009 -#define _GUARD_GLOBALS_VERSION_r00 1010 -#define _GUARD_GLOBALS_VERSION_r11 1011 -#define _GUARD_GLOBALS_VERSION_r22 1012 -#define _GUARD_GLOBALS_VERSION_r33 1013 -#define _GUARD_IP_RETURN_GENERATOR_r00 1014 -#define _GUARD_IP_RETURN_GENERATOR_r11 1015 -#define _GUARD_IP_RETURN_GENERATOR_r22 1016 -#define _GUARD_IP_RETURN_GENERATOR_r33 1017 -#define _GUARD_IP_RETURN_VALUE_r00 1018 -#define _GUARD_IP_RETURN_VALUE_r11 1019 -#define _GUARD_IP_RETURN_VALUE_r22 1020 -#define _GUARD_IP_RETURN_VALUE_r33 1021 -#define _GUARD_IP_YIELD_VALUE_r00 1022 -#define _GUARD_IP_YIELD_VALUE_r11 1023 -#define _GUARD_IP_YIELD_VALUE_r22 1024 -#define _GUARD_IP_YIELD_VALUE_r33 1025 -#define _GUARD_IP__PUSH_FRAME_r00 1026 -#define _GUARD_IP__PUSH_FRAME_r11 1027 -#define _GUARD_IP__PUSH_FRAME_r22 1028 -#define _GUARD_IP__PUSH_FRAME_r33 1029 -#define _GUARD_IS_FALSE_POP_r00 1030 -#define _GUARD_IS_FALSE_POP_r10 1031 -#define _GUARD_IS_FALSE_POP_r21 1032 -#define _GUARD_IS_FALSE_POP_r32 1033 -#define _GUARD_IS_NONE_POP_r00 1034 -#define _GUARD_IS_NONE_POP_r10 1035 -#define _GUARD_IS_NONE_POP_r21 1036 -#define _GUARD_IS_NONE_POP_r32 1037 -#define _GUARD_IS_NOT_NONE_POP_r10 1038 -#define _GUARD_IS_TRUE_POP_r00 1039 -#define _GUARD_IS_TRUE_POP_r10 1040 -#define _GUARD_IS_TRUE_POP_r21 1041 -#define _GUARD_IS_TRUE_POP_r32 1042 -#define _GUARD_ITERATOR_r01 1043 -#define _GUARD_ITERATOR_r11 1044 -#define _GUARD_ITERATOR_r22 1045 -#define _GUARD_ITERATOR_r33 1046 -#define _GUARD_ITER_VIRTUAL_r01 1047 -#define _GUARD_ITER_VIRTUAL_r11 1048 -#define _GUARD_ITER_VIRTUAL_r22 1049 -#define _GUARD_ITER_VIRTUAL_r33 1050 -#define _GUARD_KEYS_VERSION_r01 1051 -#define _GUARD_KEYS_VERSION_r11 1052 -#define _GUARD_KEYS_VERSION_r22 1053 -#define _GUARD_KEYS_VERSION_r33 1054 -#define _GUARD_LOAD_SUPER_ATTR_METHOD_r03 1055 -#define _GUARD_LOAD_SUPER_ATTR_METHOD_r13 1056 -#define _GUARD_LOAD_SUPER_ATTR_METHOD_r23 1057 -#define _GUARD_LOAD_SUPER_ATTR_METHOD_r33 1058 -#define _GUARD_NOS_ANY_DICT_r02 1059 -#define _GUARD_NOS_ANY_DICT_r12 1060 -#define _GUARD_NOS_ANY_DICT_r22 1061 -#define _GUARD_NOS_ANY_DICT_r33 1062 -#define _GUARD_NOS_COMPACT_ASCII_r02 1063 -#define _GUARD_NOS_COMPACT_ASCII_r12 1064 -#define _GUARD_NOS_COMPACT_ASCII_r22 1065 -#define _GUARD_NOS_COMPACT_ASCII_r33 1066 -#define _GUARD_NOS_DICT_r02 1067 -#define _GUARD_NOS_DICT_r12 1068 -#define _GUARD_NOS_DICT_r22 1069 -#define _GUARD_NOS_DICT_r33 1070 -#define _GUARD_NOS_FLOAT_r02 1071 -#define _GUARD_NOS_FLOAT_r12 1072 -#define _GUARD_NOS_FLOAT_r22 1073 -#define _GUARD_NOS_FLOAT_r33 1074 -#define _GUARD_NOS_INT_r02 1075 -#define _GUARD_NOS_INT_r12 1076 -#define _GUARD_NOS_INT_r22 1077 -#define _GUARD_NOS_INT_r33 1078 -#define _GUARD_NOS_ITER_VIRTUAL_r02 1079 -#define _GUARD_NOS_ITER_VIRTUAL_r12 1080 -#define _GUARD_NOS_ITER_VIRTUAL_r22 1081 -#define _GUARD_NOS_ITER_VIRTUAL_r33 1082 -#define _GUARD_NOS_LIST_r02 1083 -#define _GUARD_NOS_LIST_r12 1084 -#define _GUARD_NOS_LIST_r22 1085 -#define _GUARD_NOS_LIST_r33 1086 -#define _GUARD_NOS_NOT_NULL_r02 1087 -#define _GUARD_NOS_NOT_NULL_r12 1088 -#define _GUARD_NOS_NOT_NULL_r22 1089 -#define _GUARD_NOS_NOT_NULL_r33 1090 -#define _GUARD_NOS_NULL_r02 1091 -#define _GUARD_NOS_NULL_r12 1092 -#define _GUARD_NOS_NULL_r22 1093 -#define _GUARD_NOS_NULL_r33 1094 -#define _GUARD_NOS_OVERFLOWED_r02 1095 -#define _GUARD_NOS_OVERFLOWED_r12 1096 -#define _GUARD_NOS_OVERFLOWED_r22 1097 -#define _GUARD_NOS_OVERFLOWED_r33 1098 -#define _GUARD_NOS_TUPLE_r02 1099 -#define _GUARD_NOS_TUPLE_r12 1100 -#define _GUARD_NOS_TUPLE_r22 1101 -#define _GUARD_NOS_TUPLE_r33 1102 -#define _GUARD_NOS_TYPE_VERSION_r02 1103 -#define _GUARD_NOS_TYPE_VERSION_r12 1104 -#define _GUARD_NOS_TYPE_VERSION_r22 1105 -#define _GUARD_NOS_TYPE_VERSION_r33 1106 -#define _GUARD_NOS_UNICODE_r02 1107 -#define _GUARD_NOS_UNICODE_r12 1108 -#define _GUARD_NOS_UNICODE_r22 1109 -#define _GUARD_NOS_UNICODE_r33 1110 -#define _GUARD_NOT_EXHAUSTED_LIST_r02 1111 -#define _GUARD_NOT_EXHAUSTED_LIST_r12 1112 -#define _GUARD_NOT_EXHAUSTED_LIST_r22 1113 -#define _GUARD_NOT_EXHAUSTED_LIST_r33 1114 -#define _GUARD_NOT_EXHAUSTED_RANGE_r02 1115 -#define _GUARD_NOT_EXHAUSTED_RANGE_r12 1116 -#define _GUARD_NOT_EXHAUSTED_RANGE_r22 1117 -#define _GUARD_NOT_EXHAUSTED_RANGE_r33 1118 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 1119 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 1120 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 1121 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 1122 -#define _GUARD_THIRD_NULL_r03 1123 -#define _GUARD_THIRD_NULL_r13 1124 -#define _GUARD_THIRD_NULL_r23 1125 -#define _GUARD_THIRD_NULL_r33 1126 -#define _GUARD_TOS_ANY_DICT_r01 1127 -#define _GUARD_TOS_ANY_DICT_r11 1128 -#define _GUARD_TOS_ANY_DICT_r22 1129 -#define _GUARD_TOS_ANY_DICT_r33 1130 -#define _GUARD_TOS_ANY_SET_r01 1131 -#define _GUARD_TOS_ANY_SET_r11 1132 -#define _GUARD_TOS_ANY_SET_r22 1133 -#define _GUARD_TOS_ANY_SET_r33 1134 -#define _GUARD_TOS_DICT_r01 1135 -#define _GUARD_TOS_DICT_r11 1136 -#define _GUARD_TOS_DICT_r22 1137 -#define _GUARD_TOS_DICT_r33 1138 -#define _GUARD_TOS_FLOAT_r01 1139 -#define _GUARD_TOS_FLOAT_r11 1140 -#define _GUARD_TOS_FLOAT_r22 1141 -#define _GUARD_TOS_FLOAT_r33 1142 -#define _GUARD_TOS_FROZENDICT_r01 1143 -#define _GUARD_TOS_FROZENDICT_r11 1144 -#define _GUARD_TOS_FROZENDICT_r22 1145 -#define _GUARD_TOS_FROZENDICT_r33 1146 -#define _GUARD_TOS_FROZENSET_r01 1147 -#define _GUARD_TOS_FROZENSET_r11 1148 -#define _GUARD_TOS_FROZENSET_r22 1149 -#define _GUARD_TOS_FROZENSET_r33 1150 -#define _GUARD_TOS_INT_r01 1151 -#define _GUARD_TOS_INT_r11 1152 -#define _GUARD_TOS_INT_r22 1153 -#define _GUARD_TOS_INT_r33 1154 -#define _GUARD_TOS_LIST_r01 1155 -#define _GUARD_TOS_LIST_r11 1156 -#define _GUARD_TOS_LIST_r22 1157 -#define _GUARD_TOS_LIST_r33 1158 -#define _GUARD_TOS_OVERFLOWED_r01 1159 -#define _GUARD_TOS_OVERFLOWED_r11 1160 -#define _GUARD_TOS_OVERFLOWED_r22 1161 -#define _GUARD_TOS_OVERFLOWED_r33 1162 -#define _GUARD_TOS_SET_r01 1163 -#define _GUARD_TOS_SET_r11 1164 -#define _GUARD_TOS_SET_r22 1165 -#define _GUARD_TOS_SET_r33 1166 -#define _GUARD_TOS_SLICE_r01 1167 -#define _GUARD_TOS_SLICE_r11 1168 -#define _GUARD_TOS_SLICE_r22 1169 -#define _GUARD_TOS_SLICE_r33 1170 -#define _GUARD_TOS_TUPLE_r01 1171 -#define _GUARD_TOS_TUPLE_r11 1172 -#define _GUARD_TOS_TUPLE_r22 1173 -#define _GUARD_TOS_TUPLE_r33 1174 -#define _GUARD_TOS_UNICODE_r01 1175 -#define _GUARD_TOS_UNICODE_r11 1176 -#define _GUARD_TOS_UNICODE_r22 1177 -#define _GUARD_TOS_UNICODE_r33 1178 -#define _GUARD_TYPE_r01 1179 -#define _GUARD_TYPE_r11 1180 -#define _GUARD_TYPE_r22 1181 -#define _GUARD_TYPE_r33 1182 -#define _GUARD_TYPE_VERSION_r01 1183 -#define _GUARD_TYPE_VERSION_r11 1184 -#define _GUARD_TYPE_VERSION_r22 1185 -#define _GUARD_TYPE_VERSION_r33 1186 -#define _GUARD_TYPE_VERSION_LOCKED_r01 1187 -#define _GUARD_TYPE_VERSION_LOCKED_r11 1188 -#define _GUARD_TYPE_VERSION_LOCKED_r22 1189 -#define _GUARD_TYPE_VERSION_LOCKED_r33 1190 -#define _HANDLE_PENDING_AND_DEOPT_r00 1191 -#define _HANDLE_PENDING_AND_DEOPT_r10 1192 -#define _HANDLE_PENDING_AND_DEOPT_r20 1193 -#define _HANDLE_PENDING_AND_DEOPT_r30 1194 -#define _IMPORT_FROM_r12 1195 -#define _IMPORT_NAME_r21 1196 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 1197 -#define _INIT_CALL_PY_EXACT_ARGS_r01 1198 -#define _INIT_CALL_PY_EXACT_ARGS_0_r01 1199 -#define _INIT_CALL_PY_EXACT_ARGS_1_r01 1200 -#define _INIT_CALL_PY_EXACT_ARGS_2_r01 1201 -#define _INIT_CALL_PY_EXACT_ARGS_3_r01 1202 -#define _INIT_CALL_PY_EXACT_ARGS_4_r01 1203 -#define _INSERT_NULL_r10 1204 -#define _INSTRUMENTED_FOR_ITER_r23 1205 -#define _INSTRUMENTED_INSTRUCTION_r00 1206 -#define _INSTRUMENTED_JUMP_FORWARD_r00 1207 -#define _INSTRUMENTED_JUMP_FORWARD_r11 1208 -#define _INSTRUMENTED_JUMP_FORWARD_r22 1209 -#define _INSTRUMENTED_JUMP_FORWARD_r33 1210 -#define _INSTRUMENTED_LINE_r00 1211 -#define _INSTRUMENTED_NOT_TAKEN_r00 1212 -#define _INSTRUMENTED_NOT_TAKEN_r11 1213 -#define _INSTRUMENTED_NOT_TAKEN_r22 1214 -#define _INSTRUMENTED_NOT_TAKEN_r33 1215 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 1216 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 1217 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 1218 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 1219 -#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 1220 -#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 1221 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 1222 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 1223 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 1224 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 1225 -#define _IS_NONE_r11 1226 -#define _IS_OP_r03 1227 -#define _IS_OP_r13 1228 -#define _IS_OP_r23 1229 -#define _ITER_CHECK_LIST_r02 1230 -#define _ITER_CHECK_LIST_r12 1231 -#define _ITER_CHECK_LIST_r22 1232 -#define _ITER_CHECK_LIST_r33 1233 -#define _ITER_CHECK_RANGE_r02 1234 -#define _ITER_CHECK_RANGE_r12 1235 -#define _ITER_CHECK_RANGE_r22 1236 -#define _ITER_CHECK_RANGE_r33 1237 -#define _ITER_CHECK_TUPLE_r02 1238 -#define _ITER_CHECK_TUPLE_r12 1239 -#define _ITER_CHECK_TUPLE_r22 1240 -#define _ITER_CHECK_TUPLE_r33 1241 -#define _ITER_JUMP_LIST_r02 1242 -#define _ITER_JUMP_LIST_r12 1243 -#define _ITER_JUMP_LIST_r22 1244 -#define _ITER_JUMP_LIST_r33 1245 -#define _ITER_JUMP_RANGE_r02 1246 -#define _ITER_JUMP_RANGE_r12 1247 -#define _ITER_JUMP_RANGE_r22 1248 -#define _ITER_JUMP_RANGE_r33 1249 -#define _ITER_JUMP_TUPLE_r02 1250 -#define _ITER_JUMP_TUPLE_r12 1251 -#define _ITER_JUMP_TUPLE_r22 1252 -#define _ITER_JUMP_TUPLE_r33 1253 -#define _ITER_NEXT_LIST_r23 1254 -#define _ITER_NEXT_LIST_TIER_TWO_r23 1255 -#define _ITER_NEXT_RANGE_r03 1256 -#define _ITER_NEXT_RANGE_r13 1257 -#define _ITER_NEXT_RANGE_r23 1258 -#define _ITER_NEXT_TUPLE_r03 1259 -#define _ITER_NEXT_TUPLE_r13 1260 -#define _ITER_NEXT_TUPLE_r23 1261 -#define _JUMP_BACKWARD_NO_INTERRUPT_r00 1262 -#define _JUMP_BACKWARD_NO_INTERRUPT_r11 1263 -#define _JUMP_BACKWARD_NO_INTERRUPT_r22 1264 -#define _JUMP_BACKWARD_NO_INTERRUPT_r33 1265 -#define _JUMP_TO_TOP_r00 1266 -#define _LIST_APPEND_r10 1267 -#define _LIST_EXTEND_r11 1268 -#define _LOAD_ATTR_r10 1269 -#define _LOAD_ATTR_CLASS_r11 1270 -#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME_r11 1271 -#define _LOAD_ATTR_INSTANCE_VALUE_r02 1272 -#define _LOAD_ATTR_INSTANCE_VALUE_r12 1273 -#define _LOAD_ATTR_INSTANCE_VALUE_r23 1274 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 1275 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 1276 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 1277 -#define _LOAD_ATTR_METHOD_NO_DICT_r02 1278 -#define _LOAD_ATTR_METHOD_NO_DICT_r12 1279 -#define _LOAD_ATTR_METHOD_NO_DICT_r23 1280 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 1281 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 1282 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 1283 -#define _LOAD_ATTR_MODULE_r12 1284 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 1285 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1286 -#define _LOAD_ATTR_PROPERTY_FRAME_r01 1287 -#define _LOAD_ATTR_PROPERTY_FRAME_r11 1288 -#define _LOAD_ATTR_PROPERTY_FRAME_r22 1289 -#define _LOAD_ATTR_PROPERTY_FRAME_r33 1290 -#define _LOAD_ATTR_SLOT_r02 1291 -#define _LOAD_ATTR_SLOT_r12 1292 -#define _LOAD_ATTR_SLOT_r23 1293 -#define _LOAD_ATTR_WITH_HINT_r12 1294 -#define _LOAD_BUILD_CLASS_r01 1295 -#define _LOAD_BYTECODE_r00 1296 -#define _LOAD_COMMON_CONSTANT_r01 1297 -#define _LOAD_COMMON_CONSTANT_r12 1298 -#define _LOAD_COMMON_CONSTANT_r23 1299 -#define _LOAD_CONST_r01 1300 -#define _LOAD_CONST_r12 1301 -#define _LOAD_CONST_r23 1302 -#define _LOAD_CONST_INLINE_r01 1303 -#define _LOAD_CONST_INLINE_r12 1304 -#define _LOAD_CONST_INLINE_r23 1305 -#define _LOAD_CONST_INLINE_BORROW_r01 1306 -#define _LOAD_CONST_INLINE_BORROW_r12 1307 -#define _LOAD_CONST_INLINE_BORROW_r23 1308 -#define _LOAD_DEREF_r01 1309 -#define _LOAD_FAST_r01 1310 -#define _LOAD_FAST_r12 1311 -#define _LOAD_FAST_r23 1312 -#define _LOAD_FAST_0_r01 1313 -#define _LOAD_FAST_0_r12 1314 -#define _LOAD_FAST_0_r23 1315 -#define _LOAD_FAST_1_r01 1316 -#define _LOAD_FAST_1_r12 1317 -#define _LOAD_FAST_1_r23 1318 -#define _LOAD_FAST_2_r01 1319 -#define _LOAD_FAST_2_r12 1320 -#define _LOAD_FAST_2_r23 1321 -#define _LOAD_FAST_3_r01 1322 -#define _LOAD_FAST_3_r12 1323 -#define _LOAD_FAST_3_r23 1324 -#define _LOAD_FAST_4_r01 1325 -#define _LOAD_FAST_4_r12 1326 -#define _LOAD_FAST_4_r23 1327 -#define _LOAD_FAST_5_r01 1328 -#define _LOAD_FAST_5_r12 1329 -#define _LOAD_FAST_5_r23 1330 -#define _LOAD_FAST_6_r01 1331 -#define _LOAD_FAST_6_r12 1332 -#define _LOAD_FAST_6_r23 1333 -#define _LOAD_FAST_7_r01 1334 -#define _LOAD_FAST_7_r12 1335 -#define _LOAD_FAST_7_r23 1336 -#define _LOAD_FAST_AND_CLEAR_r01 1337 -#define _LOAD_FAST_AND_CLEAR_r12 1338 -#define _LOAD_FAST_AND_CLEAR_r23 1339 -#define _LOAD_FAST_BORROW_r01 1340 -#define _LOAD_FAST_BORROW_r12 1341 -#define _LOAD_FAST_BORROW_r23 1342 -#define _LOAD_FAST_BORROW_0_r01 1343 -#define _LOAD_FAST_BORROW_0_r12 1344 -#define _LOAD_FAST_BORROW_0_r23 1345 -#define _LOAD_FAST_BORROW_1_r01 1346 -#define _LOAD_FAST_BORROW_1_r12 1347 -#define _LOAD_FAST_BORROW_1_r23 1348 -#define _LOAD_FAST_BORROW_2_r01 1349 -#define _LOAD_FAST_BORROW_2_r12 1350 -#define _LOAD_FAST_BORROW_2_r23 1351 -#define _LOAD_FAST_BORROW_3_r01 1352 -#define _LOAD_FAST_BORROW_3_r12 1353 -#define _LOAD_FAST_BORROW_3_r23 1354 -#define _LOAD_FAST_BORROW_4_r01 1355 -#define _LOAD_FAST_BORROW_4_r12 1356 -#define _LOAD_FAST_BORROW_4_r23 1357 -#define _LOAD_FAST_BORROW_5_r01 1358 -#define _LOAD_FAST_BORROW_5_r12 1359 -#define _LOAD_FAST_BORROW_5_r23 1360 -#define _LOAD_FAST_BORROW_6_r01 1361 -#define _LOAD_FAST_BORROW_6_r12 1362 -#define _LOAD_FAST_BORROW_6_r23 1363 -#define _LOAD_FAST_BORROW_7_r01 1364 -#define _LOAD_FAST_BORROW_7_r12 1365 -#define _LOAD_FAST_BORROW_7_r23 1366 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1367 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1368 -#define _LOAD_FAST_CHECK_r01 1369 -#define _LOAD_FAST_CHECK_r12 1370 -#define _LOAD_FAST_CHECK_r23 1371 -#define _LOAD_FAST_LOAD_FAST_r02 1372 -#define _LOAD_FAST_LOAD_FAST_r13 1373 -#define _LOAD_FROM_DICT_OR_DEREF_r11 1374 -#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1375 -#define _LOAD_GLOBAL_r00 1376 -#define _LOAD_GLOBAL_BUILTINS_r01 1377 -#define _LOAD_GLOBAL_MODULE_r01 1378 -#define _LOAD_LOCALS_r01 1379 -#define _LOAD_LOCALS_r12 1380 -#define _LOAD_LOCALS_r23 1381 -#define _LOAD_NAME_r01 1382 -#define _LOAD_SMALL_INT_r01 1383 -#define _LOAD_SMALL_INT_r12 1384 -#define _LOAD_SMALL_INT_r23 1385 -#define _LOAD_SMALL_INT_0_r01 1386 -#define _LOAD_SMALL_INT_0_r12 1387 -#define _LOAD_SMALL_INT_0_r23 1388 -#define _LOAD_SMALL_INT_1_r01 1389 -#define _LOAD_SMALL_INT_1_r12 1390 -#define _LOAD_SMALL_INT_1_r23 1391 -#define _LOAD_SMALL_INT_2_r01 1392 -#define _LOAD_SMALL_INT_2_r12 1393 -#define _LOAD_SMALL_INT_2_r23 1394 -#define _LOAD_SMALL_INT_3_r01 1395 -#define _LOAD_SMALL_INT_3_r12 1396 -#define _LOAD_SMALL_INT_3_r23 1397 -#define _LOAD_SPECIAL_r00 1398 -#define _LOAD_SUPER_ATTR_ATTR_r31 1399 -#define _LOAD_SUPER_ATTR_METHOD_r32 1400 -#define _LOCK_OBJECT_r01 1401 -#define _LOCK_OBJECT_r11 1402 -#define _LOCK_OBJECT_r22 1403 -#define _LOCK_OBJECT_r33 1404 -#define _MAKE_CALLARGS_A_TUPLE_r33 1405 -#define _MAKE_CELL_r00 1406 -#define _MAKE_FUNCTION_r12 1407 -#define _MAKE_HEAP_SAFE_r01 1408 -#define _MAKE_HEAP_SAFE_r11 1409 -#define _MAKE_HEAP_SAFE_r22 1410 -#define _MAKE_HEAP_SAFE_r33 1411 -#define _MAKE_WARM_r00 1412 -#define _MAKE_WARM_r11 1413 -#define _MAKE_WARM_r22 1414 -#define _MAKE_WARM_r33 1415 -#define _MAP_ADD_r20 1416 -#define _MATCH_CLASS_r33 1417 -#define _MATCH_KEYS_r23 1418 -#define _MATCH_MAPPING_r02 1419 -#define _MATCH_MAPPING_r12 1420 -#define _MATCH_MAPPING_r23 1421 -#define _MATCH_SEQUENCE_r02 1422 -#define _MATCH_SEQUENCE_r12 1423 -#define _MATCH_SEQUENCE_r23 1424 -#define _MAYBE_EXPAND_METHOD_r00 1425 -#define _MAYBE_EXPAND_METHOD_KW_r11 1426 -#define _MONITOR_CALL_r00 1427 -#define _MONITOR_CALL_KW_r11 1428 -#define _MONITOR_JUMP_BACKWARD_r00 1429 -#define _MONITOR_JUMP_BACKWARD_r11 1430 -#define _MONITOR_JUMP_BACKWARD_r22 1431 -#define _MONITOR_JUMP_BACKWARD_r33 1432 -#define _MONITOR_RESUME_r00 1433 -#define _NOP_r00 1434 -#define _NOP_r11 1435 -#define _NOP_r22 1436 -#define _NOP_r33 1437 -#define _POP_EXCEPT_r10 1438 -#define _POP_ITER_r20 1439 -#define _POP_JUMP_IF_FALSE_r00 1440 -#define _POP_JUMP_IF_FALSE_r10 1441 -#define _POP_JUMP_IF_FALSE_r21 1442 -#define _POP_JUMP_IF_FALSE_r32 1443 -#define _POP_JUMP_IF_TRUE_r00 1444 -#define _POP_JUMP_IF_TRUE_r10 1445 -#define _POP_JUMP_IF_TRUE_r21 1446 -#define _POP_JUMP_IF_TRUE_r32 1447 -#define _POP_TOP_r10 1448 -#define _POP_TOP_FLOAT_r00 1449 -#define _POP_TOP_FLOAT_r10 1450 -#define _POP_TOP_FLOAT_r21 1451 -#define _POP_TOP_FLOAT_r32 1452 -#define _POP_TOP_INT_r00 1453 -#define _POP_TOP_INT_r10 1454 -#define _POP_TOP_INT_r21 1455 -#define _POP_TOP_INT_r32 1456 -#define _POP_TOP_NOP_r00 1457 -#define _POP_TOP_NOP_r10 1458 -#define _POP_TOP_NOP_r21 1459 -#define _POP_TOP_NOP_r32 1460 -#define _POP_TOP_OPARG_r00 1461 -#define _POP_TOP_UNICODE_r00 1462 -#define _POP_TOP_UNICODE_r10 1463 -#define _POP_TOP_UNICODE_r21 1464 -#define _POP_TOP_UNICODE_r32 1465 -#define _PUSH_EXC_INFO_r02 1466 -#define _PUSH_EXC_INFO_r12 1467 -#define _PUSH_EXC_INFO_r23 1468 -#define _PUSH_FRAME_r10 1469 -#define _PUSH_NULL_r01 1470 -#define _PUSH_NULL_r12 1471 -#define _PUSH_NULL_r23 1472 -#define _PUSH_NULL_CONDITIONAL_r00 1473 -#define _PUSH_TAGGED_ZERO_r01 1474 -#define _PUSH_TAGGED_ZERO_r12 1475 -#define _PUSH_TAGGED_ZERO_r23 1476 -#define _PY_FRAME_EX_r31 1477 -#define _PY_FRAME_GENERAL_r01 1478 -#define _PY_FRAME_KW_r11 1479 -#define _REPLACE_WITH_TRUE_r02 1480 -#define _REPLACE_WITH_TRUE_r12 1481 -#define _REPLACE_WITH_TRUE_r23 1482 -#define _RESUME_CHECK_r00 1483 -#define _RESUME_CHECK_r11 1484 -#define _RESUME_CHECK_r22 1485 -#define _RESUME_CHECK_r33 1486 -#define _RETURN_GENERATOR_r01 1487 -#define _RETURN_VALUE_r11 1488 -#define _SAVE_RETURN_OFFSET_r00 1489 -#define _SAVE_RETURN_OFFSET_r11 1490 -#define _SAVE_RETURN_OFFSET_r22 1491 -#define _SAVE_RETURN_OFFSET_r33 1492 -#define _SEND_r33 1493 -#define _SEND_GEN_FRAME_r33 1494 -#define _SETUP_ANNOTATIONS_r00 1495 -#define _SET_ADD_r10 1496 -#define _SET_FUNCTION_ATTRIBUTE_r01 1497 -#define _SET_FUNCTION_ATTRIBUTE_r11 1498 -#define _SET_FUNCTION_ATTRIBUTE_r21 1499 -#define _SET_FUNCTION_ATTRIBUTE_r32 1500 -#define _SET_IP_r00 1501 -#define _SET_IP_r11 1502 -#define _SET_IP_r22 1503 -#define _SET_IP_r33 1504 -#define _SET_UPDATE_r11 1505 -#define _SPILL_OR_RELOAD_r01 1506 -#define _SPILL_OR_RELOAD_r02 1507 -#define _SPILL_OR_RELOAD_r03 1508 -#define _SPILL_OR_RELOAD_r10 1509 -#define _SPILL_OR_RELOAD_r12 1510 -#define _SPILL_OR_RELOAD_r13 1511 -#define _SPILL_OR_RELOAD_r20 1512 -#define _SPILL_OR_RELOAD_r21 1513 -#define _SPILL_OR_RELOAD_r23 1514 -#define _SPILL_OR_RELOAD_r30 1515 -#define _SPILL_OR_RELOAD_r31 1516 -#define _SPILL_OR_RELOAD_r32 1517 -#define _START_EXECUTOR_r00 1518 -#define _STORE_ATTR_r20 1519 -#define _STORE_ATTR_INSTANCE_VALUE_r21 1520 -#define _STORE_ATTR_SLOT_r21 1521 -#define _STORE_ATTR_WITH_HINT_r21 1522 -#define _STORE_DEREF_r10 1523 -#define _STORE_FAST_LOAD_FAST_r11 1524 -#define _STORE_FAST_STORE_FAST_r20 1525 -#define _STORE_GLOBAL_r10 1526 -#define _STORE_NAME_r10 1527 -#define _STORE_SLICE_r30 1528 -#define _STORE_SUBSCR_r30 1529 -#define _STORE_SUBSCR_DICT_r31 1530 -#define _STORE_SUBSCR_DICT_KNOWN_HASH_r31 1531 -#define _STORE_SUBSCR_LIST_INT_r32 1532 -#define _SWAP_r11 1533 -#define _SWAP_2_r02 1534 -#define _SWAP_2_r12 1535 -#define _SWAP_2_r22 1536 -#define _SWAP_2_r33 1537 -#define _SWAP_3_r03 1538 -#define _SWAP_3_r13 1539 -#define _SWAP_3_r23 1540 -#define _SWAP_3_r33 1541 -#define _SWAP_FAST_r01 1542 -#define _SWAP_FAST_r11 1543 -#define _SWAP_FAST_r22 1544 -#define _SWAP_FAST_r33 1545 -#define _SWAP_FAST_0_r01 1546 -#define _SWAP_FAST_0_r11 1547 -#define _SWAP_FAST_0_r22 1548 -#define _SWAP_FAST_0_r33 1549 -#define _SWAP_FAST_1_r01 1550 -#define _SWAP_FAST_1_r11 1551 -#define _SWAP_FAST_1_r22 1552 -#define _SWAP_FAST_1_r33 1553 -#define _SWAP_FAST_2_r01 1554 -#define _SWAP_FAST_2_r11 1555 -#define _SWAP_FAST_2_r22 1556 -#define _SWAP_FAST_2_r33 1557 -#define _SWAP_FAST_3_r01 1558 -#define _SWAP_FAST_3_r11 1559 -#define _SWAP_FAST_3_r22 1560 -#define _SWAP_FAST_3_r33 1561 -#define _SWAP_FAST_4_r01 1562 -#define _SWAP_FAST_4_r11 1563 -#define _SWAP_FAST_4_r22 1564 -#define _SWAP_FAST_4_r33 1565 -#define _SWAP_FAST_5_r01 1566 -#define _SWAP_FAST_5_r11 1567 -#define _SWAP_FAST_5_r22 1568 -#define _SWAP_FAST_5_r33 1569 -#define _SWAP_FAST_6_r01 1570 -#define _SWAP_FAST_6_r11 1571 -#define _SWAP_FAST_6_r22 1572 -#define _SWAP_FAST_6_r33 1573 -#define _SWAP_FAST_7_r01 1574 -#define _SWAP_FAST_7_r11 1575 -#define _SWAP_FAST_7_r22 1576 -#define _SWAP_FAST_7_r33 1577 -#define _TIER2_RESUME_CHECK_r00 1578 -#define _TIER2_RESUME_CHECK_r11 1579 -#define _TIER2_RESUME_CHECK_r22 1580 -#define _TIER2_RESUME_CHECK_r33 1581 -#define _TO_BOOL_r11 1582 -#define _TO_BOOL_BOOL_r01 1583 -#define _TO_BOOL_BOOL_r11 1584 -#define _TO_BOOL_BOOL_r22 1585 -#define _TO_BOOL_BOOL_r33 1586 -#define _TO_BOOL_INT_r02 1587 -#define _TO_BOOL_INT_r12 1588 -#define _TO_BOOL_INT_r23 1589 -#define _TO_BOOL_LIST_r02 1590 -#define _TO_BOOL_LIST_r12 1591 -#define _TO_BOOL_LIST_r23 1592 -#define _TO_BOOL_NONE_r01 1593 -#define _TO_BOOL_NONE_r11 1594 -#define _TO_BOOL_NONE_r22 1595 -#define _TO_BOOL_NONE_r33 1596 -#define _TO_BOOL_STR_r02 1597 -#define _TO_BOOL_STR_r12 1598 -#define _TO_BOOL_STR_r23 1599 -#define _TRACE_RECORD_r00 1600 -#define _UNARY_INVERT_r12 1601 -#define _UNARY_NEGATIVE_r12 1602 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r02 1603 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r12 1604 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r23 1605 -#define _UNARY_NOT_r01 1606 -#define _UNARY_NOT_r11 1607 -#define _UNARY_NOT_r22 1608 -#define _UNARY_NOT_r33 1609 -#define _UNPACK_EX_r10 1610 -#define _UNPACK_SEQUENCE_r10 1611 -#define _UNPACK_SEQUENCE_LIST_r10 1612 -#define _UNPACK_SEQUENCE_TUPLE_r10 1613 -#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1614 -#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r03 1615 -#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r13 1616 -#define _UNPACK_SEQUENCE_UNIQUE_TUPLE_r10 1617 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r02 1618 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r12 1619 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r23 1620 -#define _WITH_EXCEPT_START_r33 1621 -#define _YIELD_VALUE_r11 1622 -#define MAX_UOP_REGS_ID 1622 +#define _YIELD_VALUE 646 +#define MAX_UOP_ID 646 +#define _ALLOCATE_OBJECT_r00 647 +#define _BINARY_OP_r23 648 +#define _BINARY_OP_ADD_FLOAT_r03 649 +#define _BINARY_OP_ADD_FLOAT_r13 650 +#define _BINARY_OP_ADD_FLOAT_r23 651 +#define _BINARY_OP_ADD_FLOAT_INPLACE_r03 652 +#define _BINARY_OP_ADD_FLOAT_INPLACE_r13 653 +#define _BINARY_OP_ADD_FLOAT_INPLACE_r23 654 +#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r03 655 +#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r13 656 +#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r23 657 +#define _BINARY_OP_ADD_INT_r03 658 +#define _BINARY_OP_ADD_INT_r13 659 +#define _BINARY_OP_ADD_INT_r23 660 +#define _BINARY_OP_ADD_INT_INPLACE_r03 661 +#define _BINARY_OP_ADD_INT_INPLACE_r13 662 +#define _BINARY_OP_ADD_INT_INPLACE_r23 663 +#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r03 664 +#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r13 665 +#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r23 666 +#define _BINARY_OP_ADD_UNICODE_r03 667 +#define _BINARY_OP_ADD_UNICODE_r13 668 +#define _BINARY_OP_ADD_UNICODE_r23 669 +#define _BINARY_OP_EXTEND_r23 670 +#define _BINARY_OP_INPLACE_ADD_UNICODE_r21 671 +#define _BINARY_OP_MULTIPLY_FLOAT_r03 672 +#define _BINARY_OP_MULTIPLY_FLOAT_r13 673 +#define _BINARY_OP_MULTIPLY_FLOAT_r23 674 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r03 675 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r13 676 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r23 677 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r03 678 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r13 679 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r23 680 +#define _BINARY_OP_MULTIPLY_INT_r03 681 +#define _BINARY_OP_MULTIPLY_INT_r13 682 +#define _BINARY_OP_MULTIPLY_INT_r23 683 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_r03 684 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_r13 685 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_r23 686 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r03 687 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r13 688 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r23 689 +#define _BINARY_OP_SUBSCR_CHECK_FUNC_r23 690 +#define _BINARY_OP_SUBSCR_DICT_r23 691 +#define _BINARY_OP_SUBSCR_DICT_KNOWN_HASH_r23 692 +#define _BINARY_OP_SUBSCR_INIT_CALL_r01 693 +#define _BINARY_OP_SUBSCR_INIT_CALL_r11 694 +#define _BINARY_OP_SUBSCR_INIT_CALL_r21 695 +#define _BINARY_OP_SUBSCR_INIT_CALL_r31 696 +#define _BINARY_OP_SUBSCR_LIST_INT_r23 697 +#define _BINARY_OP_SUBSCR_LIST_SLICE_r23 698 +#define _BINARY_OP_SUBSCR_STR_INT_r23 699 +#define _BINARY_OP_SUBSCR_TUPLE_INT_r03 700 +#define _BINARY_OP_SUBSCR_TUPLE_INT_r13 701 +#define _BINARY_OP_SUBSCR_TUPLE_INT_r23 702 +#define _BINARY_OP_SUBSCR_USTR_INT_r23 703 +#define _BINARY_OP_SUBTRACT_FLOAT_r03 704 +#define _BINARY_OP_SUBTRACT_FLOAT_r13 705 +#define _BINARY_OP_SUBTRACT_FLOAT_r23 706 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r03 707 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r13 708 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r23 709 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r03 710 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r13 711 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r23 712 +#define _BINARY_OP_SUBTRACT_INT_r03 713 +#define _BINARY_OP_SUBTRACT_INT_r13 714 +#define _BINARY_OP_SUBTRACT_INT_r23 715 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_r03 716 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_r13 717 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_r23 718 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r03 719 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r13 720 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r23 721 +#define _BINARY_OP_TRUEDIV_FLOAT_r23 722 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r03 723 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r13 724 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r23 725 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r03 726 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r13 727 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r23 728 +#define _BINARY_SLICE_r31 729 +#define _BUILD_INTERPOLATION_r01 730 +#define _BUILD_LIST_r01 731 +#define _BUILD_MAP_r01 732 +#define _BUILD_SET_r01 733 +#define _BUILD_SLICE_r01 734 +#define _BUILD_STRING_r01 735 +#define _BUILD_TEMPLATE_r21 736 +#define _BUILD_TUPLE_r01 737 +#define _CALL_BUILTIN_CLASS_r00 738 +#define _CALL_BUILTIN_FAST_r00 739 +#define _CALL_BUILTIN_FAST_WITH_KEYWORDS_r00 740 +#define _CALL_BUILTIN_O_r03 741 +#define _CALL_FUNCTION_EX_NON_PY_GENERAL_r31 742 +#define _CALL_INTRINSIC_1_r12 743 +#define _CALL_INTRINSIC_2_r23 744 +#define _CALL_ISINSTANCE_r31 745 +#define _CALL_KW_NON_PY_r11 746 +#define _CALL_LEN_r33 747 +#define _CALL_LIST_APPEND_r03 748 +#define _CALL_LIST_APPEND_r13 749 +#define _CALL_LIST_APPEND_r23 750 +#define _CALL_LIST_APPEND_r33 751 +#define _CALL_METHOD_DESCRIPTOR_FAST_r00 752 +#define _CALL_METHOD_DESCRIPTOR_FAST_INLINE_r00 753 +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r00 754 +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_INLINE_r00 755 +#define _CALL_METHOD_DESCRIPTOR_NOARGS_r03 756 +#define _CALL_METHOD_DESCRIPTOR_NOARGS_INLINE_r03 757 +#define _CALL_METHOD_DESCRIPTOR_O_r03 758 +#define _CALL_METHOD_DESCRIPTOR_O_INLINE_r03 759 +#define _CALL_NON_PY_GENERAL_r01 760 +#define _CALL_STR_1_r32 761 +#define _CALL_TUPLE_1_r32 762 +#define _CALL_TYPE_1_r02 763 +#define _CALL_TYPE_1_r12 764 +#define _CALL_TYPE_1_r22 765 +#define _CALL_TYPE_1_r32 766 +#define _CHECK_ATTR_CLASS_r01 767 +#define _CHECK_ATTR_CLASS_r11 768 +#define _CHECK_ATTR_CLASS_r22 769 +#define _CHECK_ATTR_CLASS_r33 770 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r01 771 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r11 772 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r22 773 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r33 774 +#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS_r00 775 +#define _CHECK_EG_MATCH_r22 776 +#define _CHECK_EXC_MATCH_r22 777 +#define _CHECK_FUNCTION_EXACT_ARGS_r00 778 +#define _CHECK_FUNCTION_VERSION_r00 779 +#define _CHECK_FUNCTION_VERSION_INLINE_r00 780 +#define _CHECK_FUNCTION_VERSION_INLINE_r11 781 +#define _CHECK_FUNCTION_VERSION_INLINE_r22 782 +#define _CHECK_FUNCTION_VERSION_INLINE_r33 783 +#define _CHECK_FUNCTION_VERSION_KW_r11 784 +#define _CHECK_IS_NOT_PY_CALLABLE_r00 785 +#define _CHECK_IS_NOT_PY_CALLABLE_EX_r03 786 +#define _CHECK_IS_NOT_PY_CALLABLE_EX_r13 787 +#define _CHECK_IS_NOT_PY_CALLABLE_EX_r23 788 +#define _CHECK_IS_NOT_PY_CALLABLE_EX_r33 789 +#define _CHECK_IS_NOT_PY_CALLABLE_KW_r11 790 +#define _CHECK_IS_PY_CALLABLE_EX_r03 791 +#define _CHECK_IS_PY_CALLABLE_EX_r13 792 +#define _CHECK_IS_PY_CALLABLE_EX_r23 793 +#define _CHECK_IS_PY_CALLABLE_EX_r33 794 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r01 795 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r11 796 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r22 797 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r33 798 +#define _CHECK_METHOD_VERSION_r00 799 +#define _CHECK_METHOD_VERSION_KW_r11 800 +#define _CHECK_OBJECT_r00 801 +#define _CHECK_PEP_523_r00 802 +#define _CHECK_PEP_523_r11 803 +#define _CHECK_PEP_523_r22 804 +#define _CHECK_PEP_523_r33 805 +#define _CHECK_PERIODIC_r00 806 +#define _CHECK_PERIODIC_AT_END_r00 807 +#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM_r00 808 +#define _CHECK_RECURSION_LIMIT_r00 809 +#define _CHECK_RECURSION_LIMIT_r11 810 +#define _CHECK_RECURSION_LIMIT_r22 811 +#define _CHECK_RECURSION_LIMIT_r33 812 +#define _CHECK_RECURSION_REMAINING_r00 813 +#define _CHECK_RECURSION_REMAINING_r11 814 +#define _CHECK_RECURSION_REMAINING_r22 815 +#define _CHECK_RECURSION_REMAINING_r33 816 +#define _CHECK_STACK_SPACE_r00 817 +#define _CHECK_STACK_SPACE_OPERAND_r00 818 +#define _CHECK_STACK_SPACE_OPERAND_r11 819 +#define _CHECK_STACK_SPACE_OPERAND_r22 820 +#define _CHECK_STACK_SPACE_OPERAND_r33 821 +#define _CHECK_VALIDITY_r00 822 +#define _CHECK_VALIDITY_r11 823 +#define _CHECK_VALIDITY_r22 824 +#define _CHECK_VALIDITY_r33 825 +#define _COLD_DYNAMIC_EXIT_r00 826 +#define _COLD_EXIT_r00 827 +#define _COMPARE_OP_r21 828 +#define _COMPARE_OP_FLOAT_r03 829 +#define _COMPARE_OP_FLOAT_r13 830 +#define _COMPARE_OP_FLOAT_r23 831 +#define _COMPARE_OP_INT_r23 832 +#define _COMPARE_OP_STR_r23 833 +#define _CONTAINS_OP_r23 834 +#define _CONTAINS_OP_DICT_r23 835 +#define _CONTAINS_OP_SET_r23 836 +#define _CONVERT_VALUE_r11 837 +#define _COPY_r01 838 +#define _COPY_1_r02 839 +#define _COPY_1_r12 840 +#define _COPY_1_r23 841 +#define _COPY_2_r03 842 +#define _COPY_2_r13 843 +#define _COPY_2_r23 844 +#define _COPY_3_r03 845 +#define _COPY_3_r13 846 +#define _COPY_3_r23 847 +#define _COPY_3_r33 848 +#define _COPY_FREE_VARS_r00 849 +#define _COPY_FREE_VARS_r11 850 +#define _COPY_FREE_VARS_r22 851 +#define _COPY_FREE_VARS_r33 852 +#define _CREATE_INIT_FRAME_r01 853 +#define _DELETE_ATTR_r10 854 +#define _DELETE_DEREF_r00 855 +#define _DELETE_FAST_r00 856 +#define _DELETE_GLOBAL_r00 857 +#define _DELETE_NAME_r00 858 +#define _DELETE_SUBSCR_r20 859 +#define _DEOPT_r00 860 +#define _DEOPT_r10 861 +#define _DEOPT_r20 862 +#define _DEOPT_r30 863 +#define _DICT_MERGE_r11 864 +#define _DICT_UPDATE_r11 865 +#define _DO_CALL_r01 866 +#define _DO_CALL_FUNCTION_EX_r31 867 +#define _DO_CALL_KW_r11 868 +#define _DYNAMIC_EXIT_r00 869 +#define _DYNAMIC_EXIT_r10 870 +#define _DYNAMIC_EXIT_r20 871 +#define _DYNAMIC_EXIT_r30 872 +#define _END_FOR_r10 873 +#define _END_SEND_r31 874 +#define _ERROR_POP_N_r00 875 +#define _EXIT_INIT_CHECK_r10 876 +#define _EXIT_TRACE_r00 877 +#define _EXIT_TRACE_r10 878 +#define _EXIT_TRACE_r20 879 +#define _EXIT_TRACE_r30 880 +#define _EXPAND_METHOD_r00 881 +#define _EXPAND_METHOD_KW_r11 882 +#define _FATAL_ERROR_r00 883 +#define _FATAL_ERROR_r11 884 +#define _FATAL_ERROR_r22 885 +#define _FATAL_ERROR_r33 886 +#define _FORMAT_SIMPLE_r11 887 +#define _FORMAT_WITH_SPEC_r21 888 +#define _FOR_ITER_r23 889 +#define _FOR_ITER_GEN_FRAME_r03 890 +#define _FOR_ITER_GEN_FRAME_r13 891 +#define _FOR_ITER_GEN_FRAME_r23 892 +#define _FOR_ITER_TIER_TWO_r23 893 +#define _FOR_ITER_VIRTUAL_r23 894 +#define _FOR_ITER_VIRTUAL_TIER_TWO_r23 895 +#define _GET_AITER_r11 896 +#define _GET_ANEXT_r12 897 +#define _GET_AWAITABLE_r11 898 +#define _GET_ITER_r12 899 +#define _GET_ITER_TRAD_r12 900 +#define _GET_LEN_r12 901 +#define _GUARD_BINARY_OP_EXTEND_r22 902 +#define _GUARD_BINARY_OP_EXTEND_LHS_r02 903 +#define _GUARD_BINARY_OP_EXTEND_LHS_r12 904 +#define _GUARD_BINARY_OP_EXTEND_LHS_r22 905 +#define _GUARD_BINARY_OP_EXTEND_LHS_r33 906 +#define _GUARD_BINARY_OP_EXTEND_RHS_r02 907 +#define _GUARD_BINARY_OP_EXTEND_RHS_r12 908 +#define _GUARD_BINARY_OP_EXTEND_RHS_r22 909 +#define _GUARD_BINARY_OP_EXTEND_RHS_r33 910 +#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r02 911 +#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r12 912 +#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r22 913 +#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r33 914 +#define _GUARD_BIT_IS_SET_POP_r00 915 +#define _GUARD_BIT_IS_SET_POP_r10 916 +#define _GUARD_BIT_IS_SET_POP_r21 917 +#define _GUARD_BIT_IS_SET_POP_r32 918 +#define _GUARD_BIT_IS_SET_POP_4_r00 919 +#define _GUARD_BIT_IS_SET_POP_4_r10 920 +#define _GUARD_BIT_IS_SET_POP_4_r21 921 +#define _GUARD_BIT_IS_SET_POP_4_r32 922 +#define _GUARD_BIT_IS_SET_POP_5_r00 923 +#define _GUARD_BIT_IS_SET_POP_5_r10 924 +#define _GUARD_BIT_IS_SET_POP_5_r21 925 +#define _GUARD_BIT_IS_SET_POP_5_r32 926 +#define _GUARD_BIT_IS_SET_POP_6_r00 927 +#define _GUARD_BIT_IS_SET_POP_6_r10 928 +#define _GUARD_BIT_IS_SET_POP_6_r21 929 +#define _GUARD_BIT_IS_SET_POP_6_r32 930 +#define _GUARD_BIT_IS_SET_POP_7_r00 931 +#define _GUARD_BIT_IS_SET_POP_7_r10 932 +#define _GUARD_BIT_IS_SET_POP_7_r21 933 +#define _GUARD_BIT_IS_SET_POP_7_r32 934 +#define _GUARD_BIT_IS_UNSET_POP_r00 935 +#define _GUARD_BIT_IS_UNSET_POP_r10 936 +#define _GUARD_BIT_IS_UNSET_POP_r21 937 +#define _GUARD_BIT_IS_UNSET_POP_r32 938 +#define _GUARD_BIT_IS_UNSET_POP_4_r00 939 +#define _GUARD_BIT_IS_UNSET_POP_4_r10 940 +#define _GUARD_BIT_IS_UNSET_POP_4_r21 941 +#define _GUARD_BIT_IS_UNSET_POP_4_r32 942 +#define _GUARD_BIT_IS_UNSET_POP_5_r00 943 +#define _GUARD_BIT_IS_UNSET_POP_5_r10 944 +#define _GUARD_BIT_IS_UNSET_POP_5_r21 945 +#define _GUARD_BIT_IS_UNSET_POP_5_r32 946 +#define _GUARD_BIT_IS_UNSET_POP_6_r00 947 +#define _GUARD_BIT_IS_UNSET_POP_6_r10 948 +#define _GUARD_BIT_IS_UNSET_POP_6_r21 949 +#define _GUARD_BIT_IS_UNSET_POP_6_r32 950 +#define _GUARD_BIT_IS_UNSET_POP_7_r00 951 +#define _GUARD_BIT_IS_UNSET_POP_7_r10 952 +#define _GUARD_BIT_IS_UNSET_POP_7_r21 953 +#define _GUARD_BIT_IS_UNSET_POP_7_r32 954 +#define _GUARD_CALLABLE_BUILTIN_CLASS_r00 955 +#define _GUARD_CALLABLE_BUILTIN_FAST_r00 956 +#define _GUARD_CALLABLE_BUILTIN_FAST_WITH_KEYWORDS_r00 957 +#define _GUARD_CALLABLE_BUILTIN_O_r00 958 +#define _GUARD_CALLABLE_ISINSTANCE_r03 959 +#define _GUARD_CALLABLE_ISINSTANCE_r13 960 +#define _GUARD_CALLABLE_ISINSTANCE_r23 961 +#define _GUARD_CALLABLE_ISINSTANCE_r33 962 +#define _GUARD_CALLABLE_LEN_r03 963 +#define _GUARD_CALLABLE_LEN_r13 964 +#define _GUARD_CALLABLE_LEN_r23 965 +#define _GUARD_CALLABLE_LEN_r33 966 +#define _GUARD_CALLABLE_LIST_APPEND_r03 967 +#define _GUARD_CALLABLE_LIST_APPEND_r13 968 +#define _GUARD_CALLABLE_LIST_APPEND_r23 969 +#define _GUARD_CALLABLE_LIST_APPEND_r33 970 +#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_FAST_r00 971 +#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r00 972 +#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_NOARGS_r00 973 +#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_O_r00 974 +#define _GUARD_CALLABLE_STR_1_r03 975 +#define _GUARD_CALLABLE_STR_1_r13 976 +#define _GUARD_CALLABLE_STR_1_r23 977 +#define _GUARD_CALLABLE_STR_1_r33 978 +#define _GUARD_CALLABLE_TUPLE_1_r03 979 +#define _GUARD_CALLABLE_TUPLE_1_r13 980 +#define _GUARD_CALLABLE_TUPLE_1_r23 981 +#define _GUARD_CALLABLE_TUPLE_1_r33 982 +#define _GUARD_CALLABLE_TYPE_1_r03 983 +#define _GUARD_CALLABLE_TYPE_1_r13 984 +#define _GUARD_CALLABLE_TYPE_1_r23 985 +#define _GUARD_CALLABLE_TYPE_1_r33 986 +#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r00 987 +#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r11 988 +#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r22 989 +#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r33 990 +#define _GUARD_CODE_VERSION_RETURN_VALUE_r00 991 +#define _GUARD_CODE_VERSION_RETURN_VALUE_r11 992 +#define _GUARD_CODE_VERSION_RETURN_VALUE_r22 993 +#define _GUARD_CODE_VERSION_RETURN_VALUE_r33 994 +#define _GUARD_CODE_VERSION_YIELD_VALUE_r00 995 +#define _GUARD_CODE_VERSION_YIELD_VALUE_r11 996 +#define _GUARD_CODE_VERSION_YIELD_VALUE_r22 997 +#define _GUARD_CODE_VERSION_YIELD_VALUE_r33 998 +#define _GUARD_CODE_VERSION__PUSH_FRAME_r00 999 +#define _GUARD_CODE_VERSION__PUSH_FRAME_r11 1000 +#define _GUARD_CODE_VERSION__PUSH_FRAME_r22 1001 +#define _GUARD_CODE_VERSION__PUSH_FRAME_r33 1002 +#define _GUARD_DORV_NO_DICT_r01 1003 +#define _GUARD_DORV_NO_DICT_r11 1004 +#define _GUARD_DORV_NO_DICT_r22 1005 +#define _GUARD_DORV_NO_DICT_r33 1006 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 1007 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 1008 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 1009 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 1010 +#define _GUARD_GLOBALS_VERSION_r00 1011 +#define _GUARD_GLOBALS_VERSION_r11 1012 +#define _GUARD_GLOBALS_VERSION_r22 1013 +#define _GUARD_GLOBALS_VERSION_r33 1014 +#define _GUARD_IP_RETURN_GENERATOR_r00 1015 +#define _GUARD_IP_RETURN_GENERATOR_r11 1016 +#define _GUARD_IP_RETURN_GENERATOR_r22 1017 +#define _GUARD_IP_RETURN_GENERATOR_r33 1018 +#define _GUARD_IP_RETURN_VALUE_r00 1019 +#define _GUARD_IP_RETURN_VALUE_r11 1020 +#define _GUARD_IP_RETURN_VALUE_r22 1021 +#define _GUARD_IP_RETURN_VALUE_r33 1022 +#define _GUARD_IP_YIELD_VALUE_r00 1023 +#define _GUARD_IP_YIELD_VALUE_r11 1024 +#define _GUARD_IP_YIELD_VALUE_r22 1025 +#define _GUARD_IP_YIELD_VALUE_r33 1026 +#define _GUARD_IP__PUSH_FRAME_r00 1027 +#define _GUARD_IP__PUSH_FRAME_r11 1028 +#define _GUARD_IP__PUSH_FRAME_r22 1029 +#define _GUARD_IP__PUSH_FRAME_r33 1030 +#define _GUARD_IS_FALSE_POP_r00 1031 +#define _GUARD_IS_FALSE_POP_r10 1032 +#define _GUARD_IS_FALSE_POP_r21 1033 +#define _GUARD_IS_FALSE_POP_r32 1034 +#define _GUARD_IS_NONE_POP_r00 1035 +#define _GUARD_IS_NONE_POP_r10 1036 +#define _GUARD_IS_NONE_POP_r21 1037 +#define _GUARD_IS_NONE_POP_r32 1038 +#define _GUARD_IS_NOT_NONE_POP_r10 1039 +#define _GUARD_IS_TRUE_POP_r00 1040 +#define _GUARD_IS_TRUE_POP_r10 1041 +#define _GUARD_IS_TRUE_POP_r21 1042 +#define _GUARD_IS_TRUE_POP_r32 1043 +#define _GUARD_ITERATOR_r01 1044 +#define _GUARD_ITERATOR_r11 1045 +#define _GUARD_ITERATOR_r22 1046 +#define _GUARD_ITERATOR_r33 1047 +#define _GUARD_ITER_VIRTUAL_r01 1048 +#define _GUARD_ITER_VIRTUAL_r11 1049 +#define _GUARD_ITER_VIRTUAL_r22 1050 +#define _GUARD_ITER_VIRTUAL_r33 1051 +#define _GUARD_KEYS_VERSION_r01 1052 +#define _GUARD_KEYS_VERSION_r11 1053 +#define _GUARD_KEYS_VERSION_r22 1054 +#define _GUARD_KEYS_VERSION_r33 1055 +#define _GUARD_LOAD_SUPER_ATTR_METHOD_r03 1056 +#define _GUARD_LOAD_SUPER_ATTR_METHOD_r13 1057 +#define _GUARD_LOAD_SUPER_ATTR_METHOD_r23 1058 +#define _GUARD_LOAD_SUPER_ATTR_METHOD_r33 1059 +#define _GUARD_NOS_ANY_DICT_r02 1060 +#define _GUARD_NOS_ANY_DICT_r12 1061 +#define _GUARD_NOS_ANY_DICT_r22 1062 +#define _GUARD_NOS_ANY_DICT_r33 1063 +#define _GUARD_NOS_COMPACT_ASCII_r02 1064 +#define _GUARD_NOS_COMPACT_ASCII_r12 1065 +#define _GUARD_NOS_COMPACT_ASCII_r22 1066 +#define _GUARD_NOS_COMPACT_ASCII_r33 1067 +#define _GUARD_NOS_DICT_r02 1068 +#define _GUARD_NOS_DICT_r12 1069 +#define _GUARD_NOS_DICT_r22 1070 +#define _GUARD_NOS_DICT_r33 1071 +#define _GUARD_NOS_FLOAT_r02 1072 +#define _GUARD_NOS_FLOAT_r12 1073 +#define _GUARD_NOS_FLOAT_r22 1074 +#define _GUARD_NOS_FLOAT_r33 1075 +#define _GUARD_NOS_INT_r02 1076 +#define _GUARD_NOS_INT_r12 1077 +#define _GUARD_NOS_INT_r22 1078 +#define _GUARD_NOS_INT_r33 1079 +#define _GUARD_NOS_ITER_VIRTUAL_r02 1080 +#define _GUARD_NOS_ITER_VIRTUAL_r12 1081 +#define _GUARD_NOS_ITER_VIRTUAL_r22 1082 +#define _GUARD_NOS_ITER_VIRTUAL_r33 1083 +#define _GUARD_NOS_LIST_r02 1084 +#define _GUARD_NOS_LIST_r12 1085 +#define _GUARD_NOS_LIST_r22 1086 +#define _GUARD_NOS_LIST_r33 1087 +#define _GUARD_NOS_NOT_NULL_r02 1088 +#define _GUARD_NOS_NOT_NULL_r12 1089 +#define _GUARD_NOS_NOT_NULL_r22 1090 +#define _GUARD_NOS_NOT_NULL_r33 1091 +#define _GUARD_NOS_NULL_r02 1092 +#define _GUARD_NOS_NULL_r12 1093 +#define _GUARD_NOS_NULL_r22 1094 +#define _GUARD_NOS_NULL_r33 1095 +#define _GUARD_NOS_OVERFLOWED_r02 1096 +#define _GUARD_NOS_OVERFLOWED_r12 1097 +#define _GUARD_NOS_OVERFLOWED_r22 1098 +#define _GUARD_NOS_OVERFLOWED_r33 1099 +#define _GUARD_NOS_TUPLE_r02 1100 +#define _GUARD_NOS_TUPLE_r12 1101 +#define _GUARD_NOS_TUPLE_r22 1102 +#define _GUARD_NOS_TUPLE_r33 1103 +#define _GUARD_NOS_TYPE_VERSION_r02 1104 +#define _GUARD_NOS_TYPE_VERSION_r12 1105 +#define _GUARD_NOS_TYPE_VERSION_r22 1106 +#define _GUARD_NOS_TYPE_VERSION_r33 1107 +#define _GUARD_NOS_UNICODE_r02 1108 +#define _GUARD_NOS_UNICODE_r12 1109 +#define _GUARD_NOS_UNICODE_r22 1110 +#define _GUARD_NOS_UNICODE_r33 1111 +#define _GUARD_NOT_EXHAUSTED_LIST_r02 1112 +#define _GUARD_NOT_EXHAUSTED_LIST_r12 1113 +#define _GUARD_NOT_EXHAUSTED_LIST_r22 1114 +#define _GUARD_NOT_EXHAUSTED_LIST_r33 1115 +#define _GUARD_NOT_EXHAUSTED_RANGE_r02 1116 +#define _GUARD_NOT_EXHAUSTED_RANGE_r12 1117 +#define _GUARD_NOT_EXHAUSTED_RANGE_r22 1118 +#define _GUARD_NOT_EXHAUSTED_RANGE_r33 1119 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 1120 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 1121 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 1122 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 1123 +#define _GUARD_THIRD_NULL_r03 1124 +#define _GUARD_THIRD_NULL_r13 1125 +#define _GUARD_THIRD_NULL_r23 1126 +#define _GUARD_THIRD_NULL_r33 1127 +#define _GUARD_TOS_ANY_DICT_r01 1128 +#define _GUARD_TOS_ANY_DICT_r11 1129 +#define _GUARD_TOS_ANY_DICT_r22 1130 +#define _GUARD_TOS_ANY_DICT_r33 1131 +#define _GUARD_TOS_ANY_SET_r01 1132 +#define _GUARD_TOS_ANY_SET_r11 1133 +#define _GUARD_TOS_ANY_SET_r22 1134 +#define _GUARD_TOS_ANY_SET_r33 1135 +#define _GUARD_TOS_DICT_r01 1136 +#define _GUARD_TOS_DICT_r11 1137 +#define _GUARD_TOS_DICT_r22 1138 +#define _GUARD_TOS_DICT_r33 1139 +#define _GUARD_TOS_FLOAT_r01 1140 +#define _GUARD_TOS_FLOAT_r11 1141 +#define _GUARD_TOS_FLOAT_r22 1142 +#define _GUARD_TOS_FLOAT_r33 1143 +#define _GUARD_TOS_FROZENDICT_r01 1144 +#define _GUARD_TOS_FROZENDICT_r11 1145 +#define _GUARD_TOS_FROZENDICT_r22 1146 +#define _GUARD_TOS_FROZENDICT_r33 1147 +#define _GUARD_TOS_FROZENSET_r01 1148 +#define _GUARD_TOS_FROZENSET_r11 1149 +#define _GUARD_TOS_FROZENSET_r22 1150 +#define _GUARD_TOS_FROZENSET_r33 1151 +#define _GUARD_TOS_INT_r01 1152 +#define _GUARD_TOS_INT_r11 1153 +#define _GUARD_TOS_INT_r22 1154 +#define _GUARD_TOS_INT_r33 1155 +#define _GUARD_TOS_LIST_r01 1156 +#define _GUARD_TOS_LIST_r11 1157 +#define _GUARD_TOS_LIST_r22 1158 +#define _GUARD_TOS_LIST_r33 1159 +#define _GUARD_TOS_OVERFLOWED_r01 1160 +#define _GUARD_TOS_OVERFLOWED_r11 1161 +#define _GUARD_TOS_OVERFLOWED_r22 1162 +#define _GUARD_TOS_OVERFLOWED_r33 1163 +#define _GUARD_TOS_SET_r01 1164 +#define _GUARD_TOS_SET_r11 1165 +#define _GUARD_TOS_SET_r22 1166 +#define _GUARD_TOS_SET_r33 1167 +#define _GUARD_TOS_SLICE_r01 1168 +#define _GUARD_TOS_SLICE_r11 1169 +#define _GUARD_TOS_SLICE_r22 1170 +#define _GUARD_TOS_SLICE_r33 1171 +#define _GUARD_TOS_TUPLE_r01 1172 +#define _GUARD_TOS_TUPLE_r11 1173 +#define _GUARD_TOS_TUPLE_r22 1174 +#define _GUARD_TOS_TUPLE_r33 1175 +#define _GUARD_TOS_UNICODE_r01 1176 +#define _GUARD_TOS_UNICODE_r11 1177 +#define _GUARD_TOS_UNICODE_r22 1178 +#define _GUARD_TOS_UNICODE_r33 1179 +#define _GUARD_TYPE_r01 1180 +#define _GUARD_TYPE_r11 1181 +#define _GUARD_TYPE_r22 1182 +#define _GUARD_TYPE_r33 1183 +#define _GUARD_TYPE_VERSION_r01 1184 +#define _GUARD_TYPE_VERSION_r11 1185 +#define _GUARD_TYPE_VERSION_r22 1186 +#define _GUARD_TYPE_VERSION_r33 1187 +#define _GUARD_TYPE_VERSION_LOCKED_r01 1188 +#define _GUARD_TYPE_VERSION_LOCKED_r11 1189 +#define _GUARD_TYPE_VERSION_LOCKED_r22 1190 +#define _GUARD_TYPE_VERSION_LOCKED_r33 1191 +#define _HANDLE_PENDING_AND_DEOPT_r00 1192 +#define _HANDLE_PENDING_AND_DEOPT_r10 1193 +#define _HANDLE_PENDING_AND_DEOPT_r20 1194 +#define _HANDLE_PENDING_AND_DEOPT_r30 1195 +#define _IMPORT_FROM_r12 1196 +#define _IMPORT_NAME_r21 1197 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 1198 +#define _INIT_CALL_PY_EXACT_ARGS_r01 1199 +#define _INIT_CALL_PY_EXACT_ARGS_0_r01 1200 +#define _INIT_CALL_PY_EXACT_ARGS_1_r01 1201 +#define _INIT_CALL_PY_EXACT_ARGS_2_r01 1202 +#define _INIT_CALL_PY_EXACT_ARGS_3_r01 1203 +#define _INIT_CALL_PY_EXACT_ARGS_4_r01 1204 +#define _INSERT_NULL_r10 1205 +#define _INSTRUMENTED_FOR_ITER_r23 1206 +#define _INSTRUMENTED_INSTRUCTION_r00 1207 +#define _INSTRUMENTED_JUMP_FORWARD_r00 1208 +#define _INSTRUMENTED_JUMP_FORWARD_r11 1209 +#define _INSTRUMENTED_JUMP_FORWARD_r22 1210 +#define _INSTRUMENTED_JUMP_FORWARD_r33 1211 +#define _INSTRUMENTED_LINE_r00 1212 +#define _INSTRUMENTED_NOT_TAKEN_r00 1213 +#define _INSTRUMENTED_NOT_TAKEN_r11 1214 +#define _INSTRUMENTED_NOT_TAKEN_r22 1215 +#define _INSTRUMENTED_NOT_TAKEN_r33 1216 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 1217 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 1218 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 1219 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 1220 +#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 1221 +#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 1222 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 1223 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 1224 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 1225 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 1226 +#define _IS_NONE_r11 1227 +#define _IS_OP_r03 1228 +#define _IS_OP_r13 1229 +#define _IS_OP_r23 1230 +#define _ITER_CHECK_LIST_r02 1231 +#define _ITER_CHECK_LIST_r12 1232 +#define _ITER_CHECK_LIST_r22 1233 +#define _ITER_CHECK_LIST_r33 1234 +#define _ITER_CHECK_RANGE_r02 1235 +#define _ITER_CHECK_RANGE_r12 1236 +#define _ITER_CHECK_RANGE_r22 1237 +#define _ITER_CHECK_RANGE_r33 1238 +#define _ITER_CHECK_TUPLE_r02 1239 +#define _ITER_CHECK_TUPLE_r12 1240 +#define _ITER_CHECK_TUPLE_r22 1241 +#define _ITER_CHECK_TUPLE_r33 1242 +#define _ITER_JUMP_LIST_r02 1243 +#define _ITER_JUMP_LIST_r12 1244 +#define _ITER_JUMP_LIST_r22 1245 +#define _ITER_JUMP_LIST_r33 1246 +#define _ITER_JUMP_RANGE_r02 1247 +#define _ITER_JUMP_RANGE_r12 1248 +#define _ITER_JUMP_RANGE_r22 1249 +#define _ITER_JUMP_RANGE_r33 1250 +#define _ITER_JUMP_TUPLE_r02 1251 +#define _ITER_JUMP_TUPLE_r12 1252 +#define _ITER_JUMP_TUPLE_r22 1253 +#define _ITER_JUMP_TUPLE_r33 1254 +#define _ITER_NEXT_LIST_r23 1255 +#define _ITER_NEXT_LIST_TIER_TWO_r23 1256 +#define _ITER_NEXT_RANGE_r03 1257 +#define _ITER_NEXT_RANGE_r13 1258 +#define _ITER_NEXT_RANGE_r23 1259 +#define _ITER_NEXT_TUPLE_r03 1260 +#define _ITER_NEXT_TUPLE_r13 1261 +#define _ITER_NEXT_TUPLE_r23 1262 +#define _JUMP_BACKWARD_NO_INTERRUPT_r00 1263 +#define _JUMP_BACKWARD_NO_INTERRUPT_r11 1264 +#define _JUMP_BACKWARD_NO_INTERRUPT_r22 1265 +#define _JUMP_BACKWARD_NO_INTERRUPT_r33 1266 +#define _JUMP_TO_TOP_r00 1267 +#define _LIST_APPEND_r10 1268 +#define _LIST_EXTEND_r11 1269 +#define _LOAD_ATTR_r10 1270 +#define _LOAD_ATTR_CLASS_r11 1271 +#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME_r11 1272 +#define _LOAD_ATTR_INSTANCE_VALUE_r02 1273 +#define _LOAD_ATTR_INSTANCE_VALUE_r12 1274 +#define _LOAD_ATTR_INSTANCE_VALUE_r23 1275 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 1276 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 1277 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 1278 +#define _LOAD_ATTR_METHOD_NO_DICT_r02 1279 +#define _LOAD_ATTR_METHOD_NO_DICT_r12 1280 +#define _LOAD_ATTR_METHOD_NO_DICT_r23 1281 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 1282 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 1283 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 1284 +#define _LOAD_ATTR_MODULE_r12 1285 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 1286 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1287 +#define _LOAD_ATTR_PROPERTY_FRAME_r01 1288 +#define _LOAD_ATTR_PROPERTY_FRAME_r11 1289 +#define _LOAD_ATTR_PROPERTY_FRAME_r22 1290 +#define _LOAD_ATTR_PROPERTY_FRAME_r33 1291 +#define _LOAD_ATTR_SLOT_r02 1292 +#define _LOAD_ATTR_SLOT_r12 1293 +#define _LOAD_ATTR_SLOT_r23 1294 +#define _LOAD_ATTR_WITH_HINT_r12 1295 +#define _LOAD_BUILD_CLASS_r01 1296 +#define _LOAD_BYTECODE_r00 1297 +#define _LOAD_COMMON_CONSTANT_r01 1298 +#define _LOAD_COMMON_CONSTANT_r12 1299 +#define _LOAD_COMMON_CONSTANT_r23 1300 +#define _LOAD_CONST_r01 1301 +#define _LOAD_CONST_r12 1302 +#define _LOAD_CONST_r23 1303 +#define _LOAD_CONST_INLINE_r01 1304 +#define _LOAD_CONST_INLINE_r12 1305 +#define _LOAD_CONST_INLINE_r23 1306 +#define _LOAD_CONST_INLINE_BORROW_r01 1307 +#define _LOAD_CONST_INLINE_BORROW_r12 1308 +#define _LOAD_CONST_INLINE_BORROW_r23 1309 +#define _LOAD_DEREF_r01 1310 +#define _LOAD_FAST_r01 1311 +#define _LOAD_FAST_r12 1312 +#define _LOAD_FAST_r23 1313 +#define _LOAD_FAST_0_r01 1314 +#define _LOAD_FAST_0_r12 1315 +#define _LOAD_FAST_0_r23 1316 +#define _LOAD_FAST_1_r01 1317 +#define _LOAD_FAST_1_r12 1318 +#define _LOAD_FAST_1_r23 1319 +#define _LOAD_FAST_2_r01 1320 +#define _LOAD_FAST_2_r12 1321 +#define _LOAD_FAST_2_r23 1322 +#define _LOAD_FAST_3_r01 1323 +#define _LOAD_FAST_3_r12 1324 +#define _LOAD_FAST_3_r23 1325 +#define _LOAD_FAST_4_r01 1326 +#define _LOAD_FAST_4_r12 1327 +#define _LOAD_FAST_4_r23 1328 +#define _LOAD_FAST_5_r01 1329 +#define _LOAD_FAST_5_r12 1330 +#define _LOAD_FAST_5_r23 1331 +#define _LOAD_FAST_6_r01 1332 +#define _LOAD_FAST_6_r12 1333 +#define _LOAD_FAST_6_r23 1334 +#define _LOAD_FAST_7_r01 1335 +#define _LOAD_FAST_7_r12 1336 +#define _LOAD_FAST_7_r23 1337 +#define _LOAD_FAST_AND_CLEAR_r01 1338 +#define _LOAD_FAST_AND_CLEAR_r12 1339 +#define _LOAD_FAST_AND_CLEAR_r23 1340 +#define _LOAD_FAST_BORROW_r01 1341 +#define _LOAD_FAST_BORROW_r12 1342 +#define _LOAD_FAST_BORROW_r23 1343 +#define _LOAD_FAST_BORROW_0_r01 1344 +#define _LOAD_FAST_BORROW_0_r12 1345 +#define _LOAD_FAST_BORROW_0_r23 1346 +#define _LOAD_FAST_BORROW_1_r01 1347 +#define _LOAD_FAST_BORROW_1_r12 1348 +#define _LOAD_FAST_BORROW_1_r23 1349 +#define _LOAD_FAST_BORROW_2_r01 1350 +#define _LOAD_FAST_BORROW_2_r12 1351 +#define _LOAD_FAST_BORROW_2_r23 1352 +#define _LOAD_FAST_BORROW_3_r01 1353 +#define _LOAD_FAST_BORROW_3_r12 1354 +#define _LOAD_FAST_BORROW_3_r23 1355 +#define _LOAD_FAST_BORROW_4_r01 1356 +#define _LOAD_FAST_BORROW_4_r12 1357 +#define _LOAD_FAST_BORROW_4_r23 1358 +#define _LOAD_FAST_BORROW_5_r01 1359 +#define _LOAD_FAST_BORROW_5_r12 1360 +#define _LOAD_FAST_BORROW_5_r23 1361 +#define _LOAD_FAST_BORROW_6_r01 1362 +#define _LOAD_FAST_BORROW_6_r12 1363 +#define _LOAD_FAST_BORROW_6_r23 1364 +#define _LOAD_FAST_BORROW_7_r01 1365 +#define _LOAD_FAST_BORROW_7_r12 1366 +#define _LOAD_FAST_BORROW_7_r23 1367 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1368 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1369 +#define _LOAD_FAST_CHECK_r01 1370 +#define _LOAD_FAST_CHECK_r12 1371 +#define _LOAD_FAST_CHECK_r23 1372 +#define _LOAD_FAST_LOAD_FAST_r02 1373 +#define _LOAD_FAST_LOAD_FAST_r13 1374 +#define _LOAD_FROM_DICT_OR_DEREF_r11 1375 +#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1376 +#define _LOAD_GLOBAL_r00 1377 +#define _LOAD_GLOBAL_BUILTINS_r01 1378 +#define _LOAD_GLOBAL_MODULE_r01 1379 +#define _LOAD_LOCALS_r01 1380 +#define _LOAD_LOCALS_r12 1381 +#define _LOAD_LOCALS_r23 1382 +#define _LOAD_NAME_r01 1383 +#define _LOAD_SMALL_INT_r01 1384 +#define _LOAD_SMALL_INT_r12 1385 +#define _LOAD_SMALL_INT_r23 1386 +#define _LOAD_SMALL_INT_0_r01 1387 +#define _LOAD_SMALL_INT_0_r12 1388 +#define _LOAD_SMALL_INT_0_r23 1389 +#define _LOAD_SMALL_INT_1_r01 1390 +#define _LOAD_SMALL_INT_1_r12 1391 +#define _LOAD_SMALL_INT_1_r23 1392 +#define _LOAD_SMALL_INT_2_r01 1393 +#define _LOAD_SMALL_INT_2_r12 1394 +#define _LOAD_SMALL_INT_2_r23 1395 +#define _LOAD_SMALL_INT_3_r01 1396 +#define _LOAD_SMALL_INT_3_r12 1397 +#define _LOAD_SMALL_INT_3_r23 1398 +#define _LOAD_SPECIAL_r00 1399 +#define _LOAD_SUPER_ATTR_ATTR_r31 1400 +#define _LOAD_SUPER_ATTR_METHOD_r32 1401 +#define _LOCK_OBJECT_r01 1402 +#define _LOCK_OBJECT_r11 1403 +#define _LOCK_OBJECT_r22 1404 +#define _LOCK_OBJECT_r33 1405 +#define _MAKE_CALLARGS_A_TUPLE_r33 1406 +#define _MAKE_CELL_r00 1407 +#define _MAKE_FUNCTION_r12 1408 +#define _MAKE_HEAP_SAFE_r01 1409 +#define _MAKE_HEAP_SAFE_r11 1410 +#define _MAKE_HEAP_SAFE_r22 1411 +#define _MAKE_HEAP_SAFE_r33 1412 +#define _MAKE_WARM_r00 1413 +#define _MAKE_WARM_r11 1414 +#define _MAKE_WARM_r22 1415 +#define _MAKE_WARM_r33 1416 +#define _MAP_ADD_r20 1417 +#define _MATCH_CLASS_r33 1418 +#define _MATCH_KEYS_r23 1419 +#define _MATCH_MAPPING_r02 1420 +#define _MATCH_MAPPING_r12 1421 +#define _MATCH_MAPPING_r23 1422 +#define _MATCH_SEQUENCE_r02 1423 +#define _MATCH_SEQUENCE_r12 1424 +#define _MATCH_SEQUENCE_r23 1425 +#define _MAYBE_EXPAND_METHOD_r00 1426 +#define _MAYBE_EXPAND_METHOD_KW_r11 1427 +#define _MONITOR_CALL_r00 1428 +#define _MONITOR_CALL_KW_r11 1429 +#define _MONITOR_JUMP_BACKWARD_r00 1430 +#define _MONITOR_JUMP_BACKWARD_r11 1431 +#define _MONITOR_JUMP_BACKWARD_r22 1432 +#define _MONITOR_JUMP_BACKWARD_r33 1433 +#define _MONITOR_RESUME_r00 1434 +#define _NOP_r00 1435 +#define _NOP_r11 1436 +#define _NOP_r22 1437 +#define _NOP_r33 1438 +#define _POP_EXCEPT_r10 1439 +#define _POP_ITER_r20 1440 +#define _POP_JUMP_IF_FALSE_r00 1441 +#define _POP_JUMP_IF_FALSE_r10 1442 +#define _POP_JUMP_IF_FALSE_r21 1443 +#define _POP_JUMP_IF_FALSE_r32 1444 +#define _POP_JUMP_IF_TRUE_r00 1445 +#define _POP_JUMP_IF_TRUE_r10 1446 +#define _POP_JUMP_IF_TRUE_r21 1447 +#define _POP_JUMP_IF_TRUE_r32 1448 +#define _POP_TOP_r10 1449 +#define _POP_TOP_FLOAT_r00 1450 +#define _POP_TOP_FLOAT_r10 1451 +#define _POP_TOP_FLOAT_r21 1452 +#define _POP_TOP_FLOAT_r32 1453 +#define _POP_TOP_INT_r00 1454 +#define _POP_TOP_INT_r10 1455 +#define _POP_TOP_INT_r21 1456 +#define _POP_TOP_INT_r32 1457 +#define _POP_TOP_NOP_r00 1458 +#define _POP_TOP_NOP_r10 1459 +#define _POP_TOP_NOP_r21 1460 +#define _POP_TOP_NOP_r32 1461 +#define _POP_TOP_OPARG_r00 1462 +#define _POP_TOP_UNICODE_r00 1463 +#define _POP_TOP_UNICODE_r10 1464 +#define _POP_TOP_UNICODE_r21 1465 +#define _POP_TOP_UNICODE_r32 1466 +#define _PUSH_EXC_INFO_r02 1467 +#define _PUSH_EXC_INFO_r12 1468 +#define _PUSH_EXC_INFO_r23 1469 +#define _PUSH_FRAME_r10 1470 +#define _PUSH_NULL_r01 1471 +#define _PUSH_NULL_r12 1472 +#define _PUSH_NULL_r23 1473 +#define _PUSH_NULL_CONDITIONAL_r00 1474 +#define _PUSH_TAGGED_ZERO_r01 1475 +#define _PUSH_TAGGED_ZERO_r12 1476 +#define _PUSH_TAGGED_ZERO_r23 1477 +#define _PY_FRAME_EX_r31 1478 +#define _PY_FRAME_GENERAL_r01 1479 +#define _PY_FRAME_KW_r11 1480 +#define _REPLACE_WITH_TRUE_r02 1481 +#define _REPLACE_WITH_TRUE_r12 1482 +#define _REPLACE_WITH_TRUE_r23 1483 +#define _RESUME_CHECK_r00 1484 +#define _RESUME_CHECK_r11 1485 +#define _RESUME_CHECK_r22 1486 +#define _RESUME_CHECK_r33 1487 +#define _RETURN_GENERATOR_r01 1488 +#define _RETURN_VALUE_r11 1489 +#define _RROT_3_r03 1490 +#define _RROT_3_r13 1491 +#define _RROT_3_r23 1492 +#define _RROT_3_r33 1493 +#define _SAVE_RETURN_OFFSET_r00 1494 +#define _SAVE_RETURN_OFFSET_r11 1495 +#define _SAVE_RETURN_OFFSET_r22 1496 +#define _SAVE_RETURN_OFFSET_r33 1497 +#define _SEND_r33 1498 +#define _SEND_GEN_FRAME_r33 1499 +#define _SETUP_ANNOTATIONS_r00 1500 +#define _SET_ADD_r10 1501 +#define _SET_FUNCTION_ATTRIBUTE_r01 1502 +#define _SET_FUNCTION_ATTRIBUTE_r11 1503 +#define _SET_FUNCTION_ATTRIBUTE_r21 1504 +#define _SET_FUNCTION_ATTRIBUTE_r32 1505 +#define _SET_IP_r00 1506 +#define _SET_IP_r11 1507 +#define _SET_IP_r22 1508 +#define _SET_IP_r33 1509 +#define _SET_UPDATE_r11 1510 +#define _SPILL_OR_RELOAD_r01 1511 +#define _SPILL_OR_RELOAD_r02 1512 +#define _SPILL_OR_RELOAD_r03 1513 +#define _SPILL_OR_RELOAD_r10 1514 +#define _SPILL_OR_RELOAD_r12 1515 +#define _SPILL_OR_RELOAD_r13 1516 +#define _SPILL_OR_RELOAD_r20 1517 +#define _SPILL_OR_RELOAD_r21 1518 +#define _SPILL_OR_RELOAD_r23 1519 +#define _SPILL_OR_RELOAD_r30 1520 +#define _SPILL_OR_RELOAD_r31 1521 +#define _SPILL_OR_RELOAD_r32 1522 +#define _START_EXECUTOR_r00 1523 +#define _STORE_ATTR_r20 1524 +#define _STORE_ATTR_INSTANCE_VALUE_r21 1525 +#define _STORE_ATTR_SLOT_r21 1526 +#define _STORE_ATTR_WITH_HINT_r21 1527 +#define _STORE_DEREF_r10 1528 +#define _STORE_FAST_LOAD_FAST_r11 1529 +#define _STORE_FAST_STORE_FAST_r20 1530 +#define _STORE_GLOBAL_r10 1531 +#define _STORE_NAME_r10 1532 +#define _STORE_SLICE_r30 1533 +#define _STORE_SUBSCR_r30 1534 +#define _STORE_SUBSCR_DICT_r31 1535 +#define _STORE_SUBSCR_DICT_KNOWN_HASH_r31 1536 +#define _STORE_SUBSCR_LIST_INT_r32 1537 +#define _SWAP_r11 1538 +#define _SWAP_2_r02 1539 +#define _SWAP_2_r12 1540 +#define _SWAP_2_r22 1541 +#define _SWAP_2_r33 1542 +#define _SWAP_3_r03 1543 +#define _SWAP_3_r13 1544 +#define _SWAP_3_r23 1545 +#define _SWAP_3_r33 1546 +#define _SWAP_FAST_r01 1547 +#define _SWAP_FAST_r11 1548 +#define _SWAP_FAST_r22 1549 +#define _SWAP_FAST_r33 1550 +#define _SWAP_FAST_0_r01 1551 +#define _SWAP_FAST_0_r11 1552 +#define _SWAP_FAST_0_r22 1553 +#define _SWAP_FAST_0_r33 1554 +#define _SWAP_FAST_1_r01 1555 +#define _SWAP_FAST_1_r11 1556 +#define _SWAP_FAST_1_r22 1557 +#define _SWAP_FAST_1_r33 1558 +#define _SWAP_FAST_2_r01 1559 +#define _SWAP_FAST_2_r11 1560 +#define _SWAP_FAST_2_r22 1561 +#define _SWAP_FAST_2_r33 1562 +#define _SWAP_FAST_3_r01 1563 +#define _SWAP_FAST_3_r11 1564 +#define _SWAP_FAST_3_r22 1565 +#define _SWAP_FAST_3_r33 1566 +#define _SWAP_FAST_4_r01 1567 +#define _SWAP_FAST_4_r11 1568 +#define _SWAP_FAST_4_r22 1569 +#define _SWAP_FAST_4_r33 1570 +#define _SWAP_FAST_5_r01 1571 +#define _SWAP_FAST_5_r11 1572 +#define _SWAP_FAST_5_r22 1573 +#define _SWAP_FAST_5_r33 1574 +#define _SWAP_FAST_6_r01 1575 +#define _SWAP_FAST_6_r11 1576 +#define _SWAP_FAST_6_r22 1577 +#define _SWAP_FAST_6_r33 1578 +#define _SWAP_FAST_7_r01 1579 +#define _SWAP_FAST_7_r11 1580 +#define _SWAP_FAST_7_r22 1581 +#define _SWAP_FAST_7_r33 1582 +#define _TIER2_RESUME_CHECK_r00 1583 +#define _TIER2_RESUME_CHECK_r11 1584 +#define _TIER2_RESUME_CHECK_r22 1585 +#define _TIER2_RESUME_CHECK_r33 1586 +#define _TO_BOOL_r11 1587 +#define _TO_BOOL_BOOL_r01 1588 +#define _TO_BOOL_BOOL_r11 1589 +#define _TO_BOOL_BOOL_r22 1590 +#define _TO_BOOL_BOOL_r33 1591 +#define _TO_BOOL_INT_r02 1592 +#define _TO_BOOL_INT_r12 1593 +#define _TO_BOOL_INT_r23 1594 +#define _TO_BOOL_LIST_r02 1595 +#define _TO_BOOL_LIST_r12 1596 +#define _TO_BOOL_LIST_r23 1597 +#define _TO_BOOL_NONE_r01 1598 +#define _TO_BOOL_NONE_r11 1599 +#define _TO_BOOL_NONE_r22 1600 +#define _TO_BOOL_NONE_r33 1601 +#define _TO_BOOL_STR_r02 1602 +#define _TO_BOOL_STR_r12 1603 +#define _TO_BOOL_STR_r23 1604 +#define _TRACE_RECORD_r00 1605 +#define _UNARY_INVERT_r12 1606 +#define _UNARY_NEGATIVE_r12 1607 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r02 1608 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r12 1609 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r23 1610 +#define _UNARY_NOT_r01 1611 +#define _UNARY_NOT_r11 1612 +#define _UNARY_NOT_r22 1613 +#define _UNARY_NOT_r33 1614 +#define _UNPACK_EX_r10 1615 +#define _UNPACK_SEQUENCE_r10 1616 +#define _UNPACK_SEQUENCE_LIST_r10 1617 +#define _UNPACK_SEQUENCE_TUPLE_r10 1618 +#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1619 +#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r03 1620 +#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r13 1621 +#define _UNPACK_SEQUENCE_UNIQUE_TUPLE_r10 1622 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r02 1623 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r12 1624 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r23 1625 +#define _WITH_EXCEPT_START_r33 1626 +#define _YIELD_VALUE_r11 1627 +#define MAX_UOP_REGS_ID 1627 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 17fd5d04838..8f543dbeeb8 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -397,6 +397,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, [_LOAD_CONST_INLINE] = HAS_PURE_FLAG, [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, + [_RROT_3] = HAS_PURE_FLAG, [_START_EXECUTOR] = HAS_DEOPT_FLAG, [_MAKE_WARM] = 0, [_FATAL_ERROR] = 0, @@ -3699,6 +3700,15 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { { -1, -1, -1 }, }, }, + [_RROT_3] = { + .best = { 0, 1, 2, 3 }, + .entries = { + { 3, 0, _RROT_3_r03 }, + { 3, 1, _RROT_3_r13 }, + { 3, 2, _RROT_3_r23 }, + { 3, 3, _RROT_3_r33 }, + }, + }, [_START_EXECUTOR] = { .best = { 0, 0, 0, 0 }, .entries = { @@ -4695,6 +4705,10 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_LOAD_CONST_INLINE_BORROW_r01] = _LOAD_CONST_INLINE_BORROW, [_LOAD_CONST_INLINE_BORROW_r12] = _LOAD_CONST_INLINE_BORROW, [_LOAD_CONST_INLINE_BORROW_r23] = _LOAD_CONST_INLINE_BORROW, + [_RROT_3_r03] = _RROT_3, + [_RROT_3_r13] = _RROT_3, + [_RROT_3_r23] = _RROT_3, + [_RROT_3_r33] = _RROT_3, [_START_EXECUTOR_r00] = _START_EXECUTOR, [_MAKE_WARM_r00] = _MAKE_WARM, [_MAKE_WARM_r11] = _MAKE_WARM, @@ -5896,6 +5910,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_RETURN_GENERATOR_r01] = "_RETURN_GENERATOR_r01", [_RETURN_VALUE] = "_RETURN_VALUE", [_RETURN_VALUE_r11] = "_RETURN_VALUE_r11", + [_RROT_3] = "_RROT_3", + [_RROT_3_r03] = "_RROT_3_r03", + [_RROT_3_r13] = "_RROT_3_r13", + [_RROT_3_r23] = "_RROT_3_r23", + [_RROT_3_r33] = "_RROT_3_r33", [_SAVE_RETURN_OFFSET] = "_SAVE_RETURN_OFFSET", [_SAVE_RETURN_OFFSET_r00] = "_SAVE_RETURN_OFFSET_r00", [_SAVE_RETURN_OFFSET_r11] = "_SAVE_RETURN_OFFSET_r11", @@ -6808,6 +6827,8 @@ int _PyUop_num_popped(int opcode, int oparg) return 0; case _LOAD_CONST_INLINE_BORROW: return 0; + case _RROT_3: + return 0; case _START_EXECUTOR: return 0; case _MAKE_WARM: diff --git a/Include/pyport.h b/Include/pyport.h index c975921beaf..73a3e6cdaf0 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -553,6 +553,7 @@ extern "C" { # if !defined(_Py_MEMORY_SANITIZER) # define _Py_MEMORY_SANITIZER # define _Py_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory)) +# define _Py_MSAN_UNPOISON(PTR, SIZE) (__msan_unpoison(PTR, SIZE)) # endif # endif # if __has_feature(address_sanitizer) @@ -591,6 +592,9 @@ extern "C" { #ifndef _Py_NO_SANITIZE_MEMORY # define _Py_NO_SANITIZE_MEMORY #endif +#ifndef _Py_MSAN_UNPOISON +# define _Py_MSAN_UNPOISON(PTR, SIZE) +#endif /* AIX has __bool__ redefined in it's system header file. */ #if defined(_AIX) && defined(__bool__) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index b8e1e425b0b..7e4dd801c84 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -38,12 +38,13 @@ ) from .layout import LayoutMap, LayoutResult, LayoutRow, WrappedRow, layout_content_lines from .render import RenderCell, RenderLine, RenderedScreen, ScreenOverlay -from .utils import ANSI_ESCAPE_SEQUENCE, THEME, StyleRef, wlen, gen_colors +from .utils import ANSI_ESCAPE_SEQUENCE, ColorSpan, THEME, StyleRef, wlen, gen_colors from .trace import trace # types Command = commands.Command +from collections.abc import Callable, Iterator from .types import ( Callback, CommandName, @@ -304,6 +305,7 @@ class Reader: lxy: CursorXY = field(init=False) scheduled_commands: list[CommandName] = field(default_factory=list) can_colorize: bool = False + gen_colors: Callable[[str], Iterator[ColorSpan]] = gen_colors threading_hook: Callback | None = None invalidation: RefreshInvalidation = field(init=False) @@ -534,7 +536,7 @@ def _build_content_lines( prompt_from_cache: bool, ) -> tuple[ContentLine, ...]: if self.can_colorize: - colors = list(gen_colors(self.get_unicode())) + colors = list(self.gen_colors(self.get_unicode())) else: colors = None trace("colors = {colors}", colors=colors) diff --git a/Lib/dis.py b/Lib/dis.py index 58c7f641903..64f3450da30 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -643,6 +643,7 @@ def get_argval_argrepr(self, op, arg, offset): argrepr = _intrinsic_2_descs[arg] elif deop == LOAD_COMMON_CONSTANT: obj = _common_constants[arg] + argval = obj if isinstance(obj, type): argrepr = obj.__name__ else: @@ -692,10 +693,15 @@ def _get_const_value(op, arg, co_consts): Otherwise (if it is a LOAD_CONST and co_consts is not provided) returns the dis.UNKNOWN sentinel. """ - assert op in hasconst or op == LOAD_SMALL_INT + assert op in hasconst or op == LOAD_SMALL_INT or op == LOAD_COMMON_CONSTANT if op == LOAD_SMALL_INT: return arg + if op == LOAD_COMMON_CONSTANT: + # Opargs 0-6 are callables; 7-11 are literal values. + if 7 <= arg <= 11: + return _common_constants[arg] + return UNKNOWN argval = UNKNOWN if co_consts is not None: argval = co_consts[arg] @@ -1015,8 +1021,9 @@ def _find_imports(co): if op == IMPORT_NAME and i >= 2: from_op = opargs[i-1] level_op = opargs[i-2] - if (from_op[0] in hasconst and - (level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT)): + if ((from_op[0] in hasconst or from_op[0] == LOAD_COMMON_CONSTANT) and + (level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT or + level_op[0] == LOAD_COMMON_CONSTANT)): level = _get_const_value(level_op[0], level_op[1], consts) fromlist = _get_const_value(from_op[0], from_op[1], consts) # IMPORT_NAME encodes lazy/eager flags in bits 0-1, diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py index a53903a197f..9873958f5c2 100644 --- a/Lib/email/_header_value_parser.py +++ b/Lib/email/_header_value_parser.py @@ -157,10 +157,7 @@ def all_defects(self): def startswith_fws(self): return self[0].startswith_fws() - @property - def as_ew_allowed(self): - """True if all top level tokens of this part may be RFC2047 encoded.""" - return all(part.as_ew_allowed for part in self) + as_ew_allowed = True @property def comments(self): @@ -429,6 +426,7 @@ def addr_spec(self): class AngleAddr(TokenList): token_type = 'angle-addr' + as_ew_allowed = False @property def local_part(self): @@ -847,26 +845,22 @@ def params(self): class ContentType(ParameterizedHeaderValue): token_type = 'content-type' - as_ew_allowed = False maintype = 'text' subtype = 'plain' class ContentDisposition(ParameterizedHeaderValue): token_type = 'content-disposition' - as_ew_allowed = False content_disposition = None class ContentTransferEncoding(TokenList): token_type = 'content-transfer-encoding' - as_ew_allowed = False cte = '7bit' class HeaderLabel(TokenList): token_type = 'header-label' - as_ew_allowed = False class MsgID(TokenList): @@ -1509,11 +1503,6 @@ def get_local_part(value): local_part.defects.append(errors.ObsoleteHeaderDefect( "local-part is not a dot-atom (contains CFWS)")) local_part[0] = obs_local_part - try: - local_part.value.encode('ascii') - except UnicodeEncodeError: - local_part.defects.append(errors.NonASCIILocalPartDefect( - "local-part contains non-ASCII characters)")) return local_part, value def get_obs_local_part(value): @@ -2835,13 +2824,68 @@ def _steal_trailing_WSP_if_exists(lines): def _refold_parse_tree(parse_tree, *, policy): - """Return string of contents of parse_tree folded according to RFC rules. - - """ # max_line_length 0/None means no limit, ie: infinitely long. maxlen = policy.max_line_length or sys.maxsize encoding = 'utf-8' if policy.utf8 else 'us-ascii' lines = [''] # Folded lines to be output + if parse_tree.as_ew_allowed: + _refold_with_ew(parse_tree, lines, maxlen, encoding, policy=policy) + else: + _refold_without_ew(parse_tree, lines, maxlen, encoding, policy=policy) + return policy.linesep.join(lines) + policy.linesep + +def _refold_without_ew(parse_tree, lines, maxlen, encoding, *, policy): + parts = list(parse_tree) + while parts: + part = parts.pop(0) + tstr = str(part) + try: + tstr.encode(encoding) + except UnicodeEncodeError: + if any(isinstance(x, errors.UndecodableBytesDefect) + for x in part.all_defects): + # There is garbage data from parsing a message in binary mode, + # just pass it through. Not good, but the best we can do. + pass + elif policy.utf8: + # If this happens, it's a programmer error. + raise + else: + raise errors.HeaderWriteError( + f"Non-ASCII {part.token_type} '{part}' is invalid" + " under current policy setting (utf8=False)" + ) + if len(tstr) <= maxlen - len(lines[-1]): + lines[-1] += tstr + continue + # This part is too long to fit. The RFC wants us to break at + # "major syntactic breaks", so unless we don't consider this + # to be one, check if it will fit on the next line by itself. + if (part.syntactic_break and + len(tstr) + 1 <= maxlen): + newline = _steal_trailing_WSP_if_exists(lines) + if newline or part.startswith_fws(): + lines.append(newline + tstr) + continue + if not hasattr(part, 'encode'): + # It's not a terminal, try folding the subparts. + newparts = list(part) + parts = newparts + parts + continue + # We can't figure out how to wrap, it, so give up. + newline = _steal_trailing_WSP_if_exists(lines) + if newline or part.startswith_fws(): + lines.append(newline + tstr) + else: + # We can't fold it onto the next line either... + lines[-1] += tstr + return + + +def _refold_with_ew(parse_tree, lines, maxlen, encoding, *, policy): + """Return string of contents of parse_tree folded according to RFC rules. + + """ last_word_is_ew = False last_ew = None # if there is an encoded word in the last line of lines, # points to the encoded word's first character @@ -2855,6 +2899,11 @@ def _refold_parse_tree(parse_tree, *, policy): if part is end_ew_not_allowed: wrap_as_ew_blocked -= 1 continue + if part.token_type == 'mime-parameters': + # Mime parameter folding (using RFC2231) is extra special. + _fold_mime_parameters(part, lines, maxlen, encoding) + last_word_is_ew = False + continue tstr = str(part) if not want_encoding: if part.token_type in ('ptext', 'vtext'): @@ -2876,14 +2925,11 @@ def _refold_parse_tree(parse_tree, *, policy): charset = 'utf-8' want_encoding = True - if part.token_type == 'mime-parameters': - # Mime parameter folding (using RFC2231) is extra special. - _fold_mime_parameters(part, lines, maxlen, encoding) - last_word_is_ew = False - continue - if want_encoding and not wrap_as_ew_blocked: - if not part.as_ew_allowed: + if any( + not x.as_ew_allowed for x in part + if hasattr(x, 'as_ew_allowed') + ): want_encoding = False last_ew = None if part.syntactic_break: @@ -2964,6 +3010,8 @@ def _refold_parse_tree(parse_tree, *, policy): [ValueTerminal(make_quoted_pairs(p), 'ptext') for p in newparts] + [ValueTerminal('"', 'ptext')]) + _refold_without_ew(newparts, lines, maxlen, encoding, policy=policy) + continue if part.token_type == 'comment': newparts = ( [ValueTerminal('(', 'ptext')] + @@ -2991,7 +3039,7 @@ def _refold_parse_tree(parse_tree, *, policy): lines[-1] += tstr last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP)) - return policy.linesep.join(lines) + policy.linesep + return def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, last_word_is_ew): """Fold string to_encode into lines as encoded word, combining if allowed. diff --git a/Lib/email/errors.py b/Lib/email/errors.py index 6bc744bd59c..859307dd85b 100644 --- a/Lib/email/errors.py +++ b/Lib/email/errors.py @@ -109,9 +109,9 @@ class ObsoleteHeaderDefect(HeaderDefect): """Header uses syntax declared obsolete by RFC 5322""" class NonASCIILocalPartDefect(HeaderDefect): - """local_part contains non-ASCII characters""" - # This defect only occurs during unicode parsing, not when - # parsing messages decoded from binary. + """Unused. Note: this error is deprecated and may be removed in the future.""" + # RFC 6532 permits a non-ASCII local-part. _header_value_parser previously + # treated this as a parse-time defect (when parsing Unicode, but not bytes). class InvalidDateDefect(HeaderDefect): """Header has unparsable or invalid date""" diff --git a/Lib/opcode.py b/Lib/opcode.py index 8466814d225..4e60fb5af34 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -41,7 +41,10 @@ _special_method_names = _opcode.get_special_method_names() _common_constants = [builtins.AssertionError, builtins.NotImplementedError, builtins.tuple, builtins.all, builtins.any, builtins.list, - builtins.set] + builtins.set, + # Append-only — must match CONSTANT_* in + # Include/internal/pycore_opcode_utils.h. + None, "", True, False, -1] _nb_ops = _opcode.get_nb_ops() hascompare = [opmap["COMPARE_OP"]] diff --git a/Lib/pickle.py b/Lib/pickle.py index 3e7cf25cb05..95836afdc2b 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -920,17 +920,11 @@ def save_picklebuffer(self, obj): # Write data in-band # XXX The C implementation avoids a copy here buf = m.tobytes() - in_memo = id(buf) in self.memo if m.readonly: - if in_memo: - self._save_bytes_no_memo(buf) - else: - self.save_bytes(buf) + self._save_bytes_no_memo(buf) else: - if in_memo: - self._save_bytearray_no_memo(buf) - else: - self.save_bytearray(buf) + self._save_bytearray_no_memo(buf) + self.memoize(obj) else: # Write data out-of-band self.write(NEXT_BUFFER) diff --git a/Lib/profiling/sampling/sample.py b/Lib/profiling/sampling/sample.py index 6a76bbeeb24..9195f5ee6dd 100644 --- a/Lib/profiling/sampling/sample.py +++ b/Lib/profiling/sampling/sample.py @@ -58,6 +58,10 @@ def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MOD try: self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads) except RuntimeError as err: + if os.name == "nt" and sys.executable.endswith("python.exe"): + raise SystemExit( + "Running profiling.sampling from virtualenv on Windows platform is not supported" + ) from err raise SystemExit(err) from err # Track sample intervals and total sample count self.sample_intervals = deque(maxlen=100) diff --git a/Lib/random.py b/Lib/random.py index c89cbb755ab..726a71e7828 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -836,7 +836,11 @@ def binomialvariate(self, n=1, p=0.5): if not c: return x while True: - y += _floor(_log2(random()) / c) + 1 + try: + y += _floor(_log2(random()) / c) + 1 + except ValueError: + # Reject case where random() returned 0.0 + continue if y > n: return x x += 1 diff --git a/Lib/runpy.py b/Lib/runpy.py index 9f62d20e9a2..a535b4f651a 100644 --- a/Lib/runpy.py +++ b/Lib/runpy.py @@ -102,8 +102,10 @@ def _run_module_code(code, init_globals=None, # Helper to get the full name, spec and code for a module def _get_module_details(mod_name, error=ImportError): + # name= is only accepted by ImportError and its subclasses. + kwargs = {"name": mod_name} if issubclass(error, ImportError) else {} if mod_name.startswith("."): - raise error("Relative module names not supported") + raise error("Relative module names not supported", **kwargs) pkg_name, _, _ = mod_name.rpartition(".") if pkg_name: # Try importing the parent to avoid catching initialization errors @@ -136,12 +138,13 @@ def _get_module_details(mod_name, error=ImportError): if mod_name.endswith(".py"): msg += (f". Try using '{mod_name[:-3]}' instead of " f"'{mod_name}' as the module name.") - raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex + raise error(msg.format(mod_name, type(ex).__name__, ex), + **kwargs) from ex if spec is None: - raise error("No module named %s" % mod_name) + raise error("No module named %s" % mod_name, **kwargs) if spec.submodule_search_locations is not None: if mod_name == "__main__" or mod_name.endswith(".__main__"): - raise error("Cannot use package as __main__ module") + raise error("Cannot use package as __main__ module", **kwargs) try: pkg_main_name = mod_name + ".__main__" return _get_module_details(pkg_main_name, error) @@ -149,17 +152,19 @@ def _get_module_details(mod_name, error=ImportError): if mod_name not in sys.modules: raise # No module loaded; being a package is irrelevant raise error(("%s; %r is a package and cannot " + - "be directly executed") %(e, mod_name)) + "be directly executed") %(e, mod_name), + **kwargs) loader = spec.loader if loader is None: raise error("%r is a namespace package and cannot be executed" - % mod_name) + % mod_name, + **kwargs) try: code = loader.get_code(mod_name) except ImportError as e: - raise error(format(e)) from e + raise error(format(e), **kwargs) from e if code is None: - raise error("No code object available for %s" % mod_name) + raise error("No code object available for %s" % mod_name, **kwargs) return mod_name, spec, code class _Error(Exception): @@ -232,6 +237,7 @@ def _get_main_module_details(error=ImportError): # Also moves the standard __main__ out of the way so that the # preexisting __loader__ entry doesn't cause issues main_name = "__main__" + kwargs = {"name": main_name} if issubclass(error, ImportError) else {} saved_main = sys.modules[main_name] del sys.modules[main_name] try: @@ -239,7 +245,8 @@ def _get_main_module_details(error=ImportError): except ImportError as exc: if main_name in str(exc): raise error("can't find %r module in %r" % - (main_name, sys.path[0])) from exc + (main_name, sys.path[0]), + **kwargs) from exc raise finally: sys.modules[main_name] = saved_main diff --git a/Lib/statistics.py b/Lib/statistics.py index 32fcf2313a8..01ca6c51daf 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -248,7 +248,7 @@ def count_positive(iterable): elif x == 0.0: found_zero = True else: - raise StatisticsError('No negative inputs allowed', x) + raise StatisticsError(f'No negative inputs allowed: {x!r}') total = fsum(map(log, count_positive(data))) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 7f0b0b3c632..4f47aaab902 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -337,7 +337,7 @@ class _Stream: """ def __init__(self, name, mode, comptype, fileobj, bufsize, - compresslevel, preset): + compresslevel, preset, mtime): """Construct a _Stream object. """ self._extfileobj = True @@ -372,7 +372,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, self.exception = zlib.error self._init_read_gz() else: - self._init_write_gz(compresslevel) + self._init_write_gz(compresslevel, mtime) elif comptype == "bz2": try: @@ -421,7 +421,7 @@ def __del__(self): if hasattr(self, "closed") and not self.closed: self.close() - def _init_write_gz(self, compresslevel): + def _init_write_gz(self, compresslevel, mtime): """Initialize for writing with gzip compression. """ self.cmp = self.zlib.compressobj(compresslevel, @@ -429,7 +429,9 @@ def _init_write_gz(self, compresslevel): -self.zlib.MAX_WBITS, self.zlib.DEF_MEM_LEVEL, 0) - timestamp = struct.pack("", "exec") self.assertIn("but got expr()", str(cm.exception)) @@ -1107,14 +1131,19 @@ class CopyTests(unittest.TestCase): def iter_ast_classes(): """Iterate over the (native) subclasses of ast.AST recursively. - This excludes the special class ast.Index since its constructor - returns an integer. + This excludes: + * abstract AST nodes + * the special class ast.Index, since its constructor returns + an integer. """ def do(cls): if cls.__module__ != 'ast': return if cls is ast.Index: return + # Don't attempt to create instances of abstract AST nodes + if _ast._is_abstract(cls): + return yield cls for sub in cls.__subclasses__(): @@ -2656,11 +2685,12 @@ def test_get_docstring(self): def get_load_const(self, tree): # Compile to bytecode, disassemble and get parameter of LOAD_CONST - # instructions + # and LOAD_COMMON_CONSTANT instructions co = compile(tree, '', 'exec') consts = [] for instr in dis.get_instructions(co): - if instr.opcode in dis.hasconst: + if instr.opcode in dis.hasconst or \ + instr.opname == 'LOAD_COMMON_CONSTANT': consts.append(instr.argval) return consts diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 7118dfeed9f..d4af910fb68 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3446,6 +3446,27 @@ def testfunc(n): self.assertIn("_BUILD_LIST", uops) self.assertNotIn("_LOAD_COMMON_CONSTANT", uops) + def test_load_common_constant_new_literals(self): + def testfunc(n): + x = None + s = "" + t = True + f = False + m = -1 + for _ in range(n): + x = None + s = "" + t = True + f = False + m = -1 + return x, s, t, f, m + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, (None, "", True, False, -1)) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertNotIn("_LOAD_COMMON_CONSTANT", uops) + self.assertIn("_LOAD_CONST_INLINE_BORROW", uops) + def test_load_small_int(self): def testfunc(n): x = 0 diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 5e802a929b1..3588872ed23 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -87,7 +87,7 @@ freevars: () nlocals: 0 flags: 67108867 -consts: ("'doc string'", 'None') +consts: ("'doc string'",) >>> def keywordonly_args(a,b,*,k1): ... return a,b,k1 @@ -161,7 +161,7 @@ freevars: () nlocals: 3 flags: 67108995 -consts: ("'This is a docstring from async function'", 'None') +consts: ("'This is a docstring from async function'",) >>> def no_docstring(x, y, z): ... return x + "hello" + y + z + "world" @@ -532,7 +532,7 @@ def test_co_positions_artificial_instructions(self): ], [ ("PUSH_EXC_INFO", None), - ("LOAD_CONST", None), # artificial 'None' + ("LOAD_COMMON_CONSTANT", None), # artificial 'None' ("STORE_NAME", "e"), # XX: we know the location for this ("DELETE_NAME", "e"), ("RERAISE", 1), diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index ac8837359c1..9edbca3c383 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -2485,12 +2485,13 @@ def f(): start_line, end_line, _, _ = instr.positions self.assertEqual(start_line, end_line) - # Expect four `LOAD_CONST None` instructions: + # Expect four `None`-loading instructions: # three for the no-exception __exit__ call, and one for the return. # They should all have the locations of the context manager ('xyz'). load_none = [instr for instr in dis.get_instructions(f) if - instr.opname == 'LOAD_CONST' and instr.argval is None] + instr.opname in ('LOAD_CONST', 'LOAD_COMMON_CONSTANT') + and instr.argval is None] return_value = [instr for instr in dis.get_instructions(f) if instr.opname == 'RETURN_VALUE'] diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index cc9bc918b61..8be10458581 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -56,7 +56,7 @@ def cm(cls, x): COMPARE_OP 72 (==) LOAD_FAST_BORROW 0 (self) STORE_ATTR 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (_C.__init__.__code__.co_firstlineno, _C.__init__.__code__.co_firstlineno + 1,) @@ -67,7 +67,7 @@ def cm(cls, x): COMPARE_OP 72 (==) LOAD_FAST_BORROW 0 STORE_ATTR 0 - LOAD_CONST 1 + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -79,7 +79,7 @@ def cm(cls, x): COMPARE_OP 72 (==) LOAD_FAST_BORROW 0 (cls) STORE_ATTR 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (_C.cm.__code__.co_firstlineno, _C.cm.__code__.co_firstlineno + 2,) @@ -90,7 +90,7 @@ def cm(cls, x): LOAD_SMALL_INT 1 COMPARE_OP 72 (==) STORE_FAST 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (_C.sm.__code__.co_firstlineno, _C.sm.__code__.co_firstlineno + 2,) @@ -182,7 +182,7 @@ def bug708901(): %3d L2: END_FOR POP_ITER - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (bug708901.__code__.co_firstlineno, bug708901.__code__.co_firstlineno + 1, @@ -229,7 +229,7 @@ def bug42562(): dis_bug42562 = """\ RESUME 0 - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -282,7 +282,7 @@ def wrap_func_w_kwargs(): LOAD_CONST 1 (('c',)) CALL_KW 3 POP_TOP - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (wrap_func_w_kwargs.__code__.co_firstlineno, wrap_func_w_kwargs.__code__.co_firstlineno + 1) @@ -295,7 +295,7 @@ def wrap_func_w_kwargs(): IMPORT_NAME 2 (math + eager) CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR) POP_TOP - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -322,7 +322,7 @@ def wrap_func_w_kwargs(): %3d LOAD_GLOBAL 0 (spam) POP_TOP - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -331,19 +331,19 @@ def wrap_func_w_kwargs(): %4d LOAD_GLOBAL 0 (spam) POP_TOP - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ dis_module_expected_results = """\ Disassembly of f: 4 RESUME 0 - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE Disassembly of g: 5 RESUME 0 - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -368,7 +368,7 @@ def wrap_func_w_kwargs(): LOAD_SMALL_INT 1 BINARY_OP 0 (+) STORE_NAME 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -409,7 +409,7 @@ def wrap_func_w_kwargs(): LOAD_SMALL_INT 0 CALL 1 STORE_SUBSCR - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -427,7 +427,7 @@ def foo(a: int, b: str) -> str: MAKE_FUNCTION SET_FUNCTION_ATTRIBUTE 16 (annotate) STORE_NAME 0 (foo) - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -477,14 +477,14 @@ def foo(a: int, b: str) -> str: LOAD_ATTR 2 (__traceback__) STORE_FAST 1 (tb) L7: POP_EXCEPT - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) STORE_FAST 0 (e) DELETE_FAST 0 (e) %4d LOAD_FAST 1 (tb) RETURN_VALUE - -- L8: LOAD_CONST 1 (None) + -- L8: LOAD_COMMON_CONSTANT 7 (None) STORE_FAST 0 (e) DELETE_FAST 0 (e) RERAISE 1 @@ -554,15 +554,15 @@ def _with(c): %4d LOAD_SMALL_INT 1 STORE_FAST 1 (x) -%4d L2: LOAD_CONST 1 (None) - LOAD_CONST 1 (None) - LOAD_CONST 1 (None) +%4d L2: LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) CALL 3 POP_TOP %4d LOAD_SMALL_INT 2 STORE_FAST 2 (y) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE %4d L3: PUSH_EXC_INFO @@ -579,7 +579,7 @@ def _with(c): %4d LOAD_SMALL_INT 2 STORE_FAST 2 (y) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE -- L8: COPY 3 @@ -617,7 +617,7 @@ async def _asyncwith(c): CALL 0 GET_AWAITABLE 1 PUSH_NULL - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) L2: SEND 4 (to L5) L3: YIELD_VALUE 1 L4: RESUME 3 @@ -628,13 +628,13 @@ async def _asyncwith(c): %4d LOAD_SMALL_INT 1 STORE_FAST 1 (x) -%4d L7: LOAD_CONST 0 (None) - LOAD_CONST 0 (None) - LOAD_CONST 0 (None) +%4d L7: LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) CALL 3 GET_AWAITABLE 2 PUSH_NULL - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) L8: SEND 4 (to L11) L9: YIELD_VALUE 1 L10: RESUME 3 @@ -644,7 +644,7 @@ async def _asyncwith(c): %4d LOAD_SMALL_INT 2 STORE_FAST 2 (y) - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE %4d L12: CLEANUP_THROW @@ -655,7 +655,7 @@ async def _asyncwith(c): WITH_EXCEPT_START GET_AWAITABLE 2 PUSH_NULL - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) L17: SEND 5 (to L21) L18: YIELD_VALUE 1 L19: RESUME 3 @@ -674,7 +674,7 @@ async def _asyncwith(c): %4d LOAD_SMALL_INT 2 STORE_FAST 2 (y) - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE -- L26: COPY 3 @@ -895,7 +895,7 @@ def foo(x): JUMP_BACKWARD 17 (to L2) L3: END_FOR POP_ITER - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) @@ -933,7 +933,7 @@ def loop_test(): %3d RESUME_CHECK{: <6} 0 %3d BUILD_LIST 0 - LOAD_CONST 2 ((1, 2, 3)) + LOAD_CONST 1 ((1, 2, 3)) LIST_EXTEND 1 LOAD_SMALL_INT 3 BINARY_OP 5 (*) @@ -949,7 +949,7 @@ def loop_test(): %3d L2: END_FOR POP_ITER - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (loop_test.__code__.co_firstlineno, loop_test.__code__.co_firstlineno + 1, @@ -967,7 +967,7 @@ def extended_arg_quick(): UNPACK_EX 256 POP_TOP STORE_FAST 0 (_) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """% (extended_arg_quick.__code__.co_firstlineno, extended_arg_quick.__code__.co_firstlineno + 1,) @@ -1084,7 +1084,7 @@ def test_dis_with_some_positions(self): '', '2:3-3:15 NOP', '', - '3:11-3:15 LOAD_CONST 0 (None)', + '3:11-3:15 LOAD_COMMON_CONSTANT 7 (None)', '3:11-3:15 RETURN_VALUE', '', ' -- L1: PUSH_EXC_INFO', @@ -1115,7 +1115,7 @@ def test_dis_with_linenos_but_no_columns(self): '', '2:5-2:6 LOAD_SMALL_INT 1', '2:?-2:? STORE_FAST 0 (x)', - '2:?-2:? LOAD_CONST 1 (None)', + '2:?-2:? LOAD_COMMON_CONSTANT 7 (None)', '2:?-2:? RETURN_VALUE', '', ]) @@ -1128,7 +1128,7 @@ def f(): f.__code__ = f.__code__.replace(co_linetable=b'') expect = '\n'.join([ ' RESUME 0', - ' LOAD_CONST 0 (None)', + ' LOAD_COMMON_CONSTANT 7 (None)', ' RETURN_VALUE', '', ]) @@ -1533,7 +1533,6 @@ def f(c=c): Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR Constants: 0: - 1: None Variable names: 0: a 1: b @@ -1605,7 +1604,6 @@ def f(c=c): Flags: 0x0 Constants: 0: 1 - 1: None Names: 0: x""" @@ -1640,7 +1638,6 @@ async def async_def(): Flags: OPTIMIZED, NEWLOCALS, COROUTINE Constants: 0: 1 - 1: None Names: 0: b 1: c @@ -1790,7 +1787,7 @@ def _prepare_test_cases(): make_inst(opname='MAKE_CELL', arg=0, argval='a', argrepr='a', offset=0, start_offset=0, starts_line=True, line_number=None), make_inst(opname='MAKE_CELL', arg=1, argval='b', argrepr='b', offset=2, start_offset=2, starts_line=False, line_number=None), make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=True, line_number=1, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=4, argval=(3, 4), argrepr='(3, 4)', offset=8, start_offset=8, starts_line=True, line_number=2), + make_inst(opname='LOAD_CONST', arg=3, argval=(3, 4), argrepr='(3, 4)', offset=8, start_offset=8, starts_line=True, line_number=2), make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=False, line_number=2), make_inst(opname='LOAD_FAST_BORROW', arg=1, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=False, line_number=2), make_inst(opname='BUILD_TUPLE', arg=2, argval=2, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=2), @@ -1802,11 +1799,11 @@ def _prepare_test_cases(): make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + NULL', offset=26, start_offset=26, starts_line=True, line_number=7, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), make_inst(opname='LOAD_DEREF', arg=0, argval='a', argrepr='a', offset=36, start_offset=36, starts_line=False, line_number=7), make_inst(opname='LOAD_DEREF', arg=1, argval='b', argrepr='b', offset=38, start_offset=38, starts_line=False, line_number=7), - make_inst(opname='LOAD_CONST', arg=2, argval='', argrepr="''", offset=40, start_offset=40, starts_line=False, line_number=7), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=8, argval='', argrepr="''", offset=40, start_offset=40, starts_line=False, line_number=7), make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=7), make_inst(opname='BUILD_LIST', arg=0, argval=0, argrepr='', offset=44, start_offset=44, starts_line=False, line_number=7), make_inst(opname='BUILD_MAP', arg=0, argval=0, argrepr='', offset=46, start_offset=46, starts_line=False, line_number=7), - make_inst(opname='LOAD_CONST', arg=3, argval='Hello world!', argrepr="'Hello world!'", offset=48, start_offset=48, starts_line=False, line_number=7), + make_inst(opname='LOAD_CONST', arg=2, argval='Hello world!', argrepr="'Hello world!'", offset=48, start_offset=48, starts_line=False, line_number=7), make_inst(opname='CALL', arg=7, argval=7, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=58, start_offset=58, starts_line=False, line_number=7), make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='f', argrepr='f', offset=60, start_offset=60, starts_line=True, line_number=8), @@ -1851,7 +1848,7 @@ def _prepare_test_cases(): make_inst(opname='LOAD_FAST_BORROW_LOAD_FAST_BORROW', arg=1, argval=('e', 'f'), argrepr='e, f', offset=24, start_offset=24, starts_line=False, line_number=4), make_inst(opname='CALL', arg=6, argval=6, argrepr='', offset=26, start_offset=26, starts_line=False, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=34, start_offset=34, starts_line=False, line_number=4), - make_inst(opname='LOAD_CONST', arg=0, argval=None, argrepr='None', offset=36, start_offset=36, starts_line=False, line_number=4), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=36, start_offset=36, starts_line=False, line_number=4), make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=38, start_offset=38, starts_line=False, line_number=4), ] @@ -1934,16 +1931,16 @@ def _prepare_test_cases(): make_inst(opname='LOAD_CONST', arg=3, argval='Never reach this', argrepr="'Never reach this'", offset=292, start_offset=292, starts_line=False, line_number=26), make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=294, start_offset=294, starts_line=False, line_number=26, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=302, start_offset=302, starts_line=False, line_number=26), - make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=304, start_offset=304, starts_line=True, line_number=25), - make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=306, start_offset=306, starts_line=False, line_number=25), - make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=25), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=304, start_offset=304, starts_line=True, line_number=25), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=306, start_offset=306, starts_line=False, line_number=25), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=25), make_inst(opname='CALL', arg=3, argval=3, argrepr='', offset=310, start_offset=310, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=318, start_offset=318, starts_line=False, line_number=25), make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=320, start_offset=320, starts_line=True, line_number=28, label=10, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=6, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=330, start_offset=330, starts_line=False, line_number=28), + make_inst(opname='LOAD_CONST', arg=5, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=330, start_offset=330, starts_line=False, line_number=28), make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=332, start_offset=332, starts_line=False, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=28), - make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=342, start_offset=342, starts_line=False, line_number=28), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=342, start_offset=342, starts_line=False, line_number=28), make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=28), make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=346, start_offset=346, starts_line=True, line_number=25), make_inst(opname='WITH_EXCEPT_START', arg=None, argval=None, argrepr='', offset=348, start_offset=348, starts_line=False, line_number=25), @@ -1967,7 +1964,7 @@ def _prepare_test_cases(): make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=402, start_offset=402, starts_line=False, line_number=22), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=404, start_offset=404, starts_line=False, line_number=22), make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=406, start_offset=406, starts_line=True, line_number=23, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=5, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=416, start_offset=416, starts_line=False, line_number=23), + make_inst(opname='LOAD_CONST', arg=4, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=416, start_offset=416, starts_line=False, line_number=23), make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=418, start_offset=418, starts_line=False, line_number=23, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=426, start_offset=426, starts_line=False, line_number=23), make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=428, start_offset=428, starts_line=False, line_number=23), @@ -1978,7 +1975,7 @@ def _prepare_test_cases(): make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=438, start_offset=438, starts_line=False, line_number=None), make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=440, start_offset=440, starts_line=False, line_number=None), make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=442, start_offset=442, starts_line=True, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=6, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=452, start_offset=452, starts_line=False, line_number=28), + make_inst(opname='LOAD_CONST', arg=5, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=452, start_offset=452, starts_line=False, line_number=28), make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=454, start_offset=454, starts_line=False, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=462, start_offset=462, starts_line=False, line_number=28), make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=464, start_offset=464, starts_line=False, line_number=28), @@ -1991,7 +1988,7 @@ def _prepare_test_cases(): def simple(): pass expected_opinfo_simple = [ make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=simple.__code__.co_firstlineno), - make_inst(opname='LOAD_CONST', arg=0, argval=None, argrepr='None', offset=4, start_offset=4, starts_line=False, line_number=simple.__code__.co_firstlineno), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=4, start_offset=4, starts_line=False, line_number=simple.__code__.co_firstlineno), make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=6, start_offset=6, starts_line=False, line_number=simple.__code__.co_firstlineno), ] @@ -2600,7 +2597,7 @@ def test_show_cache(self): CACHE 0 (func_version: 0) CACHE 0 POP_TOP - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE ''' for flag in ['-C', '--show-caches']: @@ -2612,7 +2609,7 @@ def test_show_offsets(self): expect = ''' 0 0 RESUME 0 - 1 4 LOAD_CONST 0 (None) + 1 4 LOAD_COMMON_CONSTANT 7 (None) 6 RETURN_VALUE ''' for flag in ['-O', '--show-offsets']: @@ -2624,7 +2621,7 @@ def test_show_positions(self): expect = ''' 0:0-1:0 RESUME 0 - 1:0-1:4 LOAD_CONST 0 (None) + 1:0-1:4 LOAD_COMMON_CONSTANT 7 (None) 1:0-1:4 RETURN_VALUE ''' for flag in ['-P', '--show-positions']: @@ -2636,7 +2633,7 @@ def test_specialized_code(self): expect = ''' 0 RESUME 0 - 1 LOAD_CONST 0 (None) + 1 LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE ''' for flag in ['-S', '--specialized']: diff --git a/Lib/test/test_email/test__header_value_parser.py b/Lib/test/test_email/test__header_value_parser.py index f3c03062572..aded44e85ee 100644 --- a/Lib/test/test_email/test__header_value_parser.py +++ b/Lib/test/test_email/test__header_value_parser.py @@ -1235,17 +1235,6 @@ def test_get_local_part_valid_and_invalid_qp_in_atom_list(self): '@example.com') self.assertEqual(local_part.local_part, r'\example\\ example') - def test_get_local_part_unicode_defect(self): - # Currently this only happens when parsing unicode, not when parsing - # stuff that was originally binary. - local_part = self._test_get_x(parser.get_local_part, - 'exámple@example.com', - 'exámple', - 'exámple', - [errors.NonASCIILocalPartDefect], - '@example.com') - self.assertEqual(local_part.local_part, 'exámple') - # get_dtext def test_get_dtext_only(self): @@ -3374,10 +3363,12 @@ def test_fold_unfoldable_element_stealing_whitespace(self): self._test(token, expected, policy=policy) def test_encoded_word_with_undecodable_bytes(self): - self._test(parser.get_address_list( - ' =?utf-8?Q?=E5=AE=A2=E6=88=B6=E6=AD=A3=E8=A6=8F=E4=BA=A4=E7?=' + self._test( + parser.get_address_list( + ' =?utf-8?Q?=E5=AE=A2=E6=88=B6=E6=AD=A3=E8=A6=8F=E4=BA=A4=E7?=' + ' ' )[0], - ' =?unknown-8bit?b?5a6i5oi25q2j6KaP5Lqk5w==?=\n', + ' =?unknown-8bit?b?5a6i5oi25q2j6KaP5Lqk5w==?= \n', ) diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py index 3c9a86f3e8c..8d912738029 100644 --- a/Lib/test/test_email/test_generator.py +++ b/Lib/test/test_email/test_generator.py @@ -1,4 +1,5 @@ import io +import re import textwrap import unittest import random @@ -295,6 +296,69 @@ def test_keep_long_encoded_newlines(self): g.flatten(msg) self.assertEqual(s.getvalue(), self.typ(expected)) + def test_non_ascii_addr_spec_raises(self): + # non-ascii is not permitted in any part of an addr-spec. If the + # programmer generated it, it's an error. (See also + # test_non_ascii_addr_spec_preserved below.) + p = self.policy.clone(utf8=False, max_line_length=20) + g = self.genclass(self.ioclass(), policy=p) + # XXX The particular part detected here isn't part of a behavioral + # spec and may change in the future. + cases = [ + ('wők@example.com', 'wők', 'local-part'), + ('wok@exàmple.com', 'exàmple.com', 'domain'), + ('wők@exàmple.com', 'wők', 'local-part'), + ( + '"Name, for display" ', + 'wők@example.com', + 'addr-spec', + ), + ( + 'Näyttönimi ', + 'wők@example.com', + 'addr-spec', + ), + ( + '"a lőng quoted string as the local part"@example.com', + 'a lőng quoted string as the local part', + 'local-part', + ), + + ] + for address, badtoken, partname in cases: + with self.subTest(address=address): + msg = EmailMessage() + msg['To'] = address + expected_error = ( + fr"(?i)(?=.*non-ascii)" + fr"(?=.*{re.escape(badtoken)})" + fr"(?=.*{partname})" + fr"(?=.*policy.*utf8)" + ) + with self.assertRaisesRegex( + email.errors.HeaderWriteError, expected_error + ): + g.flatten(msg) + + def test_local_part_quoted_string_wrapped_correctly(self): + msg = self.msgmaker(self.typ(textwrap.dedent("""\ + To: <"a long local part in a quoted string"@example.com> + Subject: test + + None + """)), policy=self.policy.clone(max_line_length=20)) + expected = textwrap.dedent("""\ + To: <"a long local part in a + quoted string"@example.com> + Subject: test + + None + """) + s = self.ioclass() + g = self.genclass(s, policy=self.policy.clone(max_line_length=30)) + g.flatten(msg) + self.assertEqual(s.getvalue(), self.typ(expected)) + def _test_boundary_detection(self, linesep): # Generate a boundary token in the same way as _make_boundary token = random.randrange(sys.maxsize) @@ -515,12 +579,12 @@ def test_cte_type_7bit_transforms_8bit_cte(self): def test_smtputf8_policy(self): msg = EmailMessage() - msg['From'] = "Páolo " + msg['From'] = "Páolo " msg['To'] = 'Dinsdale' msg['Subject'] = 'Nudge nudge, wink, wink \u1F609' msg.set_content("oh là là, know what I mean, know what I mean?") expected = textwrap.dedent("""\ - From: Páolo + From: Páolo To: Dinsdale Subject: Nudge nudge, wink, wink \u1F609 Content-Type: text/plain; charset="utf-8" @@ -555,6 +619,37 @@ def test_smtp_policy(self): g.flatten(msg) self.assertEqual(s.getvalue(), expected) + def test_non_ascii_addr_spec_preserved(self): + # A defective non-ASCII addr-spec parsed from the original + # message is left unchanged when flattening. + # (See also test_non_ascii_addr_spec_raises above.) + source = ( + 'To: jörg@example.com, "But a long name still works with refold_source" ' + ).encode() + expected = ( + b'To: j\xc3\xb6rg@example.com,\n' + b' "But a long name still works with refold_source" \n' + b'\n' + ) + msg = message_from_bytes(source, policy=policy.default) + s = io.BytesIO() + g = BytesGenerator(s, policy=policy.default) + g.flatten(msg) + self.assertEqual(s.getvalue(), expected) + + def test_idna_encoding_preserved(self): + # Nothing tries to decode a pre-encoded IDNA domain. + msg = EmailMessage() + msg["To"] = Address( + username='jörg', + domain='☕.example'.encode('idna').decode() # IDNA 2003 + ) + expected = 'To: jörg@xn--53h.example\n\n'.encode() + s = io.BytesIO() + g = BytesGenerator(s, policy=policy.default.clone(utf8=True)) + g.flatten(msg) + self.assertEqual(s.getvalue(), expected) + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py index 2aaa7d68ca3..aa918255d15 100644 --- a/Lib/test/test_email/test_headerregistry.py +++ b/Lib/test/test_email/test_headerregistry.py @@ -1543,17 +1543,19 @@ def test_quoting(self): self.assertEqual(str(a), '"Sara J." <"bad name"@example.com>') def test_il8n(self): - a = Address('Éric', 'wok', 'exàmple.com') + a = Address('Éric', 'wők', 'exàmple.com') self.assertEqual(a.display_name, 'Éric') - self.assertEqual(a.username, 'wok') + self.assertEqual(a.username, 'wők') self.assertEqual(a.domain, 'exàmple.com') - self.assertEqual(a.addr_spec, 'wok@exàmple.com') - self.assertEqual(str(a), 'Éric ') + self.assertEqual(a.addr_spec, 'wők@exàmple.com') + self.assertEqual(str(a), 'Éric ') - # XXX: there is an API design issue that needs to be solved here. - #def test_non_ascii_username_raises(self): - # with self.assertRaises(ValueError): - # Address('foo', 'wők', 'example.com') + def test_i18n_in_addr_spec(self): + a = Address(addr_spec='wők@exàmple.com') + self.assertEqual(a.username, 'wők') + self.assertEqual(a.domain, 'exàmple.com') + self.assertEqual(a.addr_spec, 'wők@exàmple.com') + self.assertEqual(str(a), 'wők@exàmple.com') def test_crlf_in_constructor_args_raises(self): cases = ( @@ -1574,10 +1576,6 @@ def test_crlf_in_constructor_args_raises(self): with self.subTest(kwargs=kwargs), self.assertRaisesRegex(ValueError, "invalid arguments"): Address(**kwargs) - def test_non_ascii_username_in_addr_spec_raises(self): - with self.assertRaises(ValueError): - Address('foo', addr_spec='wők@example.com') - def test_address_addr_spec_and_username_raises(self): with self.assertRaises(TypeError): Address('foo', username='bing', addr_spec='bar@baz') diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index ec7192b1b89..401136de8de 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -559,6 +559,75 @@ def test_self_trace_after_ctypes_import(self): f"stdout: {result.stdout}\nstderr: {result.stderr}" ) + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_remote_stack_trace_non_ascii_names(self): + # Exercise each PyUnicode kind (1-byte non-ASCII, 2-byte BMP, + # 4-byte non-BMP) for both the filename and the function name + # reported in the stack trace. + latin1 = "zażółć" # 1-byte non-ASCII (forces non-ASCII path) + bmp = "λάμβδα" # 2-byte BMP + astral = "𐌀𐌁𐌂𐌃" # 4-byte non-BMP (Old Italic; XID, no NFKC fold) + func_name = f"{latin1}_{bmp}_{astral}" + script_basename = f"mod_{latin1}_{bmp}_{astral}" + + port = find_unused_port() + script = textwrap.dedent( + f"""\ + import socket + import time + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def {func_name}(): + sock.sendall(b"ready") + time.sleep(10_000) + + {func_name}() + """ + ) + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + script_name = _make_test_script(script_dir, script_basename, script) + + server_socket = _create_server_socket(port) + client_socket = None + try: + p = subprocess.Popen([sys.executable, script_name]) + client_socket, _ = server_socket.accept() + server_socket.close() + _wait_for_signal(client_socket, b"ready") + + stack_trace = get_stack_trace(p.pid) + except PermissionError: + self.skipTest("Insufficient permissions to read the stack trace") + finally: + if client_socket is not None: + client_socket.close() + p.kill() + p.wait(timeout=SHORT_TIMEOUT) + + frames = [ + frame + for interp in stack_trace + for thread in interp.threads + for frame in thread.frame_info + ] + target = next( + (f for f in frames if f.funcname == func_name), None + ) + self.assertIsNotNone( + target, + f"Frame for {func_name!r} missing; got " + f"{[(f.filename, f.funcname) for f in frames]}", + ) + self.assertEqual(target.filename, script_name) + @skip_if_not_supported @unittest.skipIf( sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, diff --git a/Lib/test/test_frame_pointer_unwind.py b/Lib/test/test_frame_pointer_unwind.py index c70ec281686..2f9ce2bf049 100644 --- a/Lib/test/test_frame_pointer_unwind.py +++ b/Lib/test/test_frame_pointer_unwind.py @@ -27,9 +27,8 @@ def _frame_pointers_expected(machine): ) if "no-omit-frame-pointer" in cflags: - # For example, configure adds -fno-omit-frame-pointer if Python - # has perf trampoline (PY_HAVE_PERF_TRAMPOLINE) and Python is built - # in debug mode. + # For example, configure adds -fno-omit-frame-pointer by default on + # supported GCC-compatible builds. return True if "omit-frame-pointer" in cflags: return False diff --git a/Lib/test/test_gc_stats.py b/Lib/test/test_gc_stats.py new file mode 100644 index 00000000000..59365ad45b3 --- /dev/null +++ b/Lib/test/test_gc_stats.py @@ -0,0 +1,350 @@ +import gc +import os +import textwrap +import time +import unittest + +from test.support import ( + Py_GIL_DISABLED, + import_helper, + requires_gil_enabled, + requires_remote_subprocess_debugging, +) +from test.test_profiling.test_sampling_profiler.helpers import test_subprocess + +try: + import _remote_debugging # noqa: F401 +except ImportError: + raise unittest.SkipTest( + "Test only runs when _remote_debugging is available" + ) + + +GC_STATS_FIELDS = ( + "gen", "iid", "ts_start", "ts_stop", "collections", "collected", + "uncollectable", "candidates", "duration") + + +def get_interpreter_identifiers(gc_stats) -> tuple[int,...]: + return tuple(sorted({s.iid for s in gc_stats})) + + +def get_generations(gc_stats) -> tuple[int,int,int]: + generations = set() + for s in gc_stats: + generations.add(s.gen) + + return tuple(sorted(generations)) + + +def get_last_item(gc_stats, generation: int, iid: int): + item = None + for s in gc_stats: + if s.gen == generation and s.iid == iid: + if item is None or item.ts_start < s.ts_start: + item = s + + return item + + +def has_local_process_debugging(): + try: + return _remote_debugging.is_python_process(os.getpid()) + except Exception: + return False + + +def check_gc_stats_fields(testcase, stats): + testcase.assertIsInstance(stats, list) + testcase.assertGreater(len(stats), 0) + for item in stats: + testcase.assertIsInstance(item, _remote_debugging.GCStatsInfo) + testcase.assertEqual(type(item).__match_args__, GC_STATS_FIELDS) + testcase.assertEqual(len(item), len(GC_STATS_FIELDS)) + + +def gc_stats_counters_advanced(before_stats, after_stats, generations, iid): + for generation in generations: + before = get_last_item(before_stats, generation, iid) + after = get_last_item(after_stats, generation, iid) + if after is None or before is None: + return False + if after.duration <= before.duration: + return False + if after.candidates <= before.candidates: + return False + return True + + +@unittest.skipUnless( + has_local_process_debugging(), "requires local process debugging") +class TestLocalGCStats(unittest.TestCase): + + _main_iid = 0 # main interpreter ID + + def test_gc_stats_fields(self): + monitor = _remote_debugging.GCMonitor(os.getpid(), debug=True) + stats = monitor.get_gc_stats(all_interpreters=False) + check_gc_stats_fields(self, stats) + + def test_module_get_gc_stats_fields(self): + stats = _remote_debugging.get_gc_stats( + os.getpid(), all_interpreters=False) + check_gc_stats_fields(self, stats) + + def test_all_interpreters_filter_for_local_process(self): + interpreters = import_helper.import_module("concurrent.interpreters") + source = """ + import gc + objects = [] + obj = [] + obj.append(obj) + objects.append(obj) + gc.collect(0) + gc.collect(1) + gc.collect(2) + """ + interp = interpreters.create() + try: + interp.exec(textwrap.dedent(source)) + for generation in range(3): + gc.collect(generation) + + main_stats = _remote_debugging.get_gc_stats( + os.getpid(), all_interpreters=False) + all_stats = _remote_debugging.get_gc_stats( + os.getpid(), all_interpreters=True) + finally: + interp.close() + + self.assertEqual(get_interpreter_identifiers(main_stats), (0,)) + self.assertIn(0, get_interpreter_identifiers(all_stats)) + self.assertGreater(len(get_interpreter_identifiers(all_stats)), 1) + self.assertEqual(get_generations(main_stats), (0, 1, 2)) + self.assertEqual(get_generations(all_stats), (0, 1, 2)) + for iid in get_interpreter_identifiers(all_stats): + for generation in range(3): + self.assertIsNotNone(get_last_item(all_stats, generation, iid)) + + @unittest.skipUnless(Py_GIL_DISABLED, "requires free-threaded GC") + def test_gc_stats_counters_for_main_interpreter_free_threaded(self): + generations = (0, 1, 2) + before_stats = _remote_debugging.get_gc_stats( + os.getpid(), all_interpreters=False) + for generation in generations: + self.assertIsNotNone( + get_last_item(before_stats, generation, self._main_iid)) + + objects = [] + for _ in range(1000): + obj = [] + obj.append(obj) + objects.append(obj) + for generation in generations: + gc.collect(generation) + + after_stats = _remote_debugging.get_gc_stats( + os.getpid(), all_interpreters=False) + self.assertTrue( + gc_stats_counters_advanced( + before_stats, after_stats, generations, self._main_iid), + (before_stats, after_stats) + ) + + +@requires_remote_subprocess_debugging() +class TestGCStats(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls._main_iid = 0 # main interpreter ID + cls._main_interpreter_script = ''' + import gc + import time + + gc.collect(0) + gc.collect(1) + gc.collect(2) + + _test_sock.sendall(b"working") + objects = [] + while True: + if len(objects) > 100: + objects = [] + + obj = [] + obj.append(obj) + objects.append(obj) + + time.sleep(0.1) + gc.collect(0) + gc.collect(1) + gc.collect(2) + ''' + cls._script = ''' + import concurrent.interpreters as interpreters + import gc + import time + + source = """if True: + import gc + + if "objects" not in globals(): + objects = [] + if len(objects) > 100: + objects = [] + + obj = [] + obj.append(obj) + objects.append(obj) + + gc.collect(0) + gc.collect(1) + gc.collect(2) + """ + + if {0}: + interp = interpreters.create() + interp.exec(source) + + gc.collect(0) + gc.collect(1) + gc.collect(2) + + _test_sock.sendall(b"working") + objects = [] + while True: + if len(objects) > 100: + objects = [] + + obj = [] + obj.append(obj) + objects.append(obj) + + time.sleep(0.1) + if {0}: + interp.exec(source) + gc.collect(0) + gc.collect(1) + gc.collect(2) + ''' + + def _gc_stats_advanced(self, before_stats, after_stats, generations): + for generation in generations: + before = get_last_item(before_stats, generation, self._main_iid) + after = get_last_item(after_stats, generation, self._main_iid) + if after is None or before is None: + return False + if after.ts_stop <= before.ts_stop: + return False + return True + + def _collect_gc_stats(self, script: str, all_interpreters: bool, + generations=(2,)): + with (test_subprocess(script, wait_for_working=True) as subproc): + monitor = _remote_debugging.GCMonitor(subproc.process.pid, debug=True) + before_stats = monitor.get_gc_stats(all_interpreters=all_interpreters) + for generation in generations: + before = get_last_item(before_stats, generation, self._main_iid) + self.assertIsNotNone(before) + + after_stats = before_stats + for _ in range(10): + time.sleep(0.5) + after_stats = monitor.get_gc_stats(all_interpreters=all_interpreters) + if self._gc_stats_advanced(before_stats, after_stats, generations): + break + else: + self.fail( + f"GC stats for generations {generations!r} did not " + f"advance: {before_stats!r} -> {after_stats!r}" + ) + + return before_stats, after_stats + + def _check_gc_stats(self, before, after): + self.assertIsNotNone(before) + self.assertIsNotNone(after) + + self.assertGreater(after.collections, before.collections, (before, after)) + self.assertGreater(after.ts_start, before.ts_start, (before, after)) + self.assertGreater(after.ts_stop, before.ts_stop, (before, after)) + self.assertGreater(after.duration, before.duration, (before, after)) + + self.assertGreater(after.candidates, before.candidates, (before, after)) + + # may not grow + self.assertGreaterEqual(after.collected, before.collected, (before, after)) + self.assertGreaterEqual(after.uncollectable, before.uncollectable, (before, after)) + + def _check_interpreter_gc_stats(self, before_stats, after_stats): + before_iids = get_interpreter_identifiers(before_stats) + after_iids = get_interpreter_identifiers(after_stats) + + self.assertEqual(before_iids, after_iids) + + self.assertEqual(get_generations(before_stats), (0, 1, 2)) + self.assertEqual(get_generations(after_stats), (0, 1, 2)) + + for iid in after_iids: + with self.subTest(f"interpreter id={iid}"): + before_last_items = (get_last_item(before_stats, 0, iid), + get_last_item(before_stats, 1, iid), + get_last_item(before_stats, 2, iid)) + + after_last_items = (get_last_item(after_stats, 0, iid), + get_last_item(after_stats, 1, iid), + get_last_item(after_stats, 2, iid)) + + for before, after in zip(before_last_items, after_last_items): + self._check_gc_stats(before, after) + + def test_gc_stats_timestamps_for_main_interpreter(self): + script = textwrap.dedent(self._main_interpreter_script) + before_stats, after_stats = self._collect_gc_stats( + script, False, generations=(0, 1, 2)) + + for generation in range(3): + with self.subTest(generation=generation): + before = get_last_item(before_stats, generation, self._main_iid) + after = get_last_item(after_stats, generation, self._main_iid) + + self.assertIsNotNone(before) + self.assertIsNotNone(after) + self.assertGreater( + after.collections, before.collections, + (before, after)) + self.assertGreater( + after.ts_start, before.ts_start, + (before, after)) + self.assertGreater( + after.ts_stop, before.ts_stop, + (before, after)) + + @requires_gil_enabled() + def test_gc_stats_for_main_interpreter(self): + script = textwrap.dedent(self._script.format(False)) + before_stats, after_stats = self._collect_gc_stats(script, False) + + self._check_interpreter_gc_stats(before_stats, after_stats) + + @requires_gil_enabled() + def test_gc_stats_for_main_interpreter_if_subinterpreter_exists(self): + script = textwrap.dedent(self._script.format(True)) + before_stats, after_stats = self._collect_gc_stats(script, False) + + self._check_interpreter_gc_stats(before_stats, after_stats) + + @requires_gil_enabled() + def test_gc_stats_for_all_interpreters(self): + script = textwrap.dedent(self._script.format(True)) + before_stats, after_stats = self._collect_gc_stats(script, True) + + before_iids = get_interpreter_identifiers(before_stats) + after_iids = get_interpreter_identifiers(after_stats) + + self.assertGreater(len(before_iids), 1) + self.assertGreater(len(after_iids), 1) + self.assertEqual(before_iids, after_iids) + + self._check_interpreter_gc_stats(before_stats, after_stats) diff --git a/Lib/test/test_gdb/gdb_jit_sample.py b/Lib/test/test_gdb/gdb_jit_sample.py new file mode 100644 index 00000000000..b439e82e8b3 --- /dev/null +++ b/Lib/test/test_gdb/gdb_jit_sample.py @@ -0,0 +1,27 @@ +# Sample script for use by test_gdb.test_jit + +import _testinternalcapi +import operator + + +WARMUP_ITERATIONS = _testinternalcapi.TIER2_THRESHOLD + 10 + + +def jit_bt_hot(depth, warming_up_caller=False): + if depth == 0: + if not warming_up_caller: + id(42) + return + + for iteration in range(WARMUP_ITERATIONS): + operator.call( + jit_bt_hot, + depth - 1, + warming_up_caller or iteration + 1 != WARMUP_ITERATIONS, + ) + + +# Warm the shared shim once without hitting builtin_id so the real run uses +# the steady-state shim path when GDB breaks inside id(42). +jit_bt_hot(1, warming_up_caller=True) +jit_bt_hot(1) diff --git a/Lib/test/test_gdb/test_jit.py b/Lib/test/test_gdb/test_jit.py new file mode 100644 index 00000000000..ea88d7b0a1f --- /dev/null +++ b/Lib/test/test_gdb/test_jit.py @@ -0,0 +1,201 @@ +import os +import platform +import re +import sys +import unittest + +from .util import setup_module, DebuggerTests + + +JIT_SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), "gdb_jit_sample.py") +# In batch GDB, break in builtin_id() while it is running under JIT, +# then repeatedly "finish" until the selected frame is the JIT executor. +# That gives a deterministic backtrace starting with py::jit:executor. +# +# builtin_id() sits only a few helper frames above the JIT entry on this path. +# This bound is just a generous upper limit so the test fails clearly if the +# expected stack shape changes. +MAX_FINISH_STEPS = 20 +# After landing on the JIT entry frame, single-step a bounded number of +# instructions further into the blob so the backtrace is taken from JIT code +# itself rather than the immediate helper-return site. The exact number of +# steps is not significant: each step is cross-checked against the selected +# frame's symbol so the test fails loudly if stepping escapes the registered +# JIT region, instead of asserting against a misleading backtrace. +MAX_JIT_ENTRY_STEPS = 4 +EVAL_FRAME_RE = r"(_PyEval_EvalFrameDefault|_PyEval_Vector)" +JIT_EXECUTOR_FRAME = "py::jit:executor" +JIT_ENTRY_SYMBOL = "_PyJIT_Entry" +BACKTRACE_FRAME_RE = re.compile(r"^#\d+\s+.*$", re.MULTILINE) + +FINISH_TO_JIT_EXECUTOR = ( + "python exec(\"import gdb\\n" + f"target = {JIT_EXECUTOR_FRAME!r}\\n" + f"for _ in range({MAX_FINISH_STEPS}):\\n" + " frame = gdb.selected_frame()\\n" + " if frame is not None and frame.name() == target:\\n" + " break\\n" + " gdb.execute('finish')\\n" + "else:\\n" + " raise RuntimeError('did not reach %s' % target)\\n\")" +) +STEP_INSIDE_JIT_EXECUTOR = ( + "python exec(\"import gdb\\n" + f"target = {JIT_EXECUTOR_FRAME!r}\\n" + f"for _ in range({MAX_JIT_ENTRY_STEPS}):\\n" + " frame = gdb.selected_frame()\\n" + " if frame is None or frame.name() != target:\\n" + " raise RuntimeError('left JIT region during stepping: '\\n" + " + repr(frame and frame.name()))\\n" + " gdb.execute('si')\\n" + "frame = gdb.selected_frame()\\n" + "if frame is None or frame.name() != target:\\n" + " raise RuntimeError('stepped out of JIT region after si')\\n\")" +) + + +def setUpModule(): + setup_module() + + +# The GDB JIT interface registration is gated on __linux__ && __ELF__ in +# Python/jit_unwind.c, and the synthetic EH-frame is only implemented for +# x86_64 and AArch64 (a #error fires otherwise). Skip cleanly on other +# platforms or architectures instead of producing timeouts / empty backtraces. +# is_enabled() implies is_available() and also implies that the runtime has +# JIT execution active; interpreter-only tier 2 builds don't hit this path. +@unittest.skipUnless(sys.platform == "linux", + "GDB JIT interface is only implemented for Linux + ELF") +@unittest.skipUnless(platform.machine() in ("x86_64", "aarch64"), + "GDB JIT CFI emitter only supports x86_64 and AArch64") +@unittest.skipUnless(hasattr(sys, "_jit") and sys._jit.is_enabled(), + "requires a JIT-enabled build with JIT execution active") +class JitBacktraceTests(DebuggerTests): + def get_stack_trace(self, **kwargs): + # These tests validate the JIT-relevant part of the backtrace via + # _assert_jit_backtrace_shape, so an unrelated "?? ()" frame below + # the JIT/eval segment (e.g. libc without debug info) is tolerable. + kwargs.setdefault("skip_on_truncation", False) + return super().get_stack_trace(**kwargs) + + def _extract_backtrace_frames(self, gdb_output): + frames = BACKTRACE_FRAME_RE.findall(gdb_output) + self.assertGreater( + len(frames), 0, + f"expected at least one GDB backtrace frame in output:\n{gdb_output}", + ) + return frames + + def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top): + # Shape assertions applied to every JIT backtrace we produce: + # 1. The synthetic JIT symbol appears exactly once. A second + # py::jit:executor frame would mean the unwinder is + # materializing two native frames for a single logical JIT + # region, or failing to unwind out of the region entirely. + # 2. The unwinder must climb directly back out of the JIT region + # into the eval loop. _PyJIT_Entry only exists to establish the + # physical frame; the synthetic executor FDE collapses it away. + # 3. For tests that assert a specific entry PC, the JIT frame + # is also at #0. + frames = self._extract_backtrace_frames(gdb_output) + backtrace = "\n".join(frames) + + jit_frames = [frame for frame in frames if JIT_EXECUTOR_FRAME in frame] + jit_count = len(jit_frames) + self.assertEqual( + jit_count, 1, + f"expected exactly 1 {JIT_EXECUTOR_FRAME} frame, got {jit_count}\n" + f"backtrace:\n{backtrace}", + ) + eval_frames = [frame for frame in frames if re.search(EVAL_FRAME_RE, frame)] + eval_count = len(eval_frames) + self.assertGreaterEqual( + eval_count, 1, + f"expected at least one _PyEval_* frame, got {eval_count}\n" + f"backtrace:\n{backtrace}", + ) + jit_frame_index = next( + i for i, frame in enumerate(frames) if JIT_EXECUTOR_FRAME in frame + ) + frames_after_jit = frames[jit_frame_index + 1:] + first_eval_offset = next( + ( + i for i, frame in enumerate(frames_after_jit) + if re.search(EVAL_FRAME_RE, frame) + ), + None, + ) + self.assertIsNotNone( + first_eval_offset, + f"expected an eval frame after the JIT frame\n" + f"backtrace:\n{backtrace}", + ) + unexpected_between = frames_after_jit[:first_eval_offset] + self.assertFalse( + unexpected_between, + "expected the executor frame to unwind directly into eval\n" + f"backtrace:\n{backtrace}", + ) + relevant_end = max( + i + for i, frame in enumerate(frames) + if ( + JIT_EXECUTOR_FRAME in frame + or re.search(EVAL_FRAME_RE, frame) + ) + ) + truncated_frames = [ + frame for frame in frames[: relevant_end + 1] + if " ?? ()" in frame + ] + self.assertFalse( + truncated_frames, + "unexpected truncated frame before the validated JIT/eval segment\n" + f"backtrace:\n{backtrace}", + ) + if anchor_at_top: + self.assertRegex( + frames[0], + re.compile(rf"^#0\s+{re.escape(JIT_EXECUTOR_FRAME)}"), + ) + + def test_bt_unwinds_through_jit_frames(self): + gdb_output = self.get_stack_trace( + script=JIT_SAMPLE_SCRIPT, + cmds_after_breakpoint=["bt"], + PYTHON_JIT="1", + ) + # The executor should appear as a named JIT frame and unwind back into + # the eval loop. + self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=False) + + def test_bt_handoff_from_jit_entry_to_executor(self): + gdb_output = self.get_stack_trace( + script=JIT_SAMPLE_SCRIPT, + breakpoint=JIT_ENTRY_SYMBOL, + cmds_after_breakpoint=[ + "delete 1", + "tbreak builtin_id", + "continue", + "bt", + ], + PYTHON_JIT="1", + ) + # If we stop first in the shim and then continue into the real JIT + # workload, the final backtrace should match the architecture's + # executor unwind contract. + self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=False) + + def test_bt_unwinds_from_inside_jit_executor(self): + gdb_output = self.get_stack_trace( + script=JIT_SAMPLE_SCRIPT, + cmds_after_breakpoint=[ + FINISH_TO_JIT_EXECUTOR, + STEP_INSIDE_JIT_EXECUTOR, + "bt", + ], + PYTHON_JIT="1", + ) + # Once the selected PC is inside the JIT executor, we require that GDB + # identifies the JIT frame at #0 and keeps unwinding into _PyEval_*. + self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=True) diff --git a/Lib/test/test_gdb/util.py b/Lib/test/test_gdb/util.py index 8097fd52aba..d903adcf290 100644 --- a/Lib/test/test_gdb/util.py +++ b/Lib/test/test_gdb/util.py @@ -20,6 +20,27 @@ PYTHONHASHSEED = '123' +# gh-91960, bpo-40019: gdb reports these when the optimizer has dropped +# python-frame debug info; the test can't read what's not there. +_OPTIMIZED_OUT_PATTERNS = ( + '(frame information optimized out)', + 'Unable to read information on python frame', + '(unable to read python frame information)', +) +# gdb prints this when the unwinder genuinely failed to walk a frame — +# i.e. the CFI (ours or a library's) is wrong. Treat as a hard failure, +# not a skip, so regressions in our own unwind info don't hide. +_UNWIND_FAILURE_PATTERNS = ( + 'Backtrace stopped: frame did not save the PC', +) +# gh-104736: " ?? ()" in the bt usually means the unwinder bailed early, +# but can also be unrelated frames without debug info (e.g. libc). Tests +# that validate the JIT-relevant part of the backtrace themselves can +# opt out via skip_on_truncation=False. +_TRUNCATED_BACKTRACE_PATTERNS = ( + ' ?? ()', +) + def clean_environment(): # Remove PYTHON* environment variables such as PYTHONHOME @@ -160,7 +181,9 @@ def get_stack_trace(self, source=None, script=None, breakpoint=BREAKPOINT_FN, cmds_after_breakpoint=None, import_site=False, - ignore_stderr=False): + ignore_stderr=False, + skip_on_truncation=True, + **env_vars): ''' Run 'python -c SOURCE' under gdb with a breakpoint. @@ -239,7 +262,7 @@ def get_stack_trace(self, source=None, script=None, args += [script] # Use "args" to invoke gdb, capturing stdout, stderr: - out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED) + out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED, **env_vars) if not ignore_stderr: for line in err.splitlines(): @@ -255,26 +278,20 @@ def get_stack_trace(self, source=None, script=None, " because the Program Counter is" " not present") + for pattern in _UNWIND_FAILURE_PATTERNS: + if pattern in out: + raise AssertionError( + f"gdb unwinder failed ({pattern!r}) — CFI bug in our " + f"generated code or in a linked library.\n" + f"Full gdb output:\n{out}" + ) + # bpo-40019: Skip the test if gdb failed to read debug information # because the Python binary is optimized. - for pattern in ( - '(frame information optimized out)', - 'Unable to read information on python frame', - - # gh-91960: On Python built with "clang -Og", gdb gets - # "frame=" for _PyEval_EvalFrameDefault() parameter - '(unable to read python frame information)', - - # gh-104736: On Python built with "clang -Og" on ppc64le, - # "py-bt" displays a truncated or not traceback, but "where" - # logs this error message: - 'Backtrace stopped: frame did not save the PC', - - # gh-104736: When "bt" command displays something like: - # "#1 0x0000000000000000 in ?? ()", the traceback is likely - # truncated or wrong. - ' ?? ()', - ): + patterns = _OPTIMIZED_OUT_PATTERNS + if skip_on_truncation: + patterns = patterns + _TRUNCATED_BACKTRACE_PATTERNS + for pattern in patterns: if pattern in out: raise unittest.SkipTest(f"{pattern!r} found in gdb output") diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index abb071451d8..d44e61f25f7 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -106,7 +106,7 @@ def test_elim_inversion_of_is_or_in(self): self.check_lnotab(code) def test_global_as_constant(self): - # LOAD_GLOBAL None/True/False --> LOAD_CONST None/True/False + # LOAD_GLOBAL None/True/False --> LOAD_COMMON_CONSTANT None/True/False def f(): x = None x = None @@ -121,7 +121,7 @@ def h(): for func, elem in ((f, None), (g, True), (h, False)): with self.subTest(func=func): self.assertNotInBytecode(func, 'LOAD_GLOBAL') - self.assertInBytecode(func, 'LOAD_CONST', elem) + self.assertInBytecode(func, 'LOAD_COMMON_CONSTANT', elem) self.check_lnotab(func) def f(): @@ -129,7 +129,7 @@ def f(): return None self.assertNotInBytecode(f, 'LOAD_GLOBAL') - self.assertInBytecode(f, 'LOAD_CONST', None) + self.assertInBytecode(f, 'LOAD_COMMON_CONSTANT', None) self.check_lnotab(f) def test_while_one(self): @@ -146,13 +146,14 @@ def f(): def test_pack_unpack(self): for line, elem in ( - ('a, = a,', 'LOAD_CONST',), + ('a, = a,', None), ('a, b = a, b', 'SWAP',), ('a, b, c = a, b, c', 'SWAP',), ): with self.subTest(line=line): code = compile(line,'','single') - self.assertInBytecode(code, elem) + if elem is not None: + self.assertInBytecode(code, elem) self.assertNotInBytecode(code, 'BUILD_TUPLE') self.assertNotInBytecode(code, 'UNPACK_SEQUENCE') self.check_lnotab(code) @@ -174,10 +175,10 @@ def test_constant_folding_tuples_of_constants(self): # Long tuples should be folded too. code = compile(repr(tuple(range(10000))),'','single') self.assertNotInBytecode(code, 'BUILD_TUPLE') - # One LOAD_CONST for the tuple, one for the None return value + # One LOAD_CONST for the tuple; None return value uses LOAD_COMMON_CONSTANT load_consts = [instr for instr in dis.get_instructions(code) if instr.opname == 'LOAD_CONST'] - self.assertEqual(len(load_consts), 2) + self.assertEqual(len(load_consts), 1) self.check_lnotab(code) # Bug 1053819: Tuple of constants misidentified when presented with: @@ -283,11 +284,11 @@ def test_constant_folding_unaryop(self): ('-0.0', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0), ('-(1.0-1.0)', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0), ('-0.5', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.5), - ('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -1), + ('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_COMMON_CONSTANT', -1), ('---""', 'UNARY_NEGATIVE', None, False, None, None), ('~~~1', 'UNARY_INVERT', None, True, 'LOAD_CONST', -2), ('~~~""', 'UNARY_INVERT', None, False, None, None), - ('not not True', 'UNARY_NOT', None, True, 'LOAD_CONST', True), + ('not not True', 'UNARY_NOT', None, True, 'LOAD_COMMON_CONSTANT', True), ('not not x', 'UNARY_NOT', None, True, 'LOAD_NAME', 'x'), # this should be optimized regardless of constant or not ('+++1', 'CALL_INTRINSIC_1', intrinsic_positive, True, 'LOAD_SMALL_INT', 1), ('---x', 'UNARY_NEGATIVE', None, False, None, None), @@ -326,7 +327,7 @@ def test_constant_folding_binop(self): ('1 + 2', 'NB_ADD', True, 'LOAD_SMALL_INT', 3), ('1 + 2 + 3', 'NB_ADD', True, 'LOAD_SMALL_INT', 6), ('1 + ""', 'NB_ADD', False, None, None), - ('1 - 2', 'NB_SUBTRACT', True, 'LOAD_CONST', -1), + ('1 - 2', 'NB_SUBTRACT', True, 'LOAD_COMMON_CONSTANT', -1), ('1 - 2 - 3', 'NB_SUBTRACT', True, 'LOAD_CONST', -4), ('1 - ""', 'NB_SUBTRACT', False, None, None), ('2 * 2', 'NB_MULTIPLY', True, 'LOAD_SMALL_INT', 4), @@ -1539,7 +1540,7 @@ def test_optimize_literal_list_for_iter(self): end, ('END_FOR', None, 0), ('POP_ITER', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)]) @@ -1572,7 +1573,7 @@ def test_optimize_literal_list_for_iter(self): end, ('END_FOR', None, 0), ('POP_ITER', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None]) @@ -1604,7 +1605,7 @@ def test_optimize_literal_set_for_iter(self): end, ('END_FOR', None, 0), ('POP_ITER', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})]) @@ -1626,7 +1627,22 @@ def test_optimize_literal_set_for_iter(self): ('LOAD_CONST', 0, 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None]) + expected = [ + ('LOAD_SMALL_INT', 1, 0), + ('LOAD_NAME', 0, 0), + ('BUILD_SET', 2, 0), + ('GET_ITER', 0, 0), + start := self.Label(), + ('FOR_ITER', end := self.Label(), 0), + ('STORE_FAST', 0, 0), + ('JUMP', start, 0), + end, + ('END_FOR', None, 0), + ('POP_ITER', None, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), + ('RETURN_VALUE', None, 0), + ] + self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None]) def test_optimize_literal_list_contains(self): # x in [1, 2] ==> x in (1, 2) @@ -1645,7 +1661,7 @@ def test_optimize_literal_list_contains(self): ('LOAD_CONST', 1, 0), ('CONTAINS_OP', 0, 0), ('POP_TOP', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)]) @@ -1668,7 +1684,7 @@ def test_optimize_literal_list_contains(self): ('BUILD_TUPLE', 2, 0), ('CONTAINS_OP', 0, 0), ('POP_TOP', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None]) @@ -1690,7 +1706,7 @@ def test_optimize_literal_set_contains(self): ('LOAD_CONST', 1, 0), ('CONTAINS_OP', 0, 0), ('POP_TOP', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})]) @@ -1707,7 +1723,17 @@ def test_optimize_literal_set_contains(self): ('LOAD_CONST', 0, 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None]) + expected = [ + ('LOAD_NAME', 0, 0), + ('LOAD_SMALL_INT', 1, 0), + ('LOAD_NAME', 1, 0), + ('BUILD_SET', 2, 0), + ('CONTAINS_OP', 0, 0), + ('POP_TOP', None, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), + ('RETURN_VALUE', None, 0), + ] + self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None]) def test_optimize_unary_not(self): # test folding @@ -1718,10 +1744,10 @@ def test_optimize_unary_not(self): ('RETURN_VALUE', None, 0), ] after = [ - ('LOAD_CONST', 1, 0), + ('LOAD_COMMON_CONSTANT', 10, 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False]) + self.cfg_optimization_test(before, after, consts=[], expected_consts=[True]) # test cancel out before = [ @@ -1769,7 +1795,7 @@ def test_optimize_unary_not(self): ('RETURN_VALUE', None, 0), ] after = [ - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 9, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[], expected_consts=[True]) @@ -1785,10 +1811,10 @@ def test_optimize_unary_not(self): ('RETURN_VALUE', None, 0), ] after = [ - ('LOAD_CONST', 1, 0), + ('LOAD_COMMON_CONSTANT', 10, 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False]) + self.cfg_optimization_test(before, after, consts=[], expected_consts=[True]) # test cancel out & eliminate to bool (to bool stays as we are not iterating to a fixed point) before = [ @@ -2430,7 +2456,7 @@ def test_list_to_tuple_get_iter(self): end, ("END_FOR", None, 11), ("POP_TOP", None, 12), - ("LOAD_CONST", 0, 13), + ("LOAD_COMMON_CONSTANT", 7, 13), ("RETURN_VALUE", None, 14), ] self.cfg_optimization_test(insts, expected_insts, consts=[None]) @@ -2454,7 +2480,7 @@ def make_bb(self, insts): maxconst = max(maxconst, arg) consts = [None for _ in range(maxconst + 1)] return insts + [ - ("LOAD_CONST", 0, last_loc + 1), + ("LOAD_COMMON_CONSTANT", 7, last_loc + 1), ("RETURN_VALUE", None, last_loc + 2), ], consts @@ -2485,7 +2511,7 @@ def test_optimized(self): ] expected = [ ("LOAD_FAST_BORROW", 0, 1), - ("LOAD_CONST", 1, 2), + ("LOAD_COMMON_CONSTANT", 7, 2), ("SWAP", 2, 3), ("POP_TOP", None, 4), ] @@ -2523,7 +2549,13 @@ def test_unoptimized_if_support_killed(self): ("STORE_FAST", 0, 3), ("POP_TOP", None, 4), ] - self.check(insts, insts) + expected = [ + ("LOAD_FAST", 0, 1), + ("LOAD_COMMON_CONSTANT", 7, 2), + ("STORE_FAST", 0, 3), + ("POP_TOP", None, 4), + ] + self.check(insts, expected) insts = [ ("LOAD_FAST", 0, 1), @@ -2532,7 +2564,14 @@ def test_unoptimized_if_support_killed(self): ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), ("POP_TOP", None, 5), ] - self.check(insts, insts) + expected = [ + ("LOAD_FAST", 0, 1), + ("LOAD_COMMON_CONSTANT", 7, 2), + ("LOAD_COMMON_CONSTANT", 7, 3), + ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), + ("POP_TOP", None, 5), + ] + self.check(insts, expected) insts = [ ("LOAD_FAST", 0, 1), @@ -2553,7 +2592,12 @@ def test_unoptimized_if_aliased(self): ("LOAD_CONST", 0, 3), ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), ] - self.check(insts, insts) + expected = [ + ("LOAD_FAST", 0, 1), + ("LOAD_COMMON_CONSTANT", 7, 3), + ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), + ] + self.check(insts, expected) def test_consume_no_inputs(self): insts = [ @@ -2598,7 +2642,19 @@ def test_for_iter(self): ("LOAD_CONST", 0, 7), ("RETURN_VALUE", None, 8), ] - self.cfg_optimization_test(insts, insts, consts=[None]) + expected = [ + ("LOAD_FAST", 0, 1), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 2), + ("STORE_FAST", 2, 3), + ("JUMP", top, 4), + end, + ("END_FOR", None, 5), + ("POP_TOP", None, 6), + ("LOAD_COMMON_CONSTANT", 7, 7), + ("RETURN_VALUE", None, 8), + ] + self.cfg_optimization_test(insts, expected, consts=[None]) def test_load_attr(self): insts = [ @@ -2667,10 +2723,10 @@ def test_send(self): ("LOAD_FAST", 0, 1), ("LOAD_FAST_BORROW", 1, 2), ("SEND", end := self.Label(), 3), - ("LOAD_CONST", 0, 4), + ("LOAD_COMMON_CONSTANT", 7, 4), ("RETURN_VALUE", None, 5), end, - ("LOAD_CONST", 0, 6), + ("LOAD_COMMON_CONSTANT", 7, 6), ("RETURN_VALUE", None, 7) ] self.cfg_optimization_test(insts, expected, consts=[None]) @@ -2708,7 +2764,15 @@ def test_set_function_attribute(self): ("LOAD_CONST", 0, 5), ("RETURN_VALUE", None, 6) ] - self.cfg_optimization_test(insts, insts, consts=[None]) + expected = [ + ("LOAD_COMMON_CONSTANT", 7, 1), + ("LOAD_FAST", 0, 2), + ("SET_FUNCTION_ATTRIBUTE", 2, 3), + ("STORE_FAST", 1, 4), + ("LOAD_COMMON_CONSTANT", 7, 5), + ("RETURN_VALUE", None, 6) + ] + self.cfg_optimization_test(insts, expected, consts=[None]) insts = [ ("LOAD_CONST", 0, 1), @@ -2717,7 +2781,7 @@ def test_set_function_attribute(self): ("RETURN_VALUE", None, 4) ] expected = [ - ("LOAD_CONST", 0, 1), + ("LOAD_COMMON_CONSTANT", 7, 1), ("LOAD_FAST_BORROW", 0, 2), ("SET_FUNCTION_ATTRIBUTE", 2, 3), ("RETURN_VALUE", None, 4) @@ -2740,7 +2804,22 @@ def test_get_yield_from_iter(self): ("LOAD_CONST", 0, 11), ("RETURN_VALUE", None, 12), ] - self.cfg_optimization_test(insts, insts, consts=[None]) + expected = [ + ("LOAD_FAST", 0, 1), + ("GET_ITER", 1, 2), + ("PUSH_NULL", None, 3), + ("LOAD_COMMON_CONSTANT", 7, 4), + send := self.Label(), + ("SEND", end := self.Label(), 6), + ("YIELD_VALUE", 1, 7), + ("RESUME", 2, 8), + ("JUMP", send, 9), + end, + ("END_SEND", None, 10), + ("LOAD_COMMON_CONSTANT", 7, 11), + ("RETURN_VALUE", None, 12), + ] + self.cfg_optimization_test(insts, expected, consts=[None]) def test_push_exc_info(self): insts = [ diff --git a/Lib/test/test_perfmaps.py b/Lib/test/test_perfmaps.py index 647c32656ab..ee4eb50033c 100644 --- a/Lib/test/test_perfmaps.py +++ b/Lib/test/test_perfmaps.py @@ -1,4 +1,5 @@ import os +import sys import sysconfig import unittest @@ -17,6 +18,9 @@ def supports_trampoline_profiling(): raise unittest.SkipTest("perf trampoline profiling not supported") class TestPerfMapWriting(unittest.TestCase): + def tearDown(self): + perf_map_state_teardown() + def test_write_perf_map_entry(self): self.assertEqual(write_perf_map_entry(0x1234, 5678, "entry1"), 0) self.assertEqual(write_perf_map_entry(0x2345, 6789, "entry2"), 0) @@ -24,4 +28,15 @@ def test_write_perf_map_entry(self): perf_file_contents = f.read() self.assertIn("1234 162e entry1", perf_file_contents) self.assertIn("2345 1a85 entry2", perf_file_contents) - perf_map_state_teardown() + + @unittest.skipIf(sys.maxsize <= 2**32, "requires size_t wider than unsigned int") + def test_write_perf_map_entry_large_size(self): + code_addr = 0x3456 + code_size = 1 << 33 + entry_name = "entry_big" + + self.assertEqual(write_perf_map_entry(code_addr, code_size, entry_name), 0) + with open(f"/tmp/perf-{os.getpid()}.map") as f: + perf_file_contents = f.read() + self.assertIn(f"{code_addr:x} {code_size:x} {entry_name}", + perf_file_contents) diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py index 1e57b9244b4..dbd3b855f53 100644 --- a/Lib/test/test_random.py +++ b/Lib/test/test_random.py @@ -1075,6 +1075,12 @@ def test_avg_std(self): msg='%s%r' % (variate.__name__, args)) self.assertAlmostEqual(s2/(N-1), sigmasqrd, places=2, msg='%s%r' % (variate.__name__, args)) + def test_binomialvariate_log_zero(self): + # gh-149222: Variety random() return 0.0 no input Error + with unittest.mock.patch.object(random.Random, 'random', side_effect= [0.0] + [0.5] * 20): + result = random.binomialvariate(10, 0.5) + self.assertIsInstance(result, int) + self.assertIn(result, range(11)) def test_constant(self): g = random.Random() diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 9f3bc8973eb..55b9673ef6c 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -217,6 +217,25 @@ def test_invalid_names(self): # Package without __main__.py self.expect_import_error("multiprocessing") + def test_invalid_names_set_name_attribute(self): + cases = [ + # (mod_name, expected_name) -- comment indicates raise site + ("nonexistent_runpy_test_module", + "nonexistent_runpy_test_module"), # spec is None + ("sys.imp.eric", "sys.imp.eric"), # find_spec error + (".relative_name", ".relative_name"), # relative name rejected + ("sys", "sys"), # builtin: no code object + ("multiprocessing", "multiprocessing"), # package without __main__ + ] + for mod_name, expected_name in cases: + with self.subTest(mod_name=mod_name): + try: + run_module(mod_name) + except ImportError as exc: + self.assertEqual(exc.name, expected_name) + else: + self.fail("Expected ImportError for %r" % mod_name) + def test_library_module(self): self.assertEqual(run_module("runpy")["__name__"], "runpy") @@ -714,6 +733,17 @@ def test_directory_error(self): msg = "can't find '__main__' module in %r" % script_dir self._check_import_error(script_dir, msg) + def test_directory_error_sets_name_attribute(self): + with temp_dir() as script_dir: + self._make_test_script(script_dir, 'not_main') + try: + run_path(script_dir) + except ImportError as exc: + self.assertEqual(exc.name, '__main__') + else: + self.fail("Expected ImportError for directory without " + "__main__.py") + def test_zipfile(self): with temp_dir() as script_dir: mod_name = '__main__' diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 34a773ee937..3002fa528ea 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1908,7 +1908,7 @@ def test_pythontypes(self): check = self.check_sizeof # _ast.AST import _ast - check(_ast.AST(), size('P')) + check(_ast.Module(), size('3P')) try: raise TypeError except TypeError as e: diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index f2babaacc27..e270cbb22e2 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -10,6 +10,7 @@ import re import warnings import stat +import time import unittest import unittest.mock @@ -1828,6 +1829,19 @@ def test_source_directory_not_leaked(self): payload = pathlib.Path(tmpname).read_text(encoding='latin-1') assert os.path.dirname(tmpname) not in payload + def test_create_with_mtime(self): + tarfile.open(tmpname, self.mode, mtime=0).close() + with self.open(tmpname, 'r') as fobj: + fobj.read() + self.assertEqual(fobj.mtime, 0) + + def test_create_without_mtime(self): + before = int(time.time()) + tarfile.open(tmpname, self.mode).close() + after = int(time.time()) + with self.open(tmpname, 'r') as fobj: + fobj.read() + self.assertTrue(before <= fobj.mtime <= after) class Bz2StreamWriteTest(Bz2Test, StreamWriteTest): decompressor = bz2.BZ2Decompressor if bz2 else None @@ -2134,6 +2148,19 @@ def test_create_with_compresslevel(self): with tarfile.open(tmpname, 'r:gz', compresslevel=1) as tobj: pass + def test_create_with_mtime(self): + tarfile.open(tmpname, self.mode, mtime=0).close() + with self.open(tmpname, 'rb') as fobj: + fobj.read() + self.assertEqual(fobj.mtime, 0) + + def test_create_without_mtime(self): + before = int(time.time()) + tarfile.open(tmpname, self.mode).close() + after = int(time.time()) + with self.open(tmpname, 'r') as fobj: + fobj.read() + self.assertTrue(before <= fobj.mtime <= after) class Bz2CreateTest(Bz2Test, CreateTest): diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 0ca91ce0d78..3d01804513b 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2368,6 +2368,231 @@ class BarrierTests(lock_tests.BarrierTests): barriertype = staticmethod(threading.Barrier) +## Test Synchronization tools for iterators ################ + +class ThreadingIteratorToolsTests(BaseTestCase): + def test_serialize_serializes_concurrent_iteration(self): + limit = 10_000 + workers_count = 10 + result = 0 + result_lock = threading.Lock() + start = threading.Event() + + def producer(limit): + for x in range(limit): + yield x + + def consumer(iterator): + nonlocal result + start.wait() + total = 0 + for x in iterator: + total += x + with result_lock: + result += total + + iterator = threading.serialize_iterator(producer(limit)) + workers = [ + threading.Thread(target=consumer, args=(iterator,)) + for _ in range(workers_count) + ] + with threading_helper.wait_threads_exit(): + for worker in workers: + worker.start() + for worker in workers: + # Wait for the worker thread to actually start. + while worker.ident is None: + time.sleep(0.1) + start.set() + for worker in workers: + worker.join() + + self.assertEqual(result, limit * (limit - 1) // 2) + + def test_serialize_generator_methods(self): + # A generator that yields and receives + def echo(): + try: + while True: + val = yield "ready" + yield f"received {val}" + except ValueError: + yield "caught" + + it = threading.serialize_iterator(echo()) + + # Test __next__ + self.assertEqual(next(it), "ready") + + # Test send() + self.assertEqual(it.send("hello"), "received hello") + self.assertEqual(next(it), "ready") + + # Test throw() + self.assertEqual(it.throw(ValueError), "caught") + + # Test close() + it.close() + with self.assertRaises(StopIteration): + next(it) + + def test_serialize_methods_attribute_error(self): + # A standard iterator that does not have send/throw/close + # should raise AttributeError when called. + standard_it = threading.serialize_iterator([1, 2, 3]) + + with self.assertRaises(AttributeError): + standard_it.send("foo") + + with self.assertRaises(AttributeError): + standard_it.throw(ValueError) + + with self.assertRaises(AttributeError): + standard_it.close() + + def test_serialize_generator_methods_locking(self): + # Verifies that generator methods also acquire the lock. + # We can test this by checking if the lock is held during the call. + + class LockCheckingGenerator: + def __init__(self, lock): + self.lock = lock + def __iter__(self): + return self + def send(self, value): + if not self.lock.locked(): + raise RuntimeError("Lock not held during send()") + return value + def throw(self, *args): + if not self.lock.locked(): + raise RuntimeError("Lock not held during throw()") + def close(self): + if not self.lock.locked(): + raise RuntimeError("Lock not held during close()") + + # Manually create the serialize object to inspect the lock + it = threading.serialize_iterator([]) + mock_gen = LockCheckingGenerator(it._lock) + it._iterator = mock_gen + + # These should not raise RuntimeError + it.send(1) + it.throw(ValueError) + it.close() + + def test_serialize_next_exception(self): + # Verify exception pass through for calls to next() + + def f(): + raise RuntimeError + yield None + + g = threading.serialize_iterator(f()) + with self.assertRaises(RuntimeError): + next(g) + + def test_synchronized_serializes_generator_instances(self): + unique = 10 + repetitions = 5 + limit = 100 + start = threading.Event() + + @threading.synchronized_iterator + def atomic_counter(): + # The sleep widens the race window that would exist without + # synchronization between yielding a value and advancing state. + i = 0 + while True: + yield i + time.sleep(0.0005) + i += 1 + + def consumer(counter): + start.wait() + for _ in range(limit): + next(counter) + + unique_counters = [atomic_counter() for _ in range(unique)] + counters = unique_counters * repetitions + workers = [ + threading.Thread(target=consumer, args=(counter,)) + for counter in counters + ] + with threading_helper.wait_threads_exit(): + for worker in workers: + worker.start() + start.set() + for worker in workers: + worker.join() + + self.assertEqual( + {next(counter) for counter in unique_counters}, + {limit * repetitions}, + ) + + def test_synchronized_preserves_wrapped_metadata(self): + def gen(): + yield 1 + + wrapped = threading.synchronized_iterator(gen) + + self.assertEqual(wrapped.__name__, gen.__name__) + self.assertIs(wrapped.__wrapped__, gen) + self.assertEqual(list(wrapped()), [1]) + + def test_concurrent_tee_supports_concurrent_consumers(self): + limit = 5_000 + num_threads = 25 + successes = 0 + failures = [] + result_lock = threading.Lock() + start = threading.Event() + expected = list(range(limit)) + + def producer(limit): + for x in range(limit): + yield x + + def consumer(iterator): + nonlocal successes + start.wait() + items = list(iterator) + with result_lock: + if items == expected: + successes += 1 + else: + failures.append(items[:20]) + + tees = threading.concurrent_tee(producer(limit), n=num_threads) + workers = [ + threading.Thread(target=consumer, args=(iterator,)) + for iterator in tees + ] + with threading_helper.wait_threads_exit(): + for worker in workers: + worker.start() + start.set() + for worker in workers: + worker.join() + + self.assertEqual(failures, []) + self.assertEqual(successes, len(tees)) + + # Verify that locks are shared + self.assertEqual(len({id(t_obj.lock) for t_obj in tees}), 1) + + def test_concurrent_tee_zero_iterators(self): + self.assertEqual(threading.concurrent_tee(range(10), n=0), ()) + + def test_concurrent_tee_negative_n(self): + with self.assertRaises(ValueError): + threading.concurrent_tee(range(10), n=-1) + + +################# + + + class MiscTestCase(unittest.TestCase): def test__all__(self): restore_default_excepthook(self) diff --git a/Lib/test/test_type_aliases.py b/Lib/test/test_type_aliases.py index 9ceee565764..05bc6a80888 100644 --- a/Lib/test/test_type_aliases.py +++ b/Lib/test/test_type_aliases.py @@ -372,6 +372,8 @@ def test_module(self): mod_generics_cache.__name__) self.assertEqual(mod_generics_cache.OldStyle.__module__, mod_generics_cache.__name__) + Alias.__module__ = "ham.spam.eggs" + self.assertEqual(Alias.__module__, "ham.spam.eggs") def test_unpack(self): type Alias = tuple[int, int] diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 43864820688..8f3efe9fc90 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -3197,7 +3197,7 @@ def test_deeply_nested_deepcopy(self): # This should raise a RecursionError and not crash. # See https://github.com/python/cpython/issues/148801. root = cur = ET.Element('s') - for _ in range(150_000): + for _ in range(500_000): cur = ET.SubElement(cur, 'u') with support.infinite_recursion(): with self.assertRaises(RecursionError): diff --git a/Lib/threading.py b/Lib/threading.py index 4ebceae7029..abac31e2588 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -29,6 +29,7 @@ 'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError', 'setprofile', 'settrace', 'local', 'stack_size', 'excepthook', 'ExceptHookArgs', 'gettrace', 'getprofile', + 'serialize_iterator', 'synchronized_iterator', 'concurrent_tee', 'setprofile_all_threads','settrace_all_threads'] # Rename some stuff so "from threading import *" is safe @@ -842,6 +843,148 @@ class BrokenBarrierError(RuntimeError): pass +## Synchronization tools for iterators ##################### + +class serialize_iterator: + """Wrap a non-concurrent iterator with a lock to enforce sequential access. + + Applies a non-reentrant lock around calls to __next__. If the + wrapped iterator also defines send(), throw(), or close(), those + calls are serialized as well. + + Allows iterator and generator instances to be shared by multiple consumer + threads. + + For example, itertools.count does not make thread-safe instances, + but that is easily fixed with: + + atomic_counter = serialize_iterator(itertools.count()) + + """ + + __slots__ = ('_iterator', '_lock') + + def __init__(self, iterable): + self._iterator = iter(iterable) + self._lock = Lock() + + def __iter__(self): + return self + + def __next__(self): + with self._lock: + return next(self._iterator) + + def send(self, value, /): + """Send a value to a generator. + + Raises AttributeError if not a generator. + """ + with self._lock: + return self._iterator.send(value) + + def throw(self, *args): + """Call throw() on a generator. + + Raises AttributeError if not a generator. + """ + with self._lock: + return self._iterator.throw(*args) + + def close(self): + """Call close() on a generator. + + Raises AttributeError if not a generator. + """ + with self._lock: + return self._iterator.close() + + +def synchronized_iterator(func): + """Wrap an iterator-returning callable to make its iterators thread-safe. + + Existing itertools and more-itertools can be wrapped so that their + iterator instances are serialized. + + For example, itertools.count does not make thread-safe instances, + but that is easily fixed with: + + atomic_counter = synchronized_iterator(itertools.count) + + Can also be used as a decorator for generator function definitions + so that the generator instances are serialized:: + + import time + + @synchronized_iterator + def enumerate_and_timestamp(iterable): + for count, value in enumerate(iterable): + yield count, time.time_ns(), value + + """ + + from functools import wraps + + @wraps(func) + def inner(*args, **kwargs): + iterator = func(*args, **kwargs) + return serialize_iterator(iterator) + + return inner + + +def concurrent_tee(iterable, n=2): + """Variant of itertools.tee() but with guaranteed threading semantics. + + Takes a non-threadsafe iterator as an input and creates concurrent + tee objects for other threads to have reliable independent copies of + the data stream. + + The new iterators are only thread-safe if consumed within a single thread. + To share just one of the new iterators across multiple threads, wrap it + with threading.serialize_iterator(). + """ + + if n < 0: + raise ValueError("n must be a non-negative integer") + if n == 0: + return () + iterator = _concurrent_tee(iterable) + result = [iterator] + for _ in range(n - 1): + result.append(_concurrent_tee(iterator)) + return tuple(result) + + +class _concurrent_tee: + __slots__ = ('iterator', 'link', 'lock') + + def __init__(self, iterable): + if isinstance(iterable, _concurrent_tee): + self.iterator = iterable.iterator + self.link = iterable.link + self.lock = iterable.lock + else: + self.iterator = iter(iterable) + self.link = [None, None] + self.lock = Lock() + + def __iter__(self): + return self + + def __next__(self): + link = self.link + if link[1] is None: + with self.lock: + if link[1] is None: + link[0] = next(self.iterator) + link[1] = [None, None] + value, self.link = link + return value + +############################################################ + + # Helper to generate new thread names _counter = _count(1).__next__ def _newname(name_template): diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index cd5f4c71b00..c5f92a99a1e 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -246,9 +246,9 @@ def library_recipes(): result.extend([ dict( - name="OpenSSL 3.5.5", - url="https://github.com/openssl/openssl/releases/download/openssl-3.5.5/openssl-3.5.5.tar.gz", - checksum="b28c91532a8b65a1f983b4c28b7488174e4a01008e29ce8e69bd789f28bc2a89", + name="OpenSSL 3.5.6", + url="https://github.com/openssl/openssl/releases/download/openssl-3.5.6/openssl-3.5.6.tar.gz", + checksum="deae7c80cba99c4b4f940ecadb3c3338b13cb77418409238e57d7f31f2a3b736", buildrecipe=build_universal_openssl, configure=None, install=None, diff --git a/Makefile.pre.in b/Makefile.pre.in index 0edf55d991a..3c166470b6c 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -514,6 +514,7 @@ PYTHON_OBJS= \ Python/suggestions.o \ Python/perf_trampoline.o \ Python/perf_jit_trampoline.o \ + Python/jit_unwind.o \ Python/remote_debugging.o \ Python/$(DYNLOADFILE) \ $(LIBOBJS) \ @@ -3209,7 +3210,8 @@ Python/emscripten_trampoline_wasm.c: Python/emscripten_trampoline_inner.wasm $(PYTHON_FOR_REGEN) $(srcdir)/Platforms/emscripten/prepare_external_wasm.py $< $@ getWasmTrampolineModule JIT_SHIM_BUILD_OBJS= @JIT_SHIM_BUILD_O@ -JIT_BUILD_TARGETS= jit_stencils.h @JIT_STENCILS_H@ $(JIT_SHIM_BUILD_OBJS) +JIT_UNWIND_INFO_H= $(if $(JIT_OBJS),jit_unwind_info.h $(patsubst jit_stencils-%.h,jit_unwind_info-%.h,@JIT_STENCILS_H@)) +JIT_BUILD_TARGETS= jit_stencils.h @JIT_STENCILS_H@ $(JIT_UNWIND_INFO_H) $(JIT_SHIM_BUILD_OBJS) JIT_TARGETS= $(JIT_BUILD_TARGETS) $(filter-out $(JIT_SHIM_BUILD_OBJS),$(JIT_OBJS)) JIT_GENERATED_STAMP= .jit-stamp @@ -3237,6 +3239,9 @@ jit_shim-universal2-apple-darwin.o: jit_shim-aarch64-apple-darwin.o jit_shim-x86 Python/jit.o: $(srcdir)/Python/jit.c @JIT_STENCILS_H@ $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< +Python/jit_unwind.o: $(srcdir)/Python/jit_unwind.c $(JIT_UNWIND_INFO_H) + $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< + .PHONY: regen-jit regen-jit: $(JIT_TARGETS) @@ -3362,7 +3367,7 @@ clean-profile: clean-retain-profile clean-bolt # gh-141808: The JIT stencils are deliberately kept in clean-profile .PHONY: clean-jit-stencils clean-jit-stencils: - -rm -f $(JIT_TARGETS) $(JIT_GENERATED_STAMP) jit_stencils*.h jit_shim*.o + -rm -f $(JIT_TARGETS) $(JIT_GENERATED_STAMP) jit_stencils*.h jit_unwind_info*.h jit_shim*.o .PHONY: clean clean: clean-profile clean-jit-stencils @@ -3491,7 +3496,7 @@ MODULE__DECIMAL_DEPS=@LIBMPDEC_INTERNAL@ MODULE__ELEMENTTREE_DEPS=$(srcdir)/Modules/pyexpat.c @LIBEXPAT_INTERNAL@ MODULE__HASHLIB_DEPS=$(srcdir)/Modules/hashlib.h MODULE__IO_DEPS=$(srcdir)/Modules/_io/_iomodule.h -MODULE__REMOTE_DEBUGGING_DEPS=$(srcdir)/Modules/_remote_debugging/_remote_debugging.h +MODULE__REMOTE_DEBUGGING_DEPS=$(srcdir)/Modules/_remote_debugging/_remote_debugging.h $(srcdir)/Modules/_remote_debugging/gc_stats.h # HACL*-based cryptographic primitives MODULE__MD5_DEPS=$(srcdir)/Modules/hashlib.h $(LIBHACL_MD5_HEADERS) $(LIBHACL_MD5_LIB_@LIBHACL_LDEPS_LIBTYPE@) diff --git a/Misc/NEWS.d/next/Build/2026-05-01-20-01-32.gh-issue-149252.4W_0-w.rst b/Misc/NEWS.d/next/Build/2026-05-01-20-01-32.gh-issue-149252.4W_0-w.rst new file mode 100644 index 00000000000..646a8e33732 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2026-05-01-20-01-32.gh-issue-149252.4W_0-w.rst @@ -0,0 +1 @@ +Update to WASI SDK 33. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-16-12-56-08.gh-issue-116021.hMN9yw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-16-12-56-08.gh-issue-116021.hMN9yw.rst new file mode 100644 index 00000000000..967d8faaef3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-16-12-56-08.gh-issue-116021.hMN9yw.rst @@ -0,0 +1,2 @@ +Support for creating instances of abstract AST nodes from the :mod:`ast` module +is deprecated and scheduled for removal in Python 3.20. Patch by Brian Schubert. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-17-20-30-17.gh-issue-126910.NaUwmD.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-17-20-30-17.gh-issue-126910.NaUwmD.rst new file mode 100644 index 00000000000..4d2634d0dd1 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-17-20-30-17.gh-issue-126910.NaUwmD.rst @@ -0,0 +1 @@ +Add support for unwinding JIT frames using GDB. Patch by Diego Russo and Pablo Galindo. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-05-16-10-00.gh-issue-149202.W8sQeR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-05-16-10-00.gh-issue-149202.W8sQeR.rst new file mode 100644 index 00000000000..f82ca91f5ba --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-05-16-10-00.gh-issue-149202.W8sQeR.rst @@ -0,0 +1,4 @@ +Enable frame pointers by default for GCC-compatible CPython builds, including +``-mno-omit-leaf-frame-pointer`` when the compiler supports it, so profilers +and debuggers can unwind native interpreter frames more reliably. Users can pass +``--without-frame-pointers`` to opt out. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-08-02-49-07.gh-issue-148189.0KpXID.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-08-02-49-07.gh-issue-148189.0KpXID.rst new file mode 100644 index 00000000000..d90e30b3d3f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-08-02-49-07.gh-issue-148189.0KpXID.rst @@ -0,0 +1 @@ +Repaired undercount of bytes in type-specific free lists reported by sys._debugmallocstats(). For types that participate in cyclic garbage collection, it was missing two pointers used by GC. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-23-13-19.gh-issue-146527.P3Xv4Q.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-23-13-19.gh-issue-146527.P3Xv4Q.rst new file mode 100644 index 00000000000..e83b66f71da --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-23-13-19.gh-issue-146527.P3Xv4Q.rst @@ -0,0 +1,5 @@ +Add a ``GCMonitor`` class with a ``get_gc_stats`` method to the +:mod:`!_remote_debugging` module to allow reading GC statistics from an +external Python process without requiring the full ``RemoteUnwinder`` +functionality. +Patch by Sergey Miryanov and Pablo Galindo. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-19-29-29.gh-issue-148850.MSH0J_.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-19-29-29.gh-issue-148850.MSH0J_.rst new file mode 100644 index 00000000000..324d1610310 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-19-29-29.gh-issue-148850.MSH0J_.rst @@ -0,0 +1 @@ +Fix the memory sanitizer false positive in :func:`os.getrandom`. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-30-01-35-09.gh-issue-149171.meXWpl.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-30-01-35-09.gh-issue-149171.meXWpl.rst new file mode 100644 index 00000000000..f8320dd4c6b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-30-01-35-09.gh-issue-149171.meXWpl.rst @@ -0,0 +1,2 @@ +Allow assignment to the ``__module__`` attribute of +:class:`typing.TypeAliasType` instances. diff --git a/Misc/NEWS.d/next/Library/2024-07-30-19-19-33.gh-issue-81074.YAeWNf.rst b/Misc/NEWS.d/next/Library/2024-07-30-19-19-33.gh-issue-81074.YAeWNf.rst new file mode 100644 index 00000000000..87de4fade14 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-30-19-19-33.gh-issue-81074.YAeWNf.rst @@ -0,0 +1,8 @@ +The :mod:`email` module no longer treats email addresses with non-ASCII +characters as defects when parsing a Unicode string or in the ``addr_spec`` +parameter to :class:`email.headerregistry.Address`. :rfc:`5322` permits such +addresses, and they were already supported when parsing bytes and in the Address +``username`` parameter. + +The (undocumented) :exc:`!email.errors.NonASCIILocalPartDefect` is no longer +used and should be considered deprecated. diff --git a/Misc/NEWS.d/next/Library/2024-07-31-17-22-10.gh-issue-83938.TtUa-c.rst b/Misc/NEWS.d/next/Library/2024-07-31-17-22-10.gh-issue-83938.TtUa-c.rst new file mode 100644 index 00000000000..7082c72f685 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-31-17-22-10.gh-issue-83938.TtUa-c.rst @@ -0,0 +1,8 @@ +The :mod:`email` module no longer incorrectly uses :rfc:`2047` encoding for +a mailbox with non-ASCII characters in its domain. Under a policy with +:attr:`~email.policy.EmailPolicy.utf8` set ``False``, attempting to serialize +such a message will now raise an :exc:`~email.errors.HeaderWriteError`. +Either apply an appropriate IDNA encoding to convert the domain to ASCII before +serialization, or use :data:`email.policy.SMTPUTF8` (or another policy with +``utf8=True``) to correctly pass through the internationalized domain name +as Unicode characters. diff --git a/Misc/NEWS.d/next/Library/2024-07-31-17-23-06.gh-issue-122476.TtUa-c.rst b/Misc/NEWS.d/next/Library/2024-07-31-17-23-06.gh-issue-122476.TtUa-c.rst new file mode 100644 index 00000000000..29c076d3a74 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-31-17-23-06.gh-issue-122476.TtUa-c.rst @@ -0,0 +1,7 @@ +The :mod:`email` module no longer incorrectly uses :rfc:`2047` encoding for +a mailbox with non-ASCII characters in its local-part. Under a policy with +:attr:`~email.policy.EmailPolicy.utf8` set ``False``, attempting to serialize +such a message will now raise an :exc:`~email.errors.HeaderWriteError`. +There is no valid 7-bit encoding for an internationalized local-part. Use +:data:`email.policy.SMTPUTF8` (or another policy with ``utf8=True``) to +correctly pass through the local-part as Unicode characters. diff --git a/Misc/NEWS.d/next/Library/2025-08-24-15-09-30.gh-issue-75707.GOWZrC.rst b/Misc/NEWS.d/next/Library/2025-08-24-15-09-30.gh-issue-75707.GOWZrC.rst new file mode 100644 index 00000000000..b2ff8a0cdf6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-24-15-09-30.gh-issue-75707.GOWZrC.rst @@ -0,0 +1 @@ +Add optional ``mtime`` argument to :func:`tarfile.open`, for setting the ``mtime`` header field in ``.tar.gz`` archives. diff --git a/Misc/NEWS.d/next/Library/2025-12-06-08-48-26.gh-issue-141449.hQvNW_.rst b/Misc/NEWS.d/next/Library/2025-12-06-08-48-26.gh-issue-141449.hQvNW_.rst new file mode 100644 index 00000000000..4e94c3c80d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-06-08-48-26.gh-issue-141449.hQvNW_.rst @@ -0,0 +1,2 @@ +Improve tests and documentation for non-function callables as +:term:`annotate functions `. diff --git a/Misc/NEWS.d/next/Library/2026-04-22-20-49-49.gh-issue-124397.plMglV.rst b/Misc/NEWS.d/next/Library/2026-04-22-20-49-49.gh-issue-124397.plMglV.rst new file mode 100644 index 00000000000..431448a484b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-22-20-49-49.gh-issue-124397.plMglV.rst @@ -0,0 +1,3 @@ +The threading module added tooling to support concurrent iterator access: +:class:`threading.serialize_iterator`, :func:`threading.synchronized_iterator`, +and :func:`threading.concurrent_tee`. diff --git a/Misc/NEWS.d/next/Library/2026-04-27-17-12-11.gh-issue-148914.i5C3kW.rst b/Misc/NEWS.d/next/Library/2026-04-27-17-12-11.gh-issue-148914.i5C3kW.rst new file mode 100644 index 00000000000..8348aad0d89 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-27-17-12-11.gh-issue-148914.i5C3kW.rst @@ -0,0 +1,6 @@ +Fix memoization of in-band :class:`~pickle.PickleBuffer` in the Python +implementation of :mod:`pickle`. Previously, identical +:class:`!PickleBuffer`\ s did not preserve identity, and empty writable +:class:`!PickleBuffer` memoized an empty bytearray object in place of +``b''``, so the following references to ``b''`` were unpickled as an empty +bytearray object. diff --git a/Misc/NEWS.d/next/Library/2026-04-29-16-11-27.gh-issue-149117.yEeTYd.rst b/Misc/NEWS.d/next/Library/2026-04-29-16-11-27.gh-issue-149117.yEeTYd.rst new file mode 100644 index 00000000000..41223e90ed0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-29-16-11-27.gh-issue-149117.yEeTYd.rst @@ -0,0 +1,3 @@ +Fix :func:`runpy.run_module` and :func:`runpy.run_path` to set the +:attr:`~ImportError.name` attribute on the :exc:`ImportError` they +raise. diff --git a/Misc/NEWS.d/next/Library/2026-05-01-10-20-27.gh-issue-149214.btP546.rst b/Misc/NEWS.d/next/Library/2026-05-01-10-20-27.gh-issue-149214.btP546.rst new file mode 100644 index 00000000000..cbb05620626 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-01-10-20-27.gh-issue-149214.btP546.rst @@ -0,0 +1,4 @@ +Fix :mod:`!_remote_debugging` misreading non-ASCII Unicode strings (Latin-1, +BMP and non-BMP) from a remote process. Filenames and function names that +contain non-ASCII characters are now reported correctly in stack traces, the +sampling profiler, and :mod:`asyncio` task introspection. diff --git a/Misc/NEWS.d/next/Library/2026-05-02-01-09-29.gh-issue-149221.__KOks.rst b/Misc/NEWS.d/next/Library/2026-05-02-01-09-29.gh-issue-149221.__KOks.rst new file mode 100644 index 00000000000..fab2b0f6a23 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-02-01-09-29.gh-issue-149221.__KOks.rst @@ -0,0 +1 @@ +Catch rare math domain error for :func:`random.binomialvariate`. diff --git a/Misc/NEWS.d/next/Security/2026-05-02-15-38-03.gh-issue-149254.0HOL0j.rst b/Misc/NEWS.d/next/Security/2026-05-02-15-38-03.gh-issue-149254.0HOL0j.rst new file mode 100644 index 00000000000..f3cf924db25 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-05-02-15-38-03.gh-issue-149254.0HOL0j.rst @@ -0,0 +1 @@ +Update Android and iOS installer to use OpenSSL 3.5.6. diff --git a/Misc/NEWS.d/next/macOS/2026-05-01-20-12-33.gh-issue-149254.kXdWpS.rst b/Misc/NEWS.d/next/macOS/2026-05-01-20-12-33.gh-issue-149254.kXdWpS.rst new file mode 100644 index 00000000000..278327c91f1 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2026-05-01-20-12-33.gh-issue-149254.kXdWpS.rst @@ -0,0 +1 @@ +Update macOS installer to use OpenSSL 3.5.6. diff --git a/Modules/Setup b/Modules/Setup index 7d816ead843..33737c21cb4 100644 --- a/Modules/Setup +++ b/Modules/Setup @@ -285,7 +285,7 @@ PYTHONPATH=$(COREPYTHONPATH) #*shared* #_ctypes_test _ctypes/_ctypes_test.c -#_remote_debugging _remote_debugging/module.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/threads.c _remote_debugging/asyncio.c +#_remote_debugging _remote_debugging/module.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/interpreters.c #_testcapi _testcapimodule.c #_testimportmultiple _testimportmultiple.c #_testmultiphase _testmultiphase.c diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 0d520684c79..a274c312d99 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -41,7 +41,7 @@ @MODULE__PICKLE_TRUE@_pickle _pickle.c @MODULE__QUEUE_TRUE@_queue _queuemodule.c @MODULE__RANDOM_TRUE@_random _randommodule.c -@MODULE__REMOTE_DEBUGGING_TRUE@_remote_debugging _remote_debugging/module.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/frame_cache.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/binary_io_writer.c _remote_debugging/binary_io_reader.c _remote_debugging/subprocess.c +@MODULE__REMOTE_DEBUGGING_TRUE@_remote_debugging _remote_debugging/module.c _remote_debugging/gc_stats.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/frame_cache.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/binary_io_writer.c _remote_debugging/binary_io_reader.c _remote_debugging/subprocess.c _remote_debugging/interpreters.c @MODULE__STRUCT_TRUE@_struct _struct.c # build supports subinterpreters diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 0bdc30a0cb3..5325321efe7 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2225,6 +2225,7 @@ c_void_p_from_param_impl(PyObject *type, PyTypeObject *cls, PyObject *value) static int set_stginfo_ffi_type_pointer(StgInfo *stginfo, struct fielddesc *fmt) { +#if defined(_Py_FFI_SUPPORT_C_COMPLEX) if (!fmt->pffi_type->elements) { stginfo->ffi_type_pointer = *fmt->pffi_type; } @@ -2244,6 +2245,10 @@ set_stginfo_ffi_type_pointer(StgInfo *stginfo, struct fielddesc *fmt) memcpy(stginfo->ffi_type_pointer.elements, fmt->pffi_type->elements, els_size); } +#else + assert(!fmt->pffi_type->elements); + stginfo->ffi_type_pointer = *fmt->pffi_type; +#endif return 0; } diff --git a/Modules/_remote_debugging/_remote_debugging.h b/Modules/_remote_debugging/_remote_debugging.h index 07738d45e42..7369cd1514c 100644 --- a/Modules/_remote_debugging/_remote_debugging.h +++ b/Modules/_remote_debugging/_remote_debugging.h @@ -260,8 +260,10 @@ typedef struct { PyTypeObject *ThreadInfo_Type; PyTypeObject *InterpreterInfo_Type; PyTypeObject *AwaitedInfo_Type; + PyTypeObject *GCStatsInfo_Type; PyTypeObject *BinaryWriter_Type; PyTypeObject *BinaryReader_Type; + PyTypeObject *GCMonitor_Type; } RemoteDebuggingState; enum _ThreadState { @@ -346,6 +348,13 @@ typedef struct { size_t count; } StackChunkList; +typedef struct { + proc_handle_t handle; + uintptr_t runtime_start_address; + struct _Py_DebugOffsets debug_offsets; + int debug; +} RuntimeOffsets; + /* * Context for frame chain traversal operations. */ @@ -376,6 +385,13 @@ typedef struct { int32_t tlbc_index; // Thread-local bytecode index (free-threading) } CodeObjectContext; +typedef struct { + PyObject_HEAD + RuntimeOffsets offsets; +} GCMonitorObject; + +#define GCMonitor_CAST(op) ((GCMonitorObject *)(op)) + /* Function pointer types for iteration callbacks */ typedef int (*thread_processor_func)( RemoteUnwinderObject *unwinder, @@ -390,6 +406,14 @@ typedef int (*set_entry_processor_func)( void *context ); +typedef int (*interpreter_processor_func)( + RuntimeOffsets *offsets, + uintptr_t interpreter_state_addr, + int64_t iid, + void *context +); + + /* ============================================================================ * STRUCTSEQ DESCRIPTORS (extern declarations) * ============================================================================ */ @@ -401,6 +425,7 @@ extern PyStructSequence_Desc CoroInfo_desc; extern PyStructSequence_Desc ThreadInfo_desc; extern PyStructSequence_Desc InterpreterInfo_desc; extern PyStructSequence_Desc AwaitedInfo_desc; +extern PyStructSequence_Desc GCStatsInfo_desc; /* ============================================================================ * UTILITY FUNCTION DECLARATIONS @@ -588,6 +613,17 @@ extern void _Py_RemoteDebug_InitThreadsState(RemoteUnwinderObject *unwinder, _Py extern int _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st); extern void _Py_RemoteDebug_ResumeAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st); +/* ============================================================================ + * INTERPRETER FUNCTION DECLARATIONS + * ============================================================================ */ + +extern int +iterate_interpreters( + RuntimeOffsets *offsets, + interpreter_processor_func processor, + void *context +); + /* ============================================================================ * ASYNCIO FUNCTION DECLARATIONS * ============================================================================ */ diff --git a/Modules/_remote_debugging/clinic/module.c.h b/Modules/_remote_debugging/clinic/module.c.h index 15df48fabb5..179a7b97dd4 100644 --- a/Modules/_remote_debugging/clinic/module.c.h +++ b/Modules/_remote_debugging/clinic/module.c.h @@ -495,6 +495,181 @@ _remote_debugging_RemoteUnwinder_resume_threads(PyObject *self, PyObject *Py_UNU return return_value; } +PyDoc_STRVAR(_remote_debugging_GCMonitor___init____doc__, +"GCMonitor(pid, *, debug=False)\n" +"--\n" +"\n" +"Initialize a new GCMonitor object for monitoring GC events from remote process.\n" +"\n" +"Args:\n" +" pid: Process ID of the target Python process to monitor\n" +" debug: If True, chain exceptions to explain the sequence of events that\n" +" lead to the exception.\n" +"\n" +"The GCMonitor provides functionality to read GC statistics from a running\n" +"Python process.\n" +"\n" +"Raises:\n" +" PermissionError: If access to the target process is denied\n" +" OSError: If unable to attach to the target process or access its memory\n" +" RuntimeError: If unable to read debug information from the target process"); + +static int +_remote_debugging_GCMonitor___init___impl(GCMonitorObject *self, int pid, + int debug); + +static int +_remote_debugging_GCMonitor___init__(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(pid), &_Py_ID(debug), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"pid", "debug", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "GCMonitor", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject * const *fastargs; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 1; + int pid; + int debug = 0; + + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!fastargs) { + goto exit; + } + pid = PyLong_AsInt(fastargs[0]); + if (pid == -1 && PyErr_Occurred()) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + debug = PyObject_IsTrue(fastargs[1]); + if (debug < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = _remote_debugging_GCMonitor___init___impl((GCMonitorObject *)self, pid, debug); + +exit: + return return_value; +} + +PyDoc_STRVAR(_remote_debugging_GCMonitor_get_gc_stats__doc__, +"get_gc_stats($self, /, all_interpreters=False)\n" +"--\n" +"\n" +"Get garbage collector statistics from external Python process.\n" +"\n" +" all_interpreters\n" +" If True, return GC statistics from all interpreters.\n" +" If False, return only from main interpreter.\n" +"\n" +"Returns a list of GCStatsInfo objects with GC statistics data.\n" +"\n" +"Returns:\n" +" list of GCStatsInfo: A list of stats samples containing:\n" +" - gen: GC generation number.\n" +" - iid: Interpreter ID.\n" +" - ts_start: Raw timestamp at collection start.\n" +" - ts_stop: Raw timestamp at collection stop.\n" +" - collections: Total number of collections.\n" +" - collected: Total number of collected objects.\n" +" - uncollectable: Total number of uncollectable objects.\n" +" - candidates: Total objects considered and traversed.\n" +" - duration: Total collection time, in seconds.\n" +"\n" +"Raises:\n" +" RuntimeError: If the target process cannot be inspected or if its\n" +" debug offsets or GC stats layout are incompatible."); + +#define _REMOTE_DEBUGGING_GCMONITOR_GET_GC_STATS_METHODDEF \ + {"get_gc_stats", _PyCFunction_CAST(_remote_debugging_GCMonitor_get_gc_stats), METH_FASTCALL|METH_KEYWORDS, _remote_debugging_GCMonitor_get_gc_stats__doc__}, + +static PyObject * +_remote_debugging_GCMonitor_get_gc_stats_impl(GCMonitorObject *self, + int all_interpreters); + +static PyObject * +_remote_debugging_GCMonitor_get_gc_stats(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(all_interpreters), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"all_interpreters", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "get_gc_stats", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int all_interpreters = 0; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + all_interpreters = PyObject_IsTrue(args[0]); + if (all_interpreters < 0) { + goto exit; + } +skip_optional_pos: + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _remote_debugging_GCMonitor_get_gc_stats_impl((GCMonitorObject *)self, all_interpreters); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + PyDoc_STRVAR(_remote_debugging_BinaryWriter___init____doc__, "BinaryWriter(filename, sample_interval_us, start_time_us, *,\n" " compression=0)\n" @@ -1296,4 +1471,96 @@ _remote_debugging_is_python_process(PyObject *module, PyObject *const *args, Py_ exit: return return_value; } -/*[clinic end generated code: output=34f50b18f317b9b6 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_remote_debugging_get_gc_stats__doc__, +"get_gc_stats($module, /, pid, *, all_interpreters=False)\n" +"--\n" +"\n" +"Get garbage collector statistics from external Python process.\n" +"\n" +" all_interpreters\n" +" If True, return GC statistics from all interpreters.\n" +" If False, return only from main interpreter.\n" +"\n" +"Returns:\n" +" list of GCStatsInfo: A list of stats samples containing:\n" +" - gen: GC generation number.\n" +" - iid: Interpreter ID.\n" +" - ts_start: Raw timestamp at collection start.\n" +" - ts_stop: Raw timestamp at collection stop.\n" +" - collections: Total number of collections.\n" +" - collected: Total number of collected objects.\n" +" - uncollectable: Total number of uncollectable objects.\n" +" - candidates: Total objects considered and traversed.\n" +" - duration: Total collection time, in seconds.\n" +"\n" +"Raises:\n" +" RuntimeError: If the target process cannot be inspected or if its\n" +" debug offsets or GC stats layout are incompatible."); + +#define _REMOTE_DEBUGGING_GET_GC_STATS_METHODDEF \ + {"get_gc_stats", _PyCFunction_CAST(_remote_debugging_get_gc_stats), METH_FASTCALL|METH_KEYWORDS, _remote_debugging_get_gc_stats__doc__}, + +static PyObject * +_remote_debugging_get_gc_stats_impl(PyObject *module, int pid, + int all_interpreters); + +static PyObject * +_remote_debugging_get_gc_stats(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(pid), &_Py_ID(all_interpreters), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"pid", "all_interpreters", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "get_gc_stats", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + int pid; + int all_interpreters = 0; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + pid = PyLong_AsInt(args[0]); + if (pid == -1 && PyErr_Occurred()) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + all_interpreters = PyObject_IsTrue(args[1]); + if (all_interpreters < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = _remote_debugging_get_gc_stats_impl(module, pid, all_interpreters); + +exit: + return return_value; +} +/*[clinic end generated code: output=1151e58683dab9f4 input=a9049054013a1b77]*/ diff --git a/Modules/_remote_debugging/debug_offsets_validation.h b/Modules/_remote_debugging/debug_offsets_validation.h index 32800e767b3..15070263061 100644 --- a/Modules/_remote_debugging/debug_offsets_validation.h +++ b/Modules/_remote_debugging/debug_offsets_validation.h @@ -31,7 +31,7 @@ #define FIELD_SIZE(type, member) sizeof(((type *)0)->member) enum { - PY_REMOTE_DEBUG_OFFSETS_TOTAL_SIZE = 840, + PY_REMOTE_DEBUG_OFFSETS_TOTAL_SIZE = 848, PY_REMOTE_ASYNC_DEBUG_OFFSETS_TOTAL_SIZE = 104, }; @@ -304,7 +304,9 @@ validate_fixed_field( #define PY_REMOTE_DEBUG_UNICODE_OBJECT_FIELDS(APPLY, buffer_size) \ APPLY(unicode_object, length, sizeof(Py_ssize_t), _Alignof(Py_ssize_t), buffer_size); \ - APPLY(unicode_object, asciiobject_size, sizeof(char), _Alignof(char), buffer_size) + APPLY(unicode_object, state, sizeof(struct _PyUnicodeObject_state), _Alignof(struct _PyUnicodeObject_state), buffer_size); \ + APPLY(unicode_object, asciiobject_size, sizeof(char), _Alignof(char), buffer_size); \ + APPLY(unicode_object, compactunicodeobject_size, sizeof(char), _Alignof(char), buffer_size) #define PY_REMOTE_DEBUG_GEN_OBJECT_FIELDS(APPLY, buffer_size) \ APPLY(gen_object, gi_frame_state, sizeof(int8_t), _Alignof(int8_t), buffer_size); \ @@ -370,6 +372,12 @@ _PyRemoteDebug_ValidateDebugOffsetsLayout(struct _Py_DebugOffsets *debug_offsets sizeof(uintptr_t), _Alignof(uintptr_t), SIZEOF_GC_RUNTIME_STATE); + PY_REMOTE_DEBUG_VALIDATE_FIELD( + gc, + generation_stats, + sizeof(uintptr_t), + _Alignof(uintptr_t), + SIZEOF_GC_RUNTIME_STATE); PY_REMOTE_DEBUG_VALIDATE_NESTED_FIELD( interpreter_state, gc, @@ -378,6 +386,14 @@ _PyRemoteDebug_ValidateDebugOffsetsLayout(struct _Py_DebugOffsets *debug_offsets sizeof(uintptr_t), _Alignof(uintptr_t), INTERP_STATE_BUFFER_SIZE); + PY_REMOTE_DEBUG_VALIDATE_NESTED_FIELD( + interpreter_state, + gc, + gc, + generation_stats, + sizeof(uintptr_t), + _Alignof(uintptr_t), + INTERP_STATE_BUFFER_SIZE); PY_REMOTE_DEBUG_VALIDATE_SECTION(interpreter_frame); PY_REMOTE_DEBUG_INTERPRETER_FRAME_FIELDS( diff --git a/Modules/_remote_debugging/gc_stats.c b/Modules/_remote_debugging/gc_stats.c new file mode 100644 index 00000000000..852dc866153 --- /dev/null +++ b/Modules/_remote_debugging/gc_stats.c @@ -0,0 +1,153 @@ +/****************************************************************************** + * Remote Debugging Module - GC Stats Functions + * + * This file contains functions for reading GC stats from interpreter state. + ******************************************************************************/ + +#include "gc_stats.h" + +typedef struct { + PyObject *result; + PyTypeObject *gc_stats_info_type; + bool all_interpreters; +} GetGCStatsContext; + +static int +read_gc_stats(struct gc_stats *stats, int64_t iid, PyObject *result, + PyTypeObject *gc_stats_info_type) +{ +#define SET_FIELD(converter, expr) do { \ + PyObject *value = converter(expr); \ + if (value == NULL) { \ + goto error; \ + } \ + PyStructSequence_SetItem(item, field++, value); \ +} while (0) + + PyObject *item = NULL; + + for (unsigned long gen = 0; gen < NUM_GENERATIONS; gen++) { + struct gc_generation_stats *items; + int size; + if (gen == 0) { + items = (struct gc_generation_stats *)stats->young.items; + size = GC_YOUNG_STATS_SIZE; + } + else { + items = (struct gc_generation_stats *)stats->old[gen-1].items; + size = GC_OLD_STATS_SIZE; + } + for (int i = 0; i < size; i++, items++) { + item = PyStructSequence_New(gc_stats_info_type); + if (item == NULL) { + goto error; + } + Py_ssize_t field = 0; + + SET_FIELD(PyLong_FromUnsignedLong, gen); + SET_FIELD(PyLong_FromInt64, iid); + + SET_FIELD(PyLong_FromInt64, items->ts_start); + SET_FIELD(PyLong_FromInt64, items->ts_stop); + SET_FIELD(PyLong_FromSsize_t, items->collections); + SET_FIELD(PyLong_FromSsize_t, items->collected); + SET_FIELD(PyLong_FromSsize_t, items->uncollectable); + SET_FIELD(PyLong_FromSsize_t, items->candidates); + + SET_FIELD(PyFloat_FromDouble, items->duration); + + int rc = PyList_Append(result, item); + Py_CLEAR(item); + if (rc < 0) { + goto error; + } + } + } + +#undef SET_FIELD + + return 0; + +error: + Py_XDECREF(item); + + return -1; +} + +static int +get_gc_stats_from_interpreter_state(RuntimeOffsets *offsets, + uintptr_t interpreter_state_addr, + int64_t iid, + void *context) +{ + GetGCStatsContext *ctx = (GetGCStatsContext *)context; + if (!ctx->all_interpreters && iid > 0) { + return 0; + } + + uintptr_t gc_stats_addr = 0; + uintptr_t gc_stats_pointer_address = interpreter_state_addr + + offsets->debug_offsets.interpreter_state.gc + + offsets->debug_offsets.gc.generation_stats; + if (_Py_RemoteDebug_ReadRemoteMemory(&offsets->handle, + gc_stats_pointer_address, + sizeof(gc_stats_addr), + &gc_stats_addr) < 0) { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to read GC state address"); + return -1; + } + if (gc_stats_addr == 0) { + PyErr_SetString(PyExc_RuntimeError, "GC state address is NULL"); + return -1; + } + + struct gc_stats stats; + if (_Py_RemoteDebug_ReadRemoteMemory(&offsets->handle, + gc_stats_addr, + sizeof(stats), + &stats) < 0) { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to read GC state"); + return -1; + } + + if (read_gc_stats(&stats, iid, ctx->result, + ctx->gc_stats_info_type) < 0) { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to populate GC stats result"); + return -1; + } + + return 0; +} + +PyObject * +get_gc_stats(RuntimeOffsets *offsets, bool all_interpreters, + PyTypeObject *gc_stats_info_type) +{ + uint64_t gc_stats_size = offsets->debug_offsets.gc.generation_stats_size; + if (gc_stats_size != sizeof(struct gc_stats)) { + PyErr_Format(PyExc_RuntimeError, + "Remote gc_stats size (%llu) does not match " + "local size (%zu)", + (unsigned long long)gc_stats_size, + sizeof(struct gc_stats)); + set_exception_cause(offsets, PyExc_RuntimeError, "Remote gc_stats size mismatch"); + return NULL; + } + + PyObject *result = PyList_New(0); + if (result == NULL) { + return NULL; + } + GetGCStatsContext ctx = { + .result = result, + .gc_stats_info_type = gc_stats_info_type, + .all_interpreters = all_interpreters, + }; + if (iterate_interpreters(offsets, get_gc_stats_from_interpreter_state, + &ctx) < 0) { + Py_CLEAR(result); + return NULL; + } + + return result; +} diff --git a/Modules/_remote_debugging/gc_stats.h b/Modules/_remote_debugging/gc_stats.h new file mode 100644 index 00000000000..959a49c59dc --- /dev/null +++ b/Modules/_remote_debugging/gc_stats.h @@ -0,0 +1,24 @@ +/****************************************************************************** + * Remote Debugging Module - GC Stats Functions + * + * This file contains declarations for reading GC stats from interpreter state. + ******************************************************************************/ + +#ifndef Py_REMOTE_DEBUGGING_GC_STATS_H +#define Py_REMOTE_DEBUGGING_GC_STATS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "_remote_debugging.h" + +PyObject * +get_gc_stats(RuntimeOffsets *offsets, bool all_interpreters, + PyTypeObject *gc_stats_info_type); + +#ifdef __cplusplus +} +#endif + +#endif /* Py_REMOTE_DEBUGGING_GC_STATS_H */ diff --git a/Modules/_remote_debugging/interpreters.c b/Modules/_remote_debugging/interpreters.c new file mode 100644 index 00000000000..55ebcb1d781 --- /dev/null +++ b/Modules/_remote_debugging/interpreters.c @@ -0,0 +1,66 @@ +/****************************************************************************** + * Remote Debugging Module - Interpreters Functions + * + * This file contains function for iterating interpreters. + ******************************************************************************/ + +#include "_remote_debugging.h" + +int +iterate_interpreters( + RuntimeOffsets *offsets, + interpreter_processor_func processor, + void *context +) { + uintptr_t interpreters_head_addr = + offsets->runtime_start_address + + (uintptr_t)offsets->debug_offsets.runtime_state.interpreters_head; + uintptr_t interpreter_id_offset = + (uintptr_t)offsets->debug_offsets.interpreter_state.id; + uintptr_t interpreter_next_offset = + (uintptr_t)offsets->debug_offsets.interpreter_state.next; + + uintptr_t interpreter_state_addr; + if (_Py_RemoteDebug_ReadRemoteMemory(&offsets->handle, + interpreters_head_addr, + sizeof(void*), + &interpreter_state_addr) < 0) { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to read interpreter state address"); + return -1; + } + + if (interpreter_state_addr == 0) { + PyErr_SetString(PyExc_RuntimeError, "No interpreter state found"); + return -1; + } + + int64_t iid = 0; + static_assert( + sizeof((((PyInterpreterState*)NULL)->id)) == sizeof(iid), + "Sizeof of PyInterpreterState.id mismatch with local iid value"); + while (interpreter_state_addr != 0) { + if (_Py_RemoteDebug_ReadRemoteMemory( + &offsets->handle, + interpreter_state_addr + interpreter_id_offset, + sizeof(iid), + &iid) < 0) { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to read interpreter id"); + return -1; + } + + if (processor(offsets, interpreter_state_addr, iid, context) < 0) { + return -1; + } + + if (_Py_RemoteDebug_ReadRemoteMemory( + &offsets->handle, + interpreter_state_addr + interpreter_next_offset, + sizeof(void*), + &interpreter_state_addr) < 0) { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to read next interpreter state"); + return -1; + } + } + + return 0; +} diff --git a/Modules/_remote_debugging/module.c b/Modules/_remote_debugging/module.c index 32f2cbacf21..c840c59971c 100644 --- a/Modules/_remote_debugging/module.c +++ b/Modules/_remote_debugging/module.c @@ -8,6 +8,7 @@ #include "_remote_debugging.h" #include "binary_io.h" #include "debug_offsets_validation.h" +#include "gc_stats.h" /* Forward declarations for clinic-generated code */ typedef struct { @@ -132,6 +133,27 @@ PyStructSequence_Desc AwaitedInfo_desc = { 2 }; +// GCStatsInfo structseq type +static PyStructSequence_Field GCStatsInfo_fields[] = { + {"gen", "GC generation number"}, + {"iid", "Interpreter ID"}, + {"ts_start", "Raw timestamp at collection start"}, + {"ts_stop", "Raw timestamp at collection stop"}, + {"collections", "Total number of collections"}, + {"collected", "Total number of collected objects"}, + {"uncollectable", "Total number of uncollectable objects"}, + {"candidates", "Total objects considered and traversed"}, + {"duration", "Total collection time, in seconds"}, + {NULL} +}; + +PyStructSequence_Desc GCStatsInfo_desc = { + "_remote_debugging.GCStatsInfo", + "Information about a garbage collector stats sample", + GCStatsInfo_fields, + 9 +}; + /* ============================================================================ * UTILITY FUNCTIONS * ============================================================================ */ @@ -1100,6 +1122,159 @@ static PyType_Spec RemoteUnwinder_spec = { .slots = RemoteUnwinder_slots, }; +/* ============================================================================ + * GCMONITOR CLASS IMPLEMENTATION + * ============================================================================ */ + +static void +cleanup_runtime_offsets(RuntimeOffsets *offsets) +{ + if (offsets->handle.pid != 0) { + _Py_RemoteDebug_ClearCache(&offsets->handle); + _Py_RemoteDebug_CleanupProcHandle(&offsets->handle); + } +} + +static int +init_runtime_offsets(RuntimeOffsets *offsets, int pid, int debug) +{ + offsets->debug = debug; + if (_Py_RemoteDebug_InitProcHandle(&offsets->handle, pid) < 0) { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to initialize process handle"); + return -1; + } + offsets->runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(&offsets->handle); + if (offsets->runtime_start_address == 0) { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to get Python runtime address"); + goto error; + } + if (_Py_RemoteDebug_ReadDebugOffsets(&offsets->handle, + &offsets->runtime_start_address, + &offsets->debug_offsets) < 0) + { + set_exception_cause(offsets, PyExc_RuntimeError, "Failed to read debug offsets"); + goto error; + } + if (validate_debug_offsets(&offsets->debug_offsets) == -1) { + set_exception_cause(offsets, PyExc_RuntimeError, "Invalid debug offsets found"); + goto error; + } + return 0; + +error: + cleanup_runtime_offsets(offsets); + return -1; +} + +/*[clinic input] +class _remote_debugging.GCMonitor "GCMonitorObject *" "&GCMonitor_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ebc229325a5e5154]*/ + +/*[clinic input] +@permit_long_summary +@permit_long_docstring_body +_remote_debugging.GCMonitor.__init__ + pid: int + * + debug: bool = False + +Initialize a new GCMonitor object for monitoring GC events from remote process. + +Args: + pid: Process ID of the target Python process to monitor + debug: If True, chain exceptions to explain the sequence of events that + lead to the exception. + +The GCMonitor provides functionality to read GC statistics from a running +Python process. + +Raises: + PermissionError: If access to the target process is denied + OSError: If unable to attach to the target process or access its memory + RuntimeError: If unable to read debug information from the target process +[clinic start generated code]*/ + +static int +_remote_debugging_GCMonitor___init___impl(GCMonitorObject *self, int pid, + int debug) +/*[clinic end generated code: output=2cdf351c2f6335db input=1185a48535b808be]*/ +{ + return init_runtime_offsets(&self->offsets, pid, debug); +} + +/*[clinic input] +@critical_section +_remote_debugging.GCMonitor.get_gc_stats + + all_interpreters: bool = False + If True, return GC statistics from all interpreters. + If False, return only from main interpreter. + +Get garbage collector statistics from external Python process. + +Returns a list of GCStatsInfo objects with GC statistics data. + +Returns: + list of GCStatsInfo: A list of stats samples containing: + - gen: GC generation number. + - iid: Interpreter ID. + - ts_start: Raw timestamp at collection start. + - ts_stop: Raw timestamp at collection stop. + - collections: Total number of collections. + - collected: Total number of collected objects. + - uncollectable: Total number of uncollectable objects. + - candidates: Total objects considered and traversed. + - duration: Total collection time, in seconds. + +Raises: + RuntimeError: If the target process cannot be inspected or if its + debug offsets or GC stats layout are incompatible. +[clinic start generated code]*/ + +static PyObject * +_remote_debugging_GCMonitor_get_gc_stats_impl(GCMonitorObject *self, + int all_interpreters) +/*[clinic end generated code: output=f73f365725224f7a input=09e647719c65f9e4]*/ +{ + RemoteDebuggingState *st = RemoteDebugging_GetStateFromType(Py_TYPE(self)); + return get_gc_stats(&self->offsets, all_interpreters, st->GCStatsInfo_Type); +} + +static PyMethodDef GCMonitor_methods[] = { + _REMOTE_DEBUGGING_GCMONITOR_GET_GC_STATS_METHODDEF + {NULL, NULL} +}; + +static void +GCMonitor_dealloc(PyObject *op) +{ + GCMonitorObject *self = GCMonitor_CAST(op); + PyTypeObject *tp = Py_TYPE(self); + + cleanup_runtime_offsets(&self->offsets); + PyObject_Del(self); + Py_DECREF(tp); +} + +static PyType_Slot GCMonitor_slots[] = { + {Py_tp_doc, (void *)"GCMonitor(pid): Monitor GC events of a remote Python process."}, + {Py_tp_methods, GCMonitor_methods}, + {Py_tp_init, _remote_debugging_GCMonitor___init__}, + {Py_tp_dealloc, GCMonitor_dealloc}, + {0, NULL} +}; + +static PyType_Spec GCMonitor_spec = { + .name = "_remote_debugging.GCMonitor", + .basicsize = sizeof(GCMonitorObject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = GCMonitor_slots, +}; + /* Forward declarations for type specs defined later */ static PyType_Spec BinaryWriter_spec; static PyType_Spec BinaryReader_spec; @@ -1126,6 +1301,11 @@ _remote_debugging_exec(PyObject *m) return -1; } + CREATE_TYPE(m, st->GCMonitor_Type, &GCMonitor_spec); + if (PyModule_AddType(m, st->GCMonitor_Type) < 0) { + return -1; + } + // Initialize structseq types st->TaskInfo_Type = PyStructSequence_NewType(&TaskInfo_desc); if (st->TaskInfo_Type == NULL) { @@ -1183,6 +1363,14 @@ _remote_debugging_exec(PyObject *m) return -1; } + st->GCStatsInfo_Type = PyStructSequence_NewType(&GCStatsInfo_desc); + if (st->GCStatsInfo_Type == NULL) { + return -1; + } + if (PyModule_AddType(m, st->GCStatsInfo_Type) < 0) { + return -1; + } + // Create BinaryWriter and BinaryReader types CREATE_TYPE(m, st->BinaryWriter_Type, &BinaryWriter_spec); if (PyModule_AddType(m, st->BinaryWriter_Type) < 0) { @@ -1240,8 +1428,10 @@ remote_debugging_traverse(PyObject *mod, visitproc visit, void *arg) Py_VISIT(state->ThreadInfo_Type); Py_VISIT(state->InterpreterInfo_Type); Py_VISIT(state->AwaitedInfo_Type); + Py_VISIT(state->GCStatsInfo_Type); Py_VISIT(state->BinaryWriter_Type); Py_VISIT(state->BinaryReader_Type); + Py_VISIT(state->GCMonitor_Type); return 0; } @@ -1257,8 +1447,10 @@ remote_debugging_clear(PyObject *mod) Py_CLEAR(state->ThreadInfo_Type); Py_CLEAR(state->InterpreterInfo_Type); Py_CLEAR(state->AwaitedInfo_Type); + Py_CLEAR(state->GCStatsInfo_Type); Py_CLEAR(state->BinaryWriter_Type); Py_CLEAR(state->BinaryReader_Type); + Py_CLEAR(state->GCMonitor_Type); return 0; } @@ -1837,10 +2029,57 @@ _remote_debugging_is_python_process_impl(PyObject *module, int pid) Py_RETURN_TRUE; } +/*[clinic input] +_remote_debugging.get_gc_stats + + pid: int + * + all_interpreters: bool = False + If True, return GC statistics from all interpreters. + If False, return only from main interpreter. + +Get garbage collector statistics from external Python process. + +Returns: + list of GCStatsInfo: A list of stats samples containing: + - gen: GC generation number. + - iid: Interpreter ID. + - ts_start: Raw timestamp at collection start. + - ts_stop: Raw timestamp at collection stop. + - collections: Total number of collections. + - collected: Total number of collected objects. + - uncollectable: Total number of uncollectable objects. + - candidates: Total objects considered and traversed. + - duration: Total collection time, in seconds. + +Raises: + RuntimeError: If the target process cannot be inspected or if its + debug offsets or GC stats layout are incompatible. +[clinic start generated code]*/ + +static PyObject * +_remote_debugging_get_gc_stats_impl(PyObject *module, int pid, + int all_interpreters) +/*[clinic end generated code: output=d9dce5f7add149bb input=a2a08a45a8f0b119]*/ +{ + RuntimeOffsets offsets; + if (init_runtime_offsets(&offsets, pid, /*debug=*/1) < 0) { + return NULL; + } + + RemoteDebuggingState *st = RemoteDebugging_GetState(module); + PyObject *result = get_gc_stats(&offsets, all_interpreters, + st->GCStatsInfo_Type); + + cleanup_runtime_offsets(&offsets); + return result; +} + static PyMethodDef remote_debugging_methods[] = { _REMOTE_DEBUGGING_ZSTD_AVAILABLE_METHODDEF _REMOTE_DEBUGGING_GET_CHILD_PIDS_METHODDEF _REMOTE_DEBUGGING_IS_PYTHON_PROCESS_METHODDEF + _REMOTE_DEBUGGING_GET_GC_STATS_METHODDEF {NULL, NULL, 0, NULL}, }; diff --git a/Modules/_remote_debugging/object_reading.c b/Modules/_remote_debugging/object_reading.c index 59c28e223c5..b63b103a261 100644 --- a/Modules/_remote_debugging/object_reading.c +++ b/Modules/_remote_debugging/object_reading.c @@ -48,10 +48,8 @@ read_py_str( uintptr_t address, Py_ssize_t max_len ) { - PyObject *result = NULL; - char *buf = NULL; - - // Read the entire PyUnicodeObject at once + // Read the entire PyUnicodeObject at once; for short strings the data + // is inline right after the header and we'll already have (some of) it. char unicode_obj[SIZEOF_UNICODE_OBJ]; int res = _Py_RemoteDebug_PagedReadRemoteMemory( &unwinder->handle, @@ -61,7 +59,7 @@ read_py_str( ); if (res < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read PyUnicodeObject"); - goto err; + return NULL; } Py_ssize_t len = GET_MEMBER(Py_ssize_t, unicode_obj, unwinder->debug_offsets.unicode_object.length); @@ -72,36 +70,94 @@ read_py_str( return NULL; } - buf = (char *)PyMem_RawMalloc(len+1); - if (buf == NULL) { - PyErr_NoMemory(); - set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate buffer for string reading"); + // Inspect state to pick the right data offset and character width. + // We rely on the remote process sharing this Python version's + // PyASCIIObject layout, the same assumption already used for `length`. + struct _PyUnicodeObject_state state = GET_MEMBER( + struct _PyUnicodeObject_state, + unicode_obj, + unwinder->debug_offsets.unicode_object.state); + + if (!state.compact) { + PyErr_Format(PyExc_RuntimeError, + "Cannot read non-compact Unicode object at 0x%lx", address); + set_exception_cause(unwinder, PyExc_RuntimeError, + "Legacy (non-compact) Unicode objects are not supported"); return NULL; } - size_t offset = (size_t)unwinder->debug_offsets.unicode_object.asciiobject_size; - res = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, address + offset, len, buf); - if (res < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read string data from remote memory"); - goto err; + int kind = (int)state.kind; + Py_UCS4 max_char; + switch (kind) { + case PyUnicode_1BYTE_KIND: + max_char = state.ascii ? 0x7F : 0xFF; + break; + case PyUnicode_2BYTE_KIND: + max_char = 0xFFFF; + break; + case PyUnicode_4BYTE_KIND: + max_char = 0x10FFFF; + break; + default: + PyErr_Format(PyExc_RuntimeError, + "Invalid Unicode kind %d at 0x%lx", kind, address); + set_exception_cause(unwinder, PyExc_RuntimeError, + "Invalid kind in remote Unicode object"); + return NULL; } - buf[len] = '\0'; - result = PyUnicode_FromStringAndSize(buf, len); + size_t header_size = state.ascii + ? (size_t)unwinder->debug_offsets.unicode_object.asciiobject_size + : (size_t)unwinder->debug_offsets.unicode_object.compactunicodeobject_size; + + // len * kind is bounded by max_len * 4 (kind <= 4, len <= max_len), so + // the multiplication can't overflow for any caller-sane max_len, but the + // explicit cap here keeps a corrupted remote `length` from later turning + // into a giant allocation. + size_t nbytes = (size_t)len * (size_t)kind; + if ((size_t)len > (SIZE_MAX / 4) || nbytes > (size_t)max_len * 4) { + PyErr_Format(PyExc_RuntimeError, + "Implausible Unicode byte size %zu at 0x%lx", nbytes, address); + set_exception_cause(unwinder, PyExc_RuntimeError, + "Garbage byte size in remote Unicode object"); + return NULL; + } + + PyObject *result = PyUnicode_New(len, max_char); if (result == NULL) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create PyUnicode from remote string data"); - goto err; + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to allocate PyUnicode for remote string"); + return NULL; + } + if (nbytes == 0) { + return result; + } + + void *data = PyUnicode_DATA(result); + + // Reuse data already present in the header read; only round-trip for + // whatever spills past it. + size_t inline_avail = (header_size < SIZEOF_UNICODE_OBJ) + ? SIZEOF_UNICODE_OBJ - header_size + : 0; + size_t inline_bytes = nbytes < inline_avail ? nbytes : inline_avail; + if (inline_bytes > 0) { + memcpy(data, unicode_obj + header_size, inline_bytes); + } + + if (nbytes > inline_bytes) { + res = _Py_RemoteDebug_PagedReadRemoteMemory( + &unwinder->handle, + address + header_size + inline_bytes, + nbytes - inline_bytes, + (char *)data + inline_bytes); + if (res < 0) { + Py_DECREF(result); + set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read string data from remote memory"); + return NULL; + } } - PyMem_RawFree(buf); - assert(result != NULL); return result; - -err: - if (buf != NULL) { - PyMem_RawFree(buf); - } - return NULL; } PyObject * diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 5319d9c7a48..a07675bb66d 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1256,16 +1256,22 @@ write_perf_map_entry(PyObject *self, PyObject *args) { PyObject *code_addr_v; const void *code_addr; - unsigned int code_size; + PyObject *code_size_s; + size_t code_size; const char *entry_name; - if (!PyArg_ParseTuple(args, "OIs", &code_addr_v, &code_size, &entry_name)) + if (!PyArg_ParseTuple(args, "OOs", &code_addr_v, &code_size_s, &entry_name)) return NULL; code_addr = PyLong_AsVoidPtr(code_addr_v); if (code_addr == NULL) { return NULL; } + code_size = PyLong_AsSize_t(code_size_s); + if (code_size == (size_t)-1 && PyErr_Occurred()) { + return NULL; + } + int ret = PyUnstable_WritePerfMapEntry(code_addr, code_size, entry_name); if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e5ce487723b..5bd53c2146a 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -20,6 +20,7 @@ #include "pycore_fileutils.h" // _Py_closerange() #include "pycore_import.h" // _PyImport_AcquireLock() #include "pycore_initconfig.h" // _PyStatus_EXCEPTION() +#include "pycore_jit_unwind.h" // _Py_jit_debug_mutex #include "pycore_long.h" // _PyLong_IsNegative() #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_object.h" // _PyObject_LookupSpecial() @@ -758,6 +759,13 @@ PyOS_AfterFork_Child(void) goto fatal_error; } +#if defined(PY_HAVE_JIT_GDB_UNWIND) + // The child can inherit this mutex locked if another thread held it at + // fork(), but the child itself cannot be inside gdb_jit_register_code(). + // Reinitialize it before any executor cleanup can unregister JIT code. + _Py_jit_debug_mutex = (PyMutex){0}; +#endif + reset_remotedebug_data(tstate); reset_asyncio_state((_PyThreadStateImpl *)tstate); @@ -17195,6 +17203,8 @@ os_getrandom_impl(PyObject *module, Py_ssize_t size, int flags) goto error; } + _Py_MSAN_UNPOISON(data, size); + return PyBytesWriter_FinishWithSize(writer, n); error: diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 50ebe657a0e..8be85b1accb 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -2128,10 +2128,6 @@ code_returns_only_none(PyCodeObject *co) int len = (int)Py_SIZE(co); assert(len > 0); - // The last instruction either returns or raises. We can take advantage - // of that for a quick exit. - _Py_CODEUNIT final = _Py_GetBaseCodeUnit(co, len-1); - // Look up None in co_consts. Py_ssize_t nconsts = PyTuple_Size(co->co_consts); int none_index = 0; @@ -2140,45 +2136,25 @@ code_returns_only_none(PyCodeObject *co) break; } } - if (none_index == nconsts) { - // None wasn't there, which means there was no implicit return, - // "return", or "return None". - - // That means there must be - // an explicit return (non-None), or it only raises. - if (IS_RETURN_OPCODE(final.op.code)) { - // It was an explicit return (non-None). - return 0; + /* We don't worry about EXTENDED_ARG for now. */ + for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) { + _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i); + if (!IS_RETURN_OPCODE(inst.op.code)) { + continue; } - // It must end with a raise then. We still have to walk the - // bytecode to see if there's any explicit return (non-None). - assert(IS_RAISE_OPCODE(final.op.code)); - for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) { - _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i); - if (IS_RETURN_OPCODE(inst.op.code)) { - // We alraedy know it isn't returning None. - return 0; - } + assert(i != 0); + _Py_CODEUNIT prev = _Py_GetBaseCodeUnit(co, i-1); + if (prev.op.code == LOAD_COMMON_CONSTANT && + prev.op.arg == CONSTANT_NONE) + { + continue; } - // It must only raise. - } - else { - // Walk the bytecode, looking for RETURN_VALUE. - for (int i = 0; i < len; i += _PyInstruction_GetLength(co, i)) { - _Py_CODEUNIT inst = _Py_GetBaseCodeUnit(co, i); - if (IS_RETURN_OPCODE(inst.op.code)) { - assert(i != 0); - // Ignore it if it returns None. - _Py_CODEUNIT prev = _Py_GetBaseCodeUnit(co, i-1); - if (prev.op.code == LOAD_CONST) { - // We don't worry about EXTENDED_ARG for now. - if (prev.op.arg == none_index) { - continue; - } - } - return 0; - } + if (none_index < nconsts && prev.op.code == LOAD_CONST + && prev.op.arg == none_index) + { + continue; } + return 0; } return 1; } diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 09db93b2d31..5be5baf8fc4 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -443,7 +443,7 @@ _PyDict_DebugMallocStats(FILE *out) { _PyDebugAllocatorStats(out, "free PyDictObject", _Py_FREELIST_SIZE(dicts), - sizeof(PyDictObject)); + _PyType_PreHeaderSize(&PyDict_Type) + sizeof(PyDictObject)); _PyDebugAllocatorStats(out, "free PyDictKeysObject", _Py_FREELIST_SIZE(dictkeys), sizeof(PyDictKeysObject)); diff --git a/Objects/listobject.c b/Objects/listobject.c index 685b30bb9ee..10e25bbdcdc 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -234,7 +234,7 @@ _PyList_DebugMallocStats(FILE *out) _PyDebugAllocatorStats(out, "free PyListObject", _Py_FREELIST_SIZE(lists), - sizeof(PyListObject)); + _PyType_PreHeaderSize(&PyList_Type) + sizeof(PyListObject)); } PyObject * diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 7757a102677..753c270f525 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -1286,6 +1286,6 @@ _PyTuple_DebugMallocStats(FILE *out) PyOS_snprintf(buf, sizeof(buf), "free %d-sized PyTupleObject", len); _PyDebugAllocatorStats(out, buf, _Py_FREELIST_SIZE(tuples[i]), - _PyObject_VAR_SIZE(&PyTuple_Type, len)); + _PyType_PreHeaderSize(&PyTuple_Type) + _PyObject_VAR_SIZE(&PyTuple_Type, len)); } } diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c index cdc0ea42eac..8ad590cc6e6 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -1983,8 +1983,12 @@ static PyObject * typealias_module(PyObject *self, void *Py_UNUSED(closure)) { typealiasobject *ta = typealiasobject_CAST(self); - if (ta->module != NULL) { - return Py_NewRef(ta->module); + PyObject *module; + Py_BEGIN_CRITICAL_SECTION(self); + module = Py_XNewRef(ta->module); + Py_END_CRITICAL_SECTION(); + if (module != NULL) { + return module; } if (ta->compute_value != NULL) { PyObject* mod = PyFunction_GetModule(ta->compute_value); @@ -1998,12 +2002,25 @@ typealias_module(PyObject *self, void *Py_UNUSED(closure)) Py_RETURN_NONE; } +static int +typealias_set_module(PyObject *self, PyObject *value, void *Py_UNUSED(closure)) +{ + PyObject *old; + typealiasobject *ta = typealiasobject_CAST(self); + Py_BEGIN_CRITICAL_SECTION(self); + old = ta->module; + ta->module = Py_XNewRef(value); + Py_END_CRITICAL_SECTION(); + Py_XDECREF(old); + return 0; +} + static PyGetSetDef typealias_getset[] = { {"__parameters__", typealias_parameters, NULL, NULL, NULL}, {"__type_params__", typealias_type_params, NULL, NULL, NULL}, {"__value__", typealias_value, NULL, NULL, NULL}, {"evaluate_value", typealias_evaluate_value, NULL, NULL, NULL}, - {"__module__", typealias_module, NULL, NULL, NULL}, + {"__module__", typealias_module, typealias_set_module, NULL, NULL}, {0} }; @@ -2203,7 +2220,9 @@ type checkers.\n\ At runtime, Alias is an instance of TypeAliasType. The __name__\n\ attribute holds the name of the type alias. The value of the type alias\n\ is stored in the __value__ attribute. It is evaluated lazily, so the\n\ -value is computed only if the attribute is accessed.\n\ +value is computed only if the attribute is accessed. The __module__\n\ +attribute holds the name of the module in which the type alias was\n\ +defined; it can be assigned to.\n\ \n\ Type aliases can also be generic::\n\ \n\ diff --git a/PCbuild/_remote_debugging.vcxproj b/PCbuild/_remote_debugging.vcxproj index 0e86ce9f4c9..3a9b4033a69 100644 --- a/PCbuild/_remote_debugging.vcxproj +++ b/PCbuild/_remote_debugging.vcxproj @@ -99,6 +99,7 @@ + @@ -108,10 +109,12 @@ + + diff --git a/PCbuild/_remote_debugging.vcxproj.filters b/PCbuild/_remote_debugging.vcxproj.filters index 59d4d5c5c33..001f214805f 100644 --- a/PCbuild/_remote_debugging.vcxproj.filters +++ b/PCbuild/_remote_debugging.vcxproj.filters @@ -15,6 +15,9 @@ Source Files + + Source Files + Source Files @@ -42,6 +45,9 @@ Source Files + + Source Files + @@ -50,6 +56,9 @@ Header Files + + Header Files + diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index df4454cf948..ed1d2e08df8 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -945,6 +945,19 @@ def visitModule(self, mod): return -1; } + int contains = PySet_Contains(state->abstract_types, (PyObject *)Py_TYPE(self)); + if (contains == -1) { + return -1; + } + else if (contains == 1) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "Instantiating abstract AST node class %T is deprecated. " + "This will become an error in Python 3.20", self) < 0) { + return -1; + } + } + Py_ssize_t i, numfields = 0; int res = -1; PyObject *key, *value, *fields, *attributes = NULL, *remaining_fields = NULL; @@ -1777,6 +1790,13 @@ def visitModule(self, mod): if (!state->AST_type) { return -1; } + state->abstract_types = PySet_New(NULL); + if (!state->abstract_types) { + return -1; + } + if (PySet_Add(state->abstract_types, state->AST_type) < 0) { + return -1; + } if (add_ast_fields(state) < 0) { return -1; } @@ -1818,6 +1838,7 @@ def visitSum(self, sum, name): (name, name, len(sum.attributes)), 1) else: self.emit("if (add_attributes(state, state->%s_type, NULL, 0) < 0) return -1;" % name, 1) + self.emit("if (PySet_Add(state->abstract_types, state->%s_type) < 0) return -1;" % name, 1) self.emit_defaults(name, sum.attributes, 1) simple = is_simple(sum) for t in sum.types: @@ -1850,6 +1871,30 @@ def emit_defaults(self, name, fields, depth): class ASTModuleVisitor(PickleVisitor): def visitModule(self, mod): + self.emit(""" +/* Helper for checking if a node class is abstract in the tests. */ +static PyObject * +ast_is_abstract(PyObject *Py_UNUSED(module), PyObject *cls) { + struct ast_state *state = get_ast_state(); + if (state == NULL) { + return NULL; + } + int contains = PySet_Contains(state->abstract_types, cls); + if (contains == -1) { + return NULL; + } + else if (contains == 1) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static struct PyMethodDef astmodule_methods[] = { + {"_is_abstract", ast_is_abstract, METH_O, NULL}, + {NULL} /* Sentinel */ +}; +""".strip(), 0, reflow=False) + self.emit("", 0) self.emit("static int", 0) self.emit("astmodule_exec(PyObject *m)", 0) self.emit("{", 0) @@ -1891,7 +1936,8 @@ def visitModule(self, mod): .m_name = "_ast", // The _ast module uses a per-interpreter state (PyInterpreterState.ast) .m_size = 0, - .m_slots = astmodule_slots, + .m_methods = astmodule_methods, + .m_slots = astmodule_slots }; PyMODINIT_FUNC @@ -2180,6 +2226,7 @@ def generate_module_def(mod, metadata, f, internal_h): "%s_type" % type for type in metadata.types ) + module_state.add("abstract_types") state_strings = sorted(state_strings) module_state = sorted(module_state) diff --git a/Platforms/Android/__main__.py b/Platforms/Android/__main__.py index 315632ea12c..d2546cf76c2 100755 --- a/Platforms/Android/__main__.py +++ b/Platforms/Android/__main__.py @@ -216,8 +216,14 @@ def make_build_python(context): def unpack_deps(host, prefix_dir, cache_dir): os.chdir(prefix_dir) deps_url = "https://github.com/beeware/cpython-android-source-deps/releases/download" - for name_ver in ["bzip2-1.0.8-3", "libffi-3.4.4-3", "openssl-3.5.5-0", - "sqlite-3.50.4-0", "xz-5.4.6-1", "zstd-1.5.7-2"]: + for name_ver in [ + "bzip2-1.0.8-3", + "libffi-3.4.4-3", + "openssl-3.5.6-0", + "sqlite-3.50.4-0", + "xz-5.4.6-1", + "zstd-1.5.7-2" + ]: filename = f"{name_ver}-{host}.tar.gz" out_path = download(f"{deps_url}/{name_ver}/{filename}", cache_dir) shutil.unpack_archive(out_path) diff --git a/Platforms/Apple/__main__.py b/Platforms/Apple/__main__.py index 44a991c6c20..d94198a309f 100644 --- a/Platforms/Apple/__main__.py +++ b/Platforms/Apple/__main__.py @@ -319,7 +319,7 @@ def unpack_deps( for name_ver in [ "BZip2-1.0.8-2", "libFFI-3.4.7-2", - "OpenSSL-3.5.5-1", + "OpenSSL-3.5.6-1", "XZ-5.6.4-2", "mpdecimal-4.0.0-2", "zstd-1.5.7-1", diff --git a/Platforms/WASI/config.toml b/Platforms/WASI/config.toml index 31ec2b8023d..6a6d5713ee9 100644 --- a/Platforms/WASI/config.toml +++ b/Platforms/WASI/config.toml @@ -2,5 +2,5 @@ # This allows for blanket copying of the WASI build code between supported # Python versions. [targets] -wasi-sdk = 32 +wasi-sdk = 33 host-triple = "wasm32-wasip1" diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h index 28e08e42f5c..1bcf98a7600 100644 --- a/Programs/test_frozenmain.h +++ b/Programs/test_frozenmain.h @@ -2,38 +2,38 @@ unsigned char M_test_frozenmain[] = { 227,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0, 0,0,0,0,0,243,188,0,0,0,128,0,0,0,93,0, - 81,1,72,0,115,0,93,0,81,1,72,4,115,1,92,2, - 31,0,81,2,50,1,0,0,0,0,0,0,29,0,92,2, - 31,0,81,3,92,0,79,6,0,0,0,0,0,0,0,0, + 80,7,72,0,115,0,93,0,80,7,72,4,115,1,92,2, + 31,0,81,1,50,1,0,0,0,0,0,0,29,0,92,2, + 31,0,81,2,92,0,79,6,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,50,2,0,0,0,0, 0,0,29,0,92,1,79,8,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,31,0,50,0,0,0, - 0,0,0,0,81,4,42,26,0,0,0,0,0,0,0,0, - 0,0,115,5,81,7,70,0,0,0,68,24,0,0,115,6, - 92,2,31,0,81,5,92,6,12,0,81,6,92,5,92,6, + 0,0,0,0,81,3,42,26,0,0,0,0,0,0,0,0, + 0,0,115,5,81,6,70,0,0,0,68,24,0,0,115,6, + 92,2,31,0,81,4,92,6,12,0,81,5,92,5,92,6, 42,26,0,0,0,0,0,0,0,0,0,0,12,0,48,4, 50,1,0,0,0,0,0,0,29,0,74,26,0,0,9,0, - 28,0,81,1,33,0,41,8,233,0,0,0,0,78,122,18, - 70,114,111,122,101,110,32,72,101,108,108,111,32,87,111,114, - 108,100,122,8,115,121,115,46,97,114,103,118,218,6,99,111, - 110,102,105,103,122,7,99,111,110,102,105,103,32,122,2,58, - 32,41,5,218,12,112,114,111,103,114,97,109,95,110,97,109, - 101,218,10,101,120,101,99,117,116,97,98,108,101,218,15,117, - 115,101,95,101,110,118,105,114,111,110,109,101,110,116,218,17, - 99,111,110,102,105,103,117,114,101,95,99,95,115,116,100,105, - 111,218,14,98,117,102,102,101,114,101,100,95,115,116,100,105, - 111,41,7,218,3,115,121,115,218,17,95,116,101,115,116,105, - 110,116,101,114,110,97,108,99,97,112,105,218,5,112,114,105, - 110,116,218,4,97,114,103,118,218,11,103,101,116,95,99,111, - 110,102,105,103,115,114,3,0,0,0,218,3,107,101,121,169, - 0,243,0,0,0,0,218,18,116,101,115,116,95,102,114,111, - 122,101,110,109,97,105,110,46,112,121,218,8,60,109,111,100, - 117,108,101,62,114,18,0,0,0,1,0,0,0,115,94,0, - 0,0,241,3,1,1,1,243,8,0,1,11,219,0,24,225, - 0,5,208,6,26,212,0,27,217,0,5,128,106,144,35,151, - 40,145,40,212,0,27,216,9,26,215,9,38,210,9,38,211, - 9,40,168,24,213,9,50,128,6,244,2,6,12,2,128,67, - 241,14,0,5,10,136,71,144,67,144,53,152,2,152,54,160, - 35,157,59,152,45,208,10,40,214,4,41,243,15,6,12,2, - 114,16,0,0,0, + 28,0,80,7,33,0,41,7,233,0,0,0,0,122,18,70, + 114,111,122,101,110,32,72,101,108,108,111,32,87,111,114,108, + 100,122,8,115,121,115,46,97,114,103,118,218,6,99,111,110, + 102,105,103,122,7,99,111,110,102,105,103,32,122,2,58,32, + 41,5,218,12,112,114,111,103,114,97,109,95,110,97,109,101, + 218,10,101,120,101,99,117,116,97,98,108,101,218,15,117,115, + 101,95,101,110,118,105,114,111,110,109,101,110,116,218,17,99, + 111,110,102,105,103,117,114,101,95,99,95,115,116,100,105,111, + 218,14,98,117,102,102,101,114,101,100,95,115,116,100,105,111, + 41,7,218,3,115,121,115,218,17,95,116,101,115,116,105,110, + 116,101,114,110,97,108,99,97,112,105,218,5,112,114,105,110, + 116,218,4,97,114,103,118,218,11,103,101,116,95,99,111,110, + 102,105,103,115,114,3,0,0,0,218,3,107,101,121,169,0, + 243,0,0,0,0,218,18,116,101,115,116,95,102,114,111,122, + 101,110,109,97,105,110,46,112,121,218,8,60,109,111,100,117, + 108,101,62,114,18,0,0,0,1,0,0,0,115,94,0,0, + 0,241,3,1,1,1,243,8,0,1,11,219,0,24,225,0, + 5,208,6,26,212,0,27,217,0,5,128,106,144,35,151,40, + 145,40,212,0,27,216,9,26,215,9,38,210,9,38,211,9, + 40,168,24,213,9,50,128,6,244,2,6,12,2,128,67,241, + 14,0,5,10,136,71,144,67,144,53,152,2,152,54,160,35, + 157,59,152,45,208,10,40,214,4,41,243,15,6,12,2,114, + 16,0,0,0, }; diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 6bcf57bdd6b..5f3d9c4b174 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -178,6 +178,7 @@ void _PyAST_Fini(PyInterpreterState *interp) Py_CLEAR(state->__module__); Py_CLEAR(state->_attributes); Py_CLEAR(state->_fields); + Py_CLEAR(state->abstract_types); Py_CLEAR(state->alias_type); Py_CLEAR(state->annotation); Py_CLEAR(state->arg); @@ -5269,6 +5270,19 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) return -1; } + int contains = PySet_Contains(state->abstract_types, (PyObject *)Py_TYPE(self)); + if (contains == -1) { + return -1; + } + else if (contains == 1) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "Instantiating abstract AST node class %T is deprecated. " + "This will become an error in Python 3.20", self) < 0) { + return -1; + } + } + Py_ssize_t i, numfields = 0; int res = -1; PyObject *key, *value, *fields, *attributes = NULL, *remaining_fields = NULL; @@ -6100,6 +6114,13 @@ init_types(void *arg) if (!state->AST_type) { return -1; } + state->abstract_types = PySet_New(NULL); + if (!state->abstract_types) { + return -1; + } + if (PySet_Add(state->abstract_types, state->AST_type) < 0) { + return -1; + } if (add_ast_fields(state) < 0) { return -1; } @@ -6110,6 +6131,7 @@ init_types(void *arg) " | FunctionType(expr* argtypes, expr returns)"); if (!state->mod_type) return -1; if (add_attributes(state, state->mod_type, NULL, 0) < 0) return -1; + if (PySet_Add(state->abstract_types, state->mod_type) < 0) return -1; state->Module_type = make_type(state, "Module", state->mod_type, Module_fields, 2, "Module(stmt* body, type_ignore* type_ignores)"); @@ -6159,6 +6181,7 @@ init_types(void *arg) if (!state->stmt_type) return -1; if (add_attributes(state, state->stmt_type, stmt_attributes, 4) < 0) return -1; + if (PySet_Add(state->abstract_types, state->stmt_type) < 0) return -1; if (PyObject_SetAttr(state->stmt_type, state->end_lineno, Py_None) == -1) return -1; if (PyObject_SetAttr(state->stmt_type, state->end_col_offset, Py_None) == @@ -6348,6 +6371,7 @@ init_types(void *arg) if (!state->expr_type) return -1; if (add_attributes(state, state->expr_type, expr_attributes, 4) < 0) return -1; + if (PySet_Add(state->abstract_types, state->expr_type) < 0) return -1; if (PyObject_SetAttr(state->expr_type, state->end_lineno, Py_None) == -1) return -1; if (PyObject_SetAttr(state->expr_type, state->end_col_offset, Py_None) == @@ -6494,6 +6518,8 @@ init_types(void *arg) "expr_context = Load | Store | Del"); if (!state->expr_context_type) return -1; if (add_attributes(state, state->expr_context_type, NULL, 0) < 0) return -1; + if (PySet_Add(state->abstract_types, state->expr_context_type) < 0) return + -1; state->Load_type = make_type(state, "Load", state->expr_context_type, NULL, 0, "Load"); @@ -6518,6 +6544,7 @@ init_types(void *arg) "boolop = And | Or"); if (!state->boolop_type) return -1; if (add_attributes(state, state->boolop_type, NULL, 0) < 0) return -1; + if (PySet_Add(state->abstract_types, state->boolop_type) < 0) return -1; state->And_type = make_type(state, "And", state->boolop_type, NULL, 0, "And"); if (!state->And_type) return -1; @@ -6535,6 +6562,7 @@ init_types(void *arg) "operator = Add | Sub | Mult | MatMult | Div | Mod | Pow | LShift | RShift | BitOr | BitXor | BitAnd | FloorDiv"); if (!state->operator_type) return -1; if (add_attributes(state, state->operator_type, NULL, 0) < 0) return -1; + if (PySet_Add(state->abstract_types, state->operator_type) < 0) return -1; state->Add_type = make_type(state, "Add", state->operator_type, NULL, 0, "Add"); if (!state->Add_type) return -1; @@ -6629,6 +6657,7 @@ init_types(void *arg) "unaryop = Invert | Not | UAdd | USub"); if (!state->unaryop_type) return -1; if (add_attributes(state, state->unaryop_type, NULL, 0) < 0) return -1; + if (PySet_Add(state->abstract_types, state->unaryop_type) < 0) return -1; state->Invert_type = make_type(state, "Invert", state->unaryop_type, NULL, 0, "Invert"); @@ -6659,6 +6688,7 @@ init_types(void *arg) "cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn"); if (!state->cmpop_type) return -1; if (add_attributes(state, state->cmpop_type, NULL, 0) < 0) return -1; + if (PySet_Add(state->abstract_types, state->cmpop_type) < 0) return -1; state->Eq_type = make_type(state, "Eq", state->cmpop_type, NULL, 0, "Eq"); if (!state->Eq_type) return -1; @@ -6732,6 +6762,8 @@ init_types(void *arg) if (!state->excepthandler_type) return -1; if (add_attributes(state, state->excepthandler_type, excepthandler_attributes, 4) < 0) return -1; + if (PySet_Add(state->abstract_types, state->excepthandler_type) < 0) return + -1; if (PyObject_SetAttr(state->excepthandler_type, state->end_lineno, Py_None) == -1) return -1; @@ -6822,6 +6854,7 @@ init_types(void *arg) if (!state->pattern_type) return -1; if (add_attributes(state, state->pattern_type, pattern_attributes, 4) < 0) return -1; + if (PySet_Add(state->abstract_types, state->pattern_type) < 0) return -1; state->MatchValue_type = make_type(state, "MatchValue", state->pattern_type, MatchValue_fields, 1, @@ -6872,6 +6905,8 @@ init_types(void *arg) "type_ignore = TypeIgnore(int lineno, string tag)"); if (!state->type_ignore_type) return -1; if (add_attributes(state, state->type_ignore_type, NULL, 0) < 0) return -1; + if (PySet_Add(state->abstract_types, state->type_ignore_type) < 0) return + -1; state->TypeIgnore_type = make_type(state, "TypeIgnore", state->type_ignore_type, TypeIgnore_fields, 2, @@ -6885,6 +6920,7 @@ init_types(void *arg) if (!state->type_param_type) return -1; if (add_attributes(state, state->type_param_type, type_param_attributes, 4) < 0) return -1; + if (PySet_Add(state->abstract_types, state->type_param_type) < 0) return -1; state->TypeVar_type = make_type(state, "TypeVar", state->type_param_type, TypeVar_fields, 3, "TypeVar(identifier name, expr? bound, expr? default_value)"); @@ -17956,6 +17992,28 @@ obj2ast_type_param(struct ast_state *state, PyObject* obj, type_param_ty* out, } +/* Helper for checking if a node class is abstract in the tests. */ +static PyObject * +ast_is_abstract(PyObject *Py_UNUSED(module), PyObject *cls) { + struct ast_state *state = get_ast_state(); + if (state == NULL) { + return NULL; + } + int contains = PySet_Contains(state->abstract_types, cls); + if (contains == -1) { + return NULL; + } + else if (contains == 1) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static struct PyMethodDef astmodule_methods[] = { + {"_is_abstract", ast_is_abstract, METH_O, NULL}, + {NULL} /* Sentinel */ +}; + static int astmodule_exec(PyObject *m) { @@ -18382,7 +18440,8 @@ static struct PyModuleDef _astmodule = { .m_name = "_ast", // The _ast module uses a per-interpreter state (PyInterpreterState.ast) .m_size = 0, - .m_slots = astmodule_slots, + .m_methods = astmodule_methods, + .m_slots = astmodule_slots }; PyMODINIT_FUNC diff --git a/Python/bytecodes.c b/Python/bytecodes.c index a73df99f3f5..4239ba58bc3 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2984,6 +2984,7 @@ dummy_func( macro(LOAD_ATTR_CLASS) = unused/1 + + _RECORD_TOS + _CHECK_ATTR_CLASS + unused/2 + _LOAD_ATTR_CLASS + @@ -2991,7 +2992,7 @@ dummy_func( macro(LOAD_ATTR_CLASS_WITH_METACLASS_CHECK) = unused/1 + - _RECORD_TOS_TYPE + + _RECORD_TOS + _GUARD_TYPE_VERSION + _CHECK_ATTR_CLASS + _LOAD_ATTR_CLASS + @@ -6107,6 +6108,13 @@ dummy_func( value = PyStackRef_FromPyObjectBorrow(ptr); } + tier2 pure op(_RROT_3, (bottom, middle, top -- bottom, middle, top)) { + _PyStackRef temp = top; + top = middle; + middle = bottom; + bottom = temp; + } + tier2 op(_START_EXECUTOR, (executor/4 --)) { #ifndef _Py_JIT assert(current_executor == (_PyExecutorObject*)executor); diff --git a/Python/ceval.c b/Python/ceval.c index 0d7f8a62e24..28087ba58d4 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1305,7 +1305,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int } #ifdef _Py_TIER2 #ifdef _Py_JIT -_PyJitEntryFuncPtr _Py_jit_entry = _PyJIT; +_PyJitEntryFuncPtr _Py_jit_entry = _PyJIT_Entry; #else _PyJitEntryFuncPtr _Py_jit_entry = _PyTier2Interpreter; #endif diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index a7d63fd3b82..a4e9980589e 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -168,7 +168,6 @@ #define STOP_TRACING() ((void)(0)); #endif - /* PRE_DISPATCH_GOTO() does lltrace if enabled. Normally a no-op */ #ifdef Py_DEBUG #define PRE_DISPATCH_GOTO() if (frame->lltrace >= 5) { \ diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 5a7129c1ffd..f8fc35de9d7 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -22337,6 +22337,102 @@ break; } + case _RROT_3_r03: { + CHECK_CURRENT_CACHED_VALUES(0); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef top; + _PyStackRef middle; + _PyStackRef bottom; + top = stack_pointer[-1]; + middle = stack_pointer[-2]; + bottom = stack_pointer[-3]; + _PyStackRef temp = top; + top = middle; + middle = bottom; + bottom = temp; + _tos_cache2 = top; + _tos_cache1 = middle; + _tos_cache0 = bottom; + SET_CURRENT_CACHED_VALUES(3); + stack_pointer += -3; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _RROT_3_r13: { + CHECK_CURRENT_CACHED_VALUES(1); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef top; + _PyStackRef middle; + _PyStackRef bottom; + _PyStackRef _stack_item_0 = _tos_cache0; + top = _stack_item_0; + middle = stack_pointer[-1]; + bottom = stack_pointer[-2]; + _PyStackRef temp = top; + top = middle; + middle = bottom; + bottom = temp; + _tos_cache2 = top; + _tos_cache1 = middle; + _tos_cache0 = bottom; + SET_CURRENT_CACHED_VALUES(3); + stack_pointer += -2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _RROT_3_r23: { + CHECK_CURRENT_CACHED_VALUES(2); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef top; + _PyStackRef middle; + _PyStackRef bottom; + _PyStackRef _stack_item_0 = _tos_cache0; + _PyStackRef _stack_item_1 = _tos_cache1; + top = _stack_item_1; + middle = _stack_item_0; + bottom = stack_pointer[-1]; + _PyStackRef temp = top; + top = middle; + middle = bottom; + bottom = temp; + _tos_cache2 = top; + _tos_cache1 = middle; + _tos_cache0 = bottom; + SET_CURRENT_CACHED_VALUES(3); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + + case _RROT_3_r33: { + CHECK_CURRENT_CACHED_VALUES(3); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + _PyStackRef top; + _PyStackRef middle; + _PyStackRef bottom; + _PyStackRef _stack_item_0 = _tos_cache0; + _PyStackRef _stack_item_1 = _tos_cache1; + _PyStackRef _stack_item_2 = _tos_cache2; + top = _stack_item_2; + middle = _stack_item_1; + bottom = _stack_item_0; + _PyStackRef temp = top; + top = middle; + middle = bottom; + bottom = temp; + _tos_cache2 = top; + _tos_cache1 = middle; + _tos_cache0 = bottom; + SET_CURRENT_CACHED_VALUES(3); + assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); + break; + } + case _START_EXECUTOR_r00: { CHECK_CURRENT_CACHED_VALUES(0); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 2cb2d32a410..224426b7aa4 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -10,6 +10,7 @@ #include "pycore_opcode_utils.h" #include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc +#include "pycore_pystate.h" // _PyInterpreterState_GET() #include @@ -1125,6 +1126,8 @@ remove_redundant_nops(cfg_builder *g) { return changes; } +static int loads_const(int opcode); + static int remove_redundant_nops_and_pairs(basicblock *entryblock) { @@ -1148,7 +1151,7 @@ remove_redundant_nops_and_pairs(basicblock *entryblock) int opcode = instr->i_opcode; bool is_redundant_pair = false; if (opcode == POP_TOP) { - if (prev_opcode == LOAD_CONST || prev_opcode == LOAD_SMALL_INT) { + if (loads_const(prev_opcode)) { is_redundant_pair = true; } else if (prev_opcode == COPY && prev_oparg == 1) { @@ -1300,7 +1303,9 @@ jump_thread(basicblock *bb, cfg_instr *inst, cfg_instr *target, int opcode) static int loads_const(int opcode) { - return OPCODE_HAS_CONST(opcode) || opcode == LOAD_SMALL_INT; + return OPCODE_HAS_CONST(opcode) + || opcode == LOAD_SMALL_INT + || opcode == LOAD_COMMON_CONSTANT; } /* Returns new reference */ @@ -1323,6 +1328,10 @@ get_const_value(int opcode, int oparg, PyObject *co_consts) if (opcode == LOAD_SMALL_INT) { return PyLong_FromLong(oparg); } + if (opcode == LOAD_COMMON_CONSTANT) { + assert(oparg < NUM_COMMON_CONSTANTS); + return Py_NewRef(_PyInterpreterState_GET()->common_consts[oparg]); + } if (constant == NULL) { PyErr_SetString(PyExc_SystemError, @@ -1437,6 +1446,46 @@ maybe_instr_make_load_smallint(cfg_instr *instr, PyObject *newconst, return 0; } +/* Does not steal reference to "newconst". + Return 1 if changed instruction to LOAD_COMMON_CONSTANT. + Return 0 if could not change instruction to LOAD_COMMON_CONSTANT. + Return -1 on error. +*/ +static int +maybe_instr_make_load_common_const(cfg_instr *instr, PyObject *newconst) +{ + int oparg; + if (newconst == Py_None) { + oparg = CONSTANT_NONE; + } + else if (newconst == Py_True) { + oparg = CONSTANT_TRUE; + } + else if (newconst == Py_False) { + oparg = CONSTANT_FALSE; + } + else if (PyUnicode_CheckExact(newconst) + && PyUnicode_GET_LENGTH(newconst) == 0) { + oparg = CONSTANT_EMPTY_STR; + } + else if (PyLong_CheckExact(newconst)) { + int overflow; + long val = PyLong_AsLongAndOverflow(newconst, &overflow); + if (val == -1 && PyErr_Occurred()) { + return -1; + } + if (overflow || val != -1) { + return 0; + } + oparg = CONSTANT_MINUS_ONE; + } + else { + return 0; + } + assert(_Py_IsImmortal(newconst)); + INSTR_SET_OP1(instr, LOAD_COMMON_CONSTANT, oparg); + return 1; +} /* Steals reference to "newconst" */ static int @@ -1452,6 +1501,14 @@ instr_make_load_const(cfg_instr *instr, PyObject *newconst, if (res > 0) { return SUCCESS; } + res = maybe_instr_make_load_common_const(instr, newconst); + if (res < 0) { + Py_DECREF(newconst); + return ERROR; + } + if (res > 0) { + return SUCCESS; + } int oparg = add_const(newconst, consts, const_cache, consts_index); RETURN_IF_ERROR(oparg); INSTR_SET_OP1(instr, LOAD_CONST, oparg); @@ -2208,7 +2265,7 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, oparg = inst->i_oparg; } assert(!IS_ASSEMBLER_OPCODE(opcode)); - if (opcode != LOAD_CONST && opcode != LOAD_SMALL_INT) { + if (!loads_const(opcode)) { continue; } int nextop = i+1 < bb->b_iused ? bb->b_instr[i+1].i_opcode : 0; @@ -2308,6 +2365,17 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, break; } } + if (inst->i_opcode == LOAD_CONST) { + PyObject *constant = get_const_value(inst->i_opcode, inst->i_oparg, consts); + if (constant == NULL) { + return ERROR; + } + int res = maybe_instr_make_load_common_const(inst, constant); + Py_DECREF(constant); + if (res < 0) { + return ERROR; + } + } } return SUCCESS; } diff --git a/Python/gc.c b/Python/gc.c index 59bed10c1fb..134da107e1b 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1405,7 +1405,6 @@ add_stats(GCState *gcstate, int gen, struct gc_generation_stats *stats) memcpy(cur_stats, prev_stats, sizeof(struct gc_generation_stats)); cur_stats->ts_start = stats->ts_start; - cur_stats->ts_stop = stats->ts_stop; cur_stats->collections += 1; cur_stats->collected += stats->collected; @@ -1413,6 +1412,9 @@ add_stats(GCState *gcstate, int gen, struct gc_generation_stats *stats) cur_stats->candidates += stats->candidates; cur_stats->duration += stats->duration; + /* Publish ts_stop last so remote readers do not select a partially + updated stats record as the latest collection. */ + cur_stats->ts_stop = stats->ts_stop; } /* This is the main function. Read this to understand how the @@ -1454,10 +1456,14 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) assert(generation >= 0 && generation < NUM_GENERATIONS); #ifdef Py_STATS - if (_Py_stats) { - _Py_stats->object_stats.object_visits = 0; + { + PyStats *s = _PyStats_GET(); + if (s) { + s->object_stats.object_visits = 0; + } } #endif + GC_STAT_ADD(generation, collections, 1); struct gc_generation_stats stats = { 0 }; @@ -1615,12 +1621,16 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) /* Update stats */ add_stats(gcstate, generation, &stats); - GC_STAT_ADD(generation, objects_collected, m); + GC_STAT_ADD(generation, objects_collected, stats.collected); + #ifdef Py_STATS - if (_Py_stats) { - GC_STAT_ADD(generation, object_visits, - _Py_stats->object_stats.object_visits); - _Py_stats->object_stats.object_visits = 0; + { + PyStats *s = _PyStats_GET(); + if (s) { + GC_STAT_ADD(generation, object_visits, + s->object_stats.object_visits); + s->object_stats.object_visits = 0; + } } #endif diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 4b46ca04f56..b4fcd365592 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -2492,6 +2492,8 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) /* Update stats */ struct gc_generation_stats *stats = get_stats(gcstate, generation); + stats->ts_start = start; + stats->ts_stop = stop; stats->collections++; stats->collected += m; stats->uncollectable += n; diff --git a/Python/jit.c b/Python/jit.c index 26e01b25d48..8b555105129 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -15,6 +15,7 @@ #include "pycore_interpframe.h" #include "pycore_interpolation.h" #include "pycore_intrinsics.h" +#include "pycore_jit_unwind.h" #include "pycore_lazyimportobject.h" #include "pycore_list.h" #include "pycore_long.h" @@ -60,6 +61,40 @@ jit_error(const char *message) PyErr_Format(PyExc_RuntimeWarning, "JIT %s (%d)", message, hint); } +/* + * Publish JIT code to optional tooling backends. + * + * The return value is a backend-specific deregistration handle, not a + * success/failure indicator. NULL means there is nothing to unregister later: + * perf does not need a handle, and GDB registration failures are intentionally + * non-fatal because tooling support must not make JIT compilation fail. + */ +static void * +jit_record_code(const void *code_addr, size_t code_size, + const char *entry, const char *filename) +{ +#ifdef PY_HAVE_PERF_TRAMPOLINE + _PyPerf_Callbacks callbacks; + _PyPerfTrampoline_GetCallbacks(&callbacks); + if (callbacks.write_state == _Py_perfmap_jit_callbacks.write_state) { + _PyPerfJit_WriteNamedCode( + code_addr, code_size, entry, filename); + return NULL; + } +#endif + +#if defined(PY_HAVE_JIT_GDB_UNWIND) + return _PyJitUnwind_GdbRegisterCode( + code_addr, code_size, entry, filename); +#else + (void)code_addr; + (void)code_size; + (void)entry; + (void)filename; + return NULL; +#endif +} + static int address_in_executor_array(_PyExecutorObject **ptrs, size_t count, uintptr_t addr) { @@ -715,6 +750,10 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction trace[], siz } executor->jit_code = memory; executor->jit_size = total_size; + executor->jit_gdb_handle = jit_record_code(memory, + code_size + state.trampolines.size, + "jit", + "executor"); return 0; } @@ -727,6 +766,12 @@ _PyJIT_Free(_PyExecutorObject *executor) if (memory) { executor->jit_code = NULL; executor->jit_size = 0; +#if defined(PY_HAVE_JIT_GDB_UNWIND) + if (executor->jit_gdb_handle != NULL) { + _PyJitUnwind_GdbUnregisterCode(executor->jit_gdb_handle); + executor->jit_gdb_handle = NULL; + } +#endif if (jit_free(memory, size)) { PyErr_FormatUnraisable("Exception ignored while " "freeing JIT memory"); diff --git a/Python/jit_unwind.c b/Python/jit_unwind.c new file mode 100644 index 00000000000..09002feafec --- /dev/null +++ b/Python/jit_unwind.c @@ -0,0 +1,986 @@ +/* + * Python JIT - DWARF .eh_frame builder + * + * This file contains the DWARF CFI generator used to build .eh_frame + * data for JIT code (perf jitdump and other unwinders). + */ + +#include "Python.h" +#include "pycore_jit_unwind.h" +#include "pycore_lock.h" + +#if defined(PY_HAVE_JIT_GDB_UNWIND) +# include "jit_unwind_info.h" +# if !JIT_UNWIND_INFO_SUPPORTED +# error "JIT unwind info was not generated for this target" +# endif +#endif + +#if defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND) + +#if defined(PY_HAVE_JIT_GDB_UNWIND) +# include +#endif +#include +#include + +// ============================================================================= +// DWARF CONSTANTS +// ============================================================================= + +/* + * DWARF (Debug With Arbitrary Record Formats) constants + * + * DWARF is a debugging data format used to provide stack unwinding information. + * These constants define the various encoding types and opcodes used in + * DWARF Call Frame Information (CFI) records. + */ + +/* DWARF Call Frame Information version */ +#define DWRF_CIE_VERSION 1 + +/* DWARF CFA (Call Frame Address) opcodes */ +enum { + DWRF_CFA_nop = 0x0, // No operation + DWRF_CFA_offset_extended = 0x5, // Extended offset instruction + DWRF_CFA_def_cfa = 0xc, // Define CFA rule + DWRF_CFA_def_cfa_register = 0xd, // Define CFA register + DWRF_CFA_def_cfa_offset = 0xe, // Define CFA offset + DWRF_CFA_offset_extended_sf = 0x11, // Extended signed offset + DWRF_CFA_advance_loc = 0x40, // Advance location counter + DWRF_CFA_offset = 0x80, // Simple offset instruction + DWRF_CFA_restore = 0xc0 // Restore register +}; + +/* + * Architecture-specific DWARF register numbers + * + * These constants define the register numbering scheme used by DWARF + * for each supported architecture. The numbers must match the ABI + * specification for proper stack unwinding. + */ +enum { +#ifdef __x86_64__ + /* x86_64 register numbering (note: order is defined by x86_64 ABI) */ + DWRF_REG_AX, // RAX + DWRF_REG_DX, // RDX + DWRF_REG_CX, // RCX + DWRF_REG_BX, // RBX + DWRF_REG_SI, // RSI + DWRF_REG_DI, // RDI + DWRF_REG_BP, // RBP + DWRF_REG_SP, // RSP + DWRF_REG_8, // R8 + DWRF_REG_9, // R9 + DWRF_REG_10, // R10 + DWRF_REG_11, // R11 + DWRF_REG_12, // R12 + DWRF_REG_13, // R13 + DWRF_REG_14, // R14 + DWRF_REG_15, // R15 + DWRF_REG_RA, // Return address (RIP) +#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) + /* AArch64 register numbering */ + DWRF_REG_FP = 29, // Frame Pointer + DWRF_REG_RA = 30, // Link register (return address) + DWRF_REG_SP = 31, // Stack pointer +#else +# error "Unsupported target architecture" +#endif +}; + +// ============================================================================= +// ELF OBJECT CONTEXT +// ============================================================================= + +/* + * Context for building ELF/DWARF structures + * + * This structure maintains state while constructing DWARF unwind information. + * It acts as a simple buffer manager with pointers to track current position + * and important landmarks within the buffer. + */ +typedef struct ELFObjectContext { + uint8_t* p; // Current write position in buffer + uint8_t* startp; // Start of buffer (for offset calculations) + uint8_t* fde_p; // Start of FDE data (for PC-relative calculations) + uintptr_t code_addr; // Address of the code section + size_t code_size; // Size of the code section +} ELFObjectContext; + +// ============================================================================= +// DWARF GENERATION UTILITIES +// ============================================================================= + +/* + * Append a null-terminated string to the ELF context buffer. + * + * Args: + * ctx: ELF object context + * str: String to append (must be null-terminated) + * + * Returns: Offset from start of buffer where string was written + */ +static uint32_t elfctx_append_string(ELFObjectContext* ctx, const char* str) { + uint8_t* p = ctx->p; + uint32_t ofs = (uint32_t)(p - ctx->startp); + + /* Copy string including null terminator */ + do { + *p++ = (uint8_t)*str; + } while (*str++); + + ctx->p = p; + return ofs; +} + +/* + * Append a SLEB128 (Signed Little Endian Base 128) value + * + * SLEB128 is a variable-length encoding used extensively in DWARF. + * It efficiently encodes small numbers in fewer bytes. + * + * Args: + * ctx: ELF object context + * v: Signed value to encode + */ +static void elfctx_append_sleb128(ELFObjectContext* ctx, int32_t v) { + uint8_t* p = ctx->p; + + /* Encode 7 bits at a time, with continuation bit in MSB */ + for (; (uint32_t)(v + 0x40) >= 0x80; v >>= 7) { + *p++ = (uint8_t)((v & 0x7f) | 0x80); // Set continuation bit + } + *p++ = (uint8_t)(v & 0x7f); // Final byte without continuation bit + + ctx->p = p; +} + +/* + * Append a ULEB128 (Unsigned Little Endian Base 128) value + * + * Similar to SLEB128 but for unsigned values. + * + * Args: + * ctx: ELF object context + * v: Unsigned value to encode + */ +static void elfctx_append_uleb128(ELFObjectContext* ctx, uint32_t v) { + uint8_t* p = ctx->p; + + /* Encode 7 bits at a time, with continuation bit in MSB */ + for (; v >= 0x80; v >>= 7) { + *p++ = (char)((v & 0x7f) | 0x80); // Set continuation bit + } + *p++ = (char)v; // Final byte without continuation bit + + ctx->p = p; +} + +/* + * Macros for generating DWARF structures + * + * These macros provide a convenient way to write various data types + * to the DWARF buffer while automatically advancing the pointer. + */ +#define DWRF_U8(x) (*p++ = (x)) // Write unsigned 8-bit +#define DWRF_I8(x) (*(int8_t*)p = (x), p++) // Write signed 8-bit +#define DWRF_U16(x) (*(uint16_t*)p = (x), p += 2) // Write unsigned 16-bit +#define DWRF_U32(x) (*(uint32_t*)p = (x), p += 4) // Write unsigned 32-bit +#define DWRF_ADDR(x) (*(uintptr_t*)p = (x), p += sizeof(uintptr_t)) // Write address +#define DWRF_UV(x) (ctx->p = p, elfctx_append_uleb128(ctx, (x)), p = ctx->p) // Write ULEB128 +#define DWRF_SV(x) (ctx->p = p, elfctx_append_sleb128(ctx, (x)), p = ctx->p) // Write SLEB128 +#define DWRF_STR(str) (ctx->p = p, elfctx_append_string(ctx, (str)), p = ctx->p) // Write string + +/* Align to specified boundary with NOP instructions */ +#define DWRF_ALIGNNOP(s) \ + while ((uintptr_t)p & ((s)-1)) { \ + *p++ = DWRF_CFA_nop; \ + } + +/* Write a DWARF section with automatic size calculation */ +#define DWRF_SECTION(name, stmt) \ + { \ + uint32_t* szp_##name = (uint32_t*)p; \ + p += 4; \ + stmt; \ + *szp_##name = (uint32_t)((p - (uint8_t*)szp_##name) - 4); \ + } + +// ============================================================================= +// DWARF EH FRAME GENERATION +// ============================================================================= + +static void elf_init_ehframe_perf(ELFObjectContext* ctx); +#if defined(PY_HAVE_JIT_GDB_UNWIND) +static void elf_init_ehframe_gdb(ELFObjectContext* ctx); +#endif + +static inline void elf_init_ehframe(ELFObjectContext* ctx, int absolute_addr) { + if (absolute_addr) { +#if defined(PY_HAVE_JIT_GDB_UNWIND) + elf_init_ehframe_gdb(ctx); +#else + Py_UNREACHABLE(); +#endif + } + else { + elf_init_ehframe_perf(ctx); + } +} + +size_t +_PyJitUnwind_EhFrameSize(int absolute_addr) +{ + /* The .eh_frame we emit is small and bounded; keep a generous buffer. */ + uint8_t scratch[512]; + _Static_assert(sizeof(scratch) >= 256, + "scratch buffer may be too small for elf_init_ehframe"); + ELFObjectContext ctx; + ctx.code_size = 1; + ctx.code_addr = 0; + ctx.startp = ctx.p = scratch; + ctx.fde_p = NULL; + /* Generate once into scratch to learn the required size. */ + elf_init_ehframe(&ctx, absolute_addr); + ptrdiff_t size = ctx.p - ctx.startp; + assert(size <= (ptrdiff_t)sizeof(scratch)); + return (size_t)size; +} + +size_t +_PyJitUnwind_BuildEhFrame(uint8_t *buffer, size_t buffer_size, + const void *code_addr, size_t code_size, + int absolute_addr) +{ + if (buffer == NULL || code_addr == NULL || code_size == 0) { + return 0; + } + /* Generate the frame twice: once to size-check, once to write. */ + size_t required = _PyJitUnwind_EhFrameSize(absolute_addr); + if (required == 0 || required > buffer_size) { + return 0; + } + ELFObjectContext ctx; + ctx.code_size = code_size; + ctx.code_addr = (uintptr_t)code_addr; + ctx.startp = ctx.p = buffer; + ctx.fde_p = NULL; + elf_init_ehframe(&ctx, absolute_addr); + size_t written = (size_t)(ctx.p - ctx.startp); + /* The frame size is independent of code_addr/code_size (fixed-width fields). */ + assert(written == required); + return written; +} + +/* + * Generate a minimal .eh_frame for a single JIT code region. + * + * The .eh_frame section contains Call Frame Information (CFI) that describes + * how to unwind the stack at any point in the code. This is essential for + * unwinding through JIT-generated code. + * + * The generated data contains: + * 1. A CIE (Common Information Entry) describing the calling convention. + * 2. An FDE (Frame Description Entry) describing how to unwind the JIT frame. + * + * Two flavors are emitted, dispatched on the absolute_addr flag: + * + * - absolute_addr == 0 (elf_init_ehframe_perf): PC-relative FDE address + * encoding for perf's synthesized DSO layout. The CIE describes the + * trampoline's entry state and the FDE walks through the prologue and + * epilogue with advance_loc instructions. This matches the pre-existing + * perf_jit_trampoline behavior byte-for-byte. + * + * - absolute_addr == 1 (elf_init_ehframe_gdb): absolute FDE address + * encoding for the GDB JIT in-memory ELF. The CIE describes the + * steady-state frame layout (CFA = %rbp+16 / x29+16, with saved fp and + * return-address column at fixed offsets) and the FDE emits no further + * CFI. The same rule applies at every PC in the registered region, + * which is correct for executor stencils (they pin the frame pointer + * across the region). This is the GDB-side fix; see elf_init_ehframe_gdb + * for details. + */ +static void elf_init_ehframe_perf(ELFObjectContext* ctx) { + int fde_ptr_enc = DWRF_EH_PE_pcrel | DWRF_EH_PE_sdata4; + uint8_t* p = ctx->p; + uint8_t* framep = p; // Remember start of frame data + + /* + * DWARF Unwind Table for Trampoline Function + * + * This section defines DWARF Call Frame Information (CFI) using encoded macros + * like `DWRF_U8`, `DWRF_UV`, and `DWRF_SECTION` to describe how the trampoline function + * preserves and restores registers. This is used by profiling tools (e.g., `perf`) + * and debuggers for stack unwinding in JIT-compiled code. + * + * ------------------------------------------------- + * TO REGENERATE THIS TABLE FROM GCC OBJECTS: + * ------------------------------------------------- + * + * 1. Create a trampoline source file (e.g., `trampoline.c`): + * + * #include + * typedef PyObject* (*py_evaluator)(void*, void*, int); + * PyObject* trampoline(void *ts, void *f, int throwflag, py_evaluator evaluator) { + * return evaluator(ts, f, throwflag); + * } + * + * 2. Compile to an object file with frame pointer preservation: + * + * gcc trampoline.c -I. -I./Include -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -c + * + * 3. Extract DWARF unwind info from the object file: + * + * readelf -w trampoline.o + * + * Example output from `.eh_frame`: + * + * 00000000 CIE + * Version: 1 + * Augmentation: "zR" + * Code alignment factor: 4 + * Data alignment factor: -8 + * Return address column: 30 + * DW_CFA_def_cfa: r31 (sp) ofs 0 + * + * 00000014 FDE cie=00000000 pc=0..14 + * DW_CFA_advance_loc: 4 + * DW_CFA_def_cfa_offset: 16 + * DW_CFA_offset: r29 at cfa-16 + * DW_CFA_offset: r30 at cfa-8 + * DW_CFA_advance_loc: 12 + * DW_CFA_restore: r30 + * DW_CFA_restore: r29 + * DW_CFA_def_cfa_offset: 0 + * + * -- These values can be verified by comparing with `readelf -w` or `llvm-dwarfdump --eh-frame`. + * + * ---------------------------------- + * HOW TO TRANSLATE TO DWRF_* MACROS: + * ---------------------------------- + * + * After compiling your trampoline with: + * + * gcc trampoline.c -I. -I./Include -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -c + * + * run: + * + * readelf -w trampoline.o + * + * to inspect the generated `.eh_frame` data. You will see two main components: + * + * 1. A CIE (Common Information Entry): shared configuration used by all FDEs. + * 2. An FDE (Frame Description Entry): function-specific unwind instructions. + * + * --------------------- + * Translating the CIE: + * --------------------- + * From `readelf -w`, you might see: + * + * 00000000 0000000000000010 00000000 CIE + * Version: 1 + * Augmentation: "zR" + * Code alignment factor: 4 + * Data alignment factor: -8 + * Return address column: 30 + * Augmentation data: 1b + * DW_CFA_def_cfa: r31 (sp) ofs 0 + * + * Map this to: + * + * DWRF_SECTION(CIE, + * DWRF_U32(0); // CIE ID (always 0 for CIEs) + * DWRF_U8(DWRF_CIE_VERSION); // Version: 1 + * DWRF_STR("zR"); // Augmentation string "zR" + * DWRF_UV(4); // Code alignment factor = 4 + * DWRF_SV(-8); // Data alignment factor = -8 + * DWRF_U8(DWRF_REG_RA); // Return address register (e.g., x30 = 30) + * DWRF_UV(1); // Augmentation data length = 1 + * DWRF_U8(DWRF_EH_PE_pcrel | DWRF_EH_PE_sdata4); // Encoding for FDE pointers + * + * DWRF_U8(DWRF_CFA_def_cfa); // DW_CFA_def_cfa + * DWRF_UV(DWRF_REG_SP); // Register: SP (r31) + * DWRF_UV(0); // Offset = 0 + * + * DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer size boundary + * ) + * + * Notes: + * - Use `DWRF_UV` for unsigned LEB128, `DWRF_SV` for signed LEB128. + * - `DWRF_REG_RA` and `DWRF_REG_SP` are architecture-defined constants. + * + * --------------------- + * Translating the FDE: + * --------------------- + * From `readelf -w`: + * + * 00000014 0000000000000020 00000018 FDE cie=00000000 pc=0000000000000000..0000000000000014 + * DW_CFA_advance_loc: 4 + * DW_CFA_def_cfa_offset: 16 + * DW_CFA_offset: r29 at cfa-16 + * DW_CFA_offset: r30 at cfa-8 + * DW_CFA_advance_loc: 12 + * DW_CFA_restore: r30 + * DW_CFA_restore: r29 + * DW_CFA_def_cfa_offset: 0 + * + * Map the FDE header and instructions to: + * + * DWRF_SECTION(FDE, + * DWRF_U32((uint32_t)(p - framep)); // Offset to CIE (relative from here) + * DWRF_U32(pc_relative_offset); // PC-relative location of the code (calculated dynamically) + * DWRF_U32(ctx->code_size); // Code range covered by this FDE + * DWRF_U8(0); // Augmentation data length (none) + * + * DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance location by 1 unit (1 * 4 = 4 bytes) + * DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP + 16 + * DWRF_UV(16); + * + * DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // Save x29 (frame pointer) + * DWRF_UV(2); // At offset 2 * 8 = 16 bytes + * + * DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // Save x30 (return address) + * DWRF_UV(1); // At offset 1 * 8 = 8 bytes + * + * DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance location by 3 units (3 * 4 = 12 bytes) + * + * DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // Restore x30 + * DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // Restore x29 + * + * DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP + * DWRF_UV(0); + * ) + * + * To regenerate: + * 1. Get the `code alignment factor`, `data alignment factor`, and `RA column` from the CIE. + * 2. Note the range of the function from the FDE's `pc=...` line and map it to the JIT code as + * the code is in a different address space every time. + * 3. For each `DW_CFA_*` entry, use the corresponding `DWRF_*` macro: + * - `DW_CFA_def_cfa_offset` → DWRF_U8(DWRF_CFA_def_cfa_offset), DWRF_UV(value) + * - `DW_CFA_offset: rX` → DWRF_U8(DWRF_CFA_offset | reg), DWRF_UV(offset) + * - `DW_CFA_restore: rX` → DWRF_U8(DWRF_CFA_offset | reg) // restore is same as reusing offset + * - `DW_CFA_advance_loc: N` → DWRF_U8(DWRF_CFA_advance_loc | (N / code_alignment_factor)) + * 4. Use `DWRF_REG_FP`, `DWRF_REG_RA`, etc., for register numbers. + * 5. Use `sizeof(uintptr_t)` (typically 8) for pointer size calculations and alignment. + */ + + /* + * Emit DWARF EH CIE (Common Information Entry) + * + * The CIE describes the calling conventions and basic unwinding rules + * that apply to all functions in this compilation unit. + */ + DWRF_SECTION(CIE, + DWRF_U32(0); // CIE ID (0 indicates this is a CIE) + DWRF_U8(DWRF_CIE_VERSION); // CIE version (1) + DWRF_STR("zR"); // Augmentation string ("zR" = has LSDA) +#ifdef __x86_64__ + DWRF_UV(1); // Code alignment factor (x86_64: 1 byte) +#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) + DWRF_UV(4); // Code alignment factor (AArch64: 4 bytes per instruction) +#endif + DWRF_SV(-(int64_t)sizeof(uintptr_t)); // Data alignment factor (negative) + DWRF_U8(DWRF_REG_RA); // Return address register number + DWRF_UV(1); // Augmentation data length + DWRF_U8(fde_ptr_enc); // FDE pointer encoding + + /* Initial CFI instructions - describe default calling convention */ +#ifdef __x86_64__ + /* x86_64 initial CFI state */ + DWRF_U8(DWRF_CFA_def_cfa); // Define CFA (Call Frame Address) + DWRF_UV(DWRF_REG_SP); // CFA = SP register + DWRF_UV(sizeof(uintptr_t)); // CFA = SP + pointer_size + DWRF_U8(DWRF_CFA_offset|DWRF_REG_RA); // Return address is saved + DWRF_UV(1); // At offset 1 from CFA +#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) + /* AArch64 initial CFI state */ + DWRF_U8(DWRF_CFA_def_cfa); // Define CFA (Call Frame Address) + DWRF_UV(DWRF_REG_SP); // CFA = SP register + DWRF_UV(0); // CFA = SP + 0 (AArch64 starts with offset 0) + // No initial register saves in AArch64 CIE +#endif + DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer boundary + ) + + /* + * Emit DWARF EH FDE (Frame Description Entry) + * + * The FDE describes unwinding information specific to this function. + * It references the CIE and provides function-specific CFI instructions. + * + * The PC-relative offset is calculated after the entire EH frame is built + * to ensure accurate positioning relative to the synthesized DSO layout. + */ + DWRF_SECTION(FDE, + DWRF_U32((uint32_t)(p - framep)); // Offset to CIE (backwards reference) + /* + * In perf jitdump mode the FDE PC field is encoded PC-relative and + * points back to code_start. Record where that field lives so we can + * patch in the final offset after the rest of the synthetic DSO + * layout is known. + */ + ctx->fde_p = p; // Remember where PC offset field is located for later calculation + DWRF_U32(0); // Placeholder for PC-relative offset (calculated below) + DWRF_U32(ctx->code_size); // Address range covered by this FDE (code length) + DWRF_U8(0); // Augmentation data length (none) + + /* + * Architecture-specific CFI instructions + * + * These instructions describe how registers are saved and restored + * during function calls. Each architecture has different calling + * conventions and register usage patterns. + */ +#ifdef __x86_64__ + /* x86_64 calling convention unwinding rules */ +# if defined(__CET__) && (__CET__ & 1) + DWRF_U8(DWRF_CFA_advance_loc | 4); // Advance past endbr64 (4 bytes) +# endif + DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance past push %rbp (1 byte) + DWRF_U8(DWRF_CFA_def_cfa_offset); // def_cfa_offset 16 + DWRF_UV(16); // New offset: SP + 16 + DWRF_U8(DWRF_CFA_offset | DWRF_REG_BP); // offset r6 at cfa-16 + DWRF_UV(2); // Offset factor: 2 * 8 = 16 bytes + DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance past mov %rsp,%rbp (3 bytes) + DWRF_U8(DWRF_CFA_def_cfa_register); // def_cfa_register r6 + DWRF_UV(DWRF_REG_BP); // Use base pointer register + DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance past call *%rcx (2 bytes) + pop %rbp (1 byte) = 3 + DWRF_U8(DWRF_CFA_def_cfa); // def_cfa r7 ofs 8 + DWRF_UV(DWRF_REG_SP); // Use stack pointer register + DWRF_UV(8); // New offset: SP + 8 +#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) + /* AArch64 calling convention unwinding rules */ + DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance by 1 instruction (4 bytes) + DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP + 16 + DWRF_UV(16); // Stack pointer moved by 16 bytes + DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // x29 (frame pointer) saved + DWRF_UV(2); // At CFA-16 (2 * 8 = 16 bytes from CFA) + DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // x30 (link register) saved + DWRF_UV(1); // At CFA-8 (1 * 8 = 8 bytes from CFA) + DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance by 3 instructions (12 bytes) + DWRF_U8(DWRF_CFA_def_cfa_register); // CFA = FP (x29) + 16 + DWRF_UV(DWRF_REG_FP); + DWRF_U8(DWRF_CFA_restore | DWRF_REG_RA); // Restore x30 - NO DWRF_UV() after this! + DWRF_U8(DWRF_CFA_restore | DWRF_REG_FP); // Restore x29 - NO DWRF_UV() after this! + DWRF_U8(DWRF_CFA_def_cfa); // CFA = SP + 0 (stack restored) + DWRF_UV(DWRF_REG_SP); + DWRF_UV(0); + +#else +# error "Unsupported target architecture" +#endif + + DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer boundary + ) + + ctx->p = p; // Update context pointer to end of generated data + + /* Calculate and update the PC-relative offset in the FDE + * + * When perf processes the jitdump, it creates a synthesized DSO with this layout: + * + * Synthesized DSO Memory Layout: + * ┌─────────────────────────────────────────────────────────────┐ < code_start + * │ Code Section │ + * │ (round_up(code_size, 8) bytes) │ + * ├─────────────────────────────────────────────────────────────┤ < start of EH frame data + * │ EH Frame Data │ + * │ ┌─────────────────────────────────────────────────────┐ │ + * │ │ CIE data │ │ + * │ └─────────────────────────────────────────────────────┘ │ + * │ ┌─────────────────────────────────────────────────────┐ │ + * │ │ FDE Header: │ │ + * │ │ - CIE offset (4 bytes) │ │ + * │ │ - PC offset (4 bytes) <─ fde_offset_in_frame ─────┼────┼─> points to code_start + * │ │ - address range (4 bytes) │ │ (this specific field) + * │ │ CFI Instructions... │ │ + * │ └─────────────────────────────────────────────────────┘ │ + * ├─────────────────────────────────────────────────────────────┤ < reference_point + * │ EhFrameHeader │ + * │ (navigation metadata) │ + * └─────────────────────────────────────────────────────────────┘ + * + * The PC offset field in the FDE must contain the distance from itself to code_start: + * + * distance = code_start - fde_pc_field + * + * Where: + * fde_pc_field_location = reference_point - eh_frame_size + fde_offset_in_frame + * code_start_location = reference_point - eh_frame_size - round_up(code_size, 8) + * + * Therefore: + * distance = code_start_location - fde_pc_field_location + * = (ref - eh_frame_size - rounded_code_size) - (ref - eh_frame_size + fde_offset_in_frame) + * = -rounded_code_size - fde_offset_in_frame + * = -(round_up(code_size, 8) + fde_offset_in_frame) + * + * Note: fde_offset_in_frame is the offset from EH frame start to the PC offset field. + * + */ + int32_t rounded_code_size = + (int32_t)_Py_SIZE_ROUND_UP(ctx->code_size, 8); + int32_t fde_offset_in_frame = (int32_t)(ctx->fde_p - framep); + *(int32_t *)ctx->fde_p = -(rounded_code_size + fde_offset_in_frame); +} + +/* + * Build .eh_frame data for the GDB JIT interface. + * + * The executor runs inside the frame established by _PyJIT_Entry, but the + * synthetic executor FDE collapses that state into a single logical JIT frame + * that unwinds directly into _PyEval_*. Executor stencils never touch the + * frame pointer - enforced by Tools/jit/_optimizers.py _validate() and + * -mframe-pointer=reserved - so the steady-state rule is valid at every PC + * and the FDE body is empty. Tools/jit/_targets.py derives the initial CFI + * rules from the row active at the executor call in the compiled shim object. + */ +#if defined(PY_HAVE_JIT_GDB_UNWIND) +static void elf_init_ehframe_gdb(ELFObjectContext* ctx) { + int fde_ptr_enc = DWRF_EH_PE_absptr; + uint8_t* p = ctx->p; + uint8_t* framep = p; + + DWRF_SECTION(CIE, + DWRF_U32(0); // CIE ID + DWRF_U8(DWRF_CIE_VERSION); + DWRF_STR("zR"); // aug data length + FDE ptr encoding follow + DWRF_UV(JIT_UNWIND_CODE_ALIGNMENT_FACTOR); + DWRF_SV(JIT_UNWIND_DATA_ALIGNMENT_FACTOR); + DWRF_U8(JIT_UNWIND_RA_REG); + DWRF_UV(1); // Augmentation data length + DWRF_U8(fde_ptr_enc); // FDE pointer encoding + + /* Executor steady-state rule (our invariant, not the compiler's). */ + DWRF_U8(DWRF_CFA_def_cfa); + DWRF_UV(JIT_UNWIND_CFA_REG); + DWRF_UV(JIT_UNWIND_CFA_OFFSET); + DWRF_U8(DWRF_CFA_offset | JIT_UNWIND_FP_REG); + DWRF_UV(JIT_UNWIND_FP_OFFSET); + DWRF_U8(DWRF_CFA_offset | JIT_UNWIND_RA_REG); + DWRF_UV(JIT_UNWIND_RA_OFFSET); + DWRF_ALIGNNOP(sizeof(uintptr_t)); + ) + + DWRF_SECTION(FDE, + DWRF_U32((uint32_t)(p - framep)); // Offset to CIE (backwards reference) + DWRF_ADDR(ctx->code_addr); // Absolute code start + DWRF_ADDR((uintptr_t)ctx->code_size); // Code range covered + DWRF_U8(0); // Augmentation data length (none) + DWRF_ALIGNNOP(sizeof(uintptr_t)); + ) + + ctx->p = p; +} +#endif + +#if defined(PY_HAVE_JIT_GDB_UNWIND) +enum { + JIT_NOACTION = 0, + JIT_REGISTER_FN = 1, + JIT_UNREGISTER_FN = 2, +}; + +struct jit_code_entry { + struct jit_code_entry *next; + struct jit_code_entry *prev; + const char *symfile_addr; + uint64_t symfile_size; + const void *code_addr; +}; + +struct jit_descriptor { + uint32_t version; + uint32_t action_flag; + struct jit_code_entry *relevant_entry; + struct jit_code_entry *first_entry; +}; + +PyMutex _Py_jit_debug_mutex = {0}; + +Py_EXPORTED_SYMBOL volatile struct jit_descriptor __jit_debug_descriptor = { + 1, JIT_NOACTION, NULL, NULL +}; + +Py_EXPORTED_SYMBOL void __attribute__((noinline)) +__jit_debug_register_code(void) +{ + /* Keep this call visible to debuggers and not optimized away. */ + (void)__jit_debug_descriptor.action_flag; +#if defined(__GNUC__) || defined(__clang__) + __asm__ __volatile__("" ::: "memory"); +#endif +} + +static uint16_t +gdb_jit_machine_id(void) +{ + /* Map the current target to ELF e_machine; return 0 to skip registration. */ +#if defined(__x86_64__) || defined(_M_X64) + return EM_X86_64; +#elif defined(__aarch64__) && !defined(__ILP32__) + return EM_AARCH64; +#else + return 0; +#endif +} + +static struct jit_code_entry * +gdb_jit_register_code( + const void *code_addr, + size_t code_size, + const char *symname, + const uint8_t *eh_frame, + size_t eh_frame_size +) +{ + /* + * Build a minimal in-memory ELF for GDB's JIT interface and link it into + * __jit_debug_descriptor so debuggers can resolve JIT code. + */ + if (code_addr == NULL || code_size == 0 || symname == NULL) { + return NULL; + } + + const uint16_t machine = gdb_jit_machine_id(); + if (machine == 0) { + return NULL; + } + + enum { + SH_NULL = 0, + SH_TEXT, + SH_EH_FRAME, + SH_SHSTRTAB, + SH_STRTAB, + SH_SYMTAB, + SH_NUM, + }; + static const char shstrtab[] = + "\0.text\0.eh_frame\0.shstrtab\0.strtab\0.symtab"; + _Static_assert(sizeof(shstrtab) == + 1 + sizeof(".text") + sizeof(".eh_frame") + + sizeof(".shstrtab") + sizeof(".strtab") + sizeof(".symtab"), + "shstrtab size mismatch"); + const size_t shstrtab_size = sizeof(shstrtab); + const size_t sh_text = 1; + const size_t sh_eh_frame = sh_text + sizeof(".text"); + const size_t sh_shstrtab = sh_eh_frame + sizeof(".eh_frame"); + const size_t sh_strtab = sh_shstrtab + sizeof(".shstrtab"); + const size_t sh_symtab = sh_strtab + sizeof(".strtab"); + const size_t text_size = code_size; + const size_t text_padded = _Py_SIZE_ROUND_UP(text_size, 8); + const size_t strtab_size = 1 + strlen(symname) + 1; + const size_t symtab_size = 3 * sizeof(Elf64_Sym); + + size_t offset = sizeof(Elf64_Ehdr); + offset = _Py_SIZE_ROUND_UP(offset, 16); + const size_t text_off = offset; + const size_t eh_off = text_off + text_padded; + offset = eh_off + eh_frame_size; + const size_t shstr_off = offset; + offset += shstrtab_size; + const size_t str_off = offset; + offset += strtab_size; + /* Elf64_Sym requires 8-byte alignment for st_value/st_size. */ + offset = _Py_SIZE_ROUND_UP(offset, 8); + const size_t sym_off = offset; + offset += symtab_size; + offset = _Py_SIZE_ROUND_UP(offset, sizeof(Elf64_Shdr)); + const size_t sh_off = offset; + + const size_t shnum = SH_NUM; + const size_t total_size = sh_off + shnum * sizeof(Elf64_Shdr); + uint8_t *buf = (uint8_t *)PyMem_RawMalloc(total_size); + if (buf == NULL) { + return NULL; + } + memset(buf, 0, total_size); + + Elf64_Ehdr *ehdr = (Elf64_Ehdr *)buf; + memcpy(ehdr->e_ident, ELFMAG, SELFMAG); + ehdr->e_ident[EI_CLASS] = ELFCLASS64; + ehdr->e_ident[EI_DATA] = ELFDATA2LSB; + ehdr->e_ident[EI_VERSION] = EV_CURRENT; + ehdr->e_ident[EI_OSABI] = ELFOSABI_NONE; + ehdr->e_type = ET_DYN; + ehdr->e_machine = machine; + ehdr->e_version = EV_CURRENT; + ehdr->e_entry = 0; + ehdr->e_phoff = 0; + ehdr->e_shoff = sh_off; + ehdr->e_ehsize = sizeof(Elf64_Ehdr); + ehdr->e_shentsize = sizeof(Elf64_Shdr); + ehdr->e_shnum = shnum; + ehdr->e_shstrndx = SH_SHSTRTAB; + + memcpy(buf + text_off, code_addr, text_size); + memcpy(buf + eh_off, eh_frame, eh_frame_size); + + char *shstr = (char *)(buf + shstr_off); + memcpy(shstr, shstrtab, shstrtab_size); + + char *strtab = (char *)(buf + str_off); + strtab[0] = '\0'; + memcpy(strtab + 1, symname, strlen(symname)); + strtab[strtab_size - 1] = '\0'; + + Elf64_Sym *syms = (Elf64_Sym *)(buf + sym_off); + memset(syms, 0, symtab_size); + /* Section symbol for .text (local) */ + syms[1].st_info = ELF64_ST_INFO(STB_LOCAL, STT_SECTION); + syms[1].st_shndx = 1; + /* Function symbol */ + syms[2].st_name = 1; + syms[2].st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC); + syms[2].st_other = STV_DEFAULT; + syms[2].st_shndx = 1; + /* For ET_DYN/ET_EXEC, st_value is the absolute virtual address. */ + syms[2].st_value = (Elf64_Addr)(uintptr_t)code_addr; + syms[2].st_size = code_size; + + Elf64_Shdr *shdrs = (Elf64_Shdr *)(buf + sh_off); + memset(shdrs, 0, shnum * sizeof(Elf64_Shdr)); + + shdrs[SH_TEXT].sh_name = sh_text; + shdrs[SH_TEXT].sh_type = SHT_PROGBITS; + shdrs[SH_TEXT].sh_flags = SHF_ALLOC | SHF_EXECINSTR; + shdrs[SH_TEXT].sh_addr = (Elf64_Addr)(uintptr_t)code_addr; + shdrs[SH_TEXT].sh_offset = text_off; + shdrs[SH_TEXT].sh_size = text_size; + shdrs[SH_TEXT].sh_addralign = 16; + + shdrs[SH_EH_FRAME].sh_name = sh_eh_frame; + shdrs[SH_EH_FRAME].sh_type = SHT_PROGBITS; + shdrs[SH_EH_FRAME].sh_flags = SHF_ALLOC; + shdrs[SH_EH_FRAME].sh_addr = + (Elf64_Addr)((uintptr_t)code_addr + text_padded); + shdrs[SH_EH_FRAME].sh_offset = eh_off; + shdrs[SH_EH_FRAME].sh_size = eh_frame_size; + shdrs[SH_EH_FRAME].sh_addralign = 8; + + shdrs[SH_SHSTRTAB].sh_name = sh_shstrtab; + shdrs[SH_SHSTRTAB].sh_type = SHT_STRTAB; + shdrs[SH_SHSTRTAB].sh_offset = shstr_off; + shdrs[SH_SHSTRTAB].sh_size = shstrtab_size; + shdrs[SH_SHSTRTAB].sh_addralign = 1; + + shdrs[SH_STRTAB].sh_name = sh_strtab; + shdrs[SH_STRTAB].sh_type = SHT_STRTAB; + shdrs[SH_STRTAB].sh_offset = str_off; + shdrs[SH_STRTAB].sh_size = strtab_size; + shdrs[SH_STRTAB].sh_addralign = 1; + + shdrs[SH_SYMTAB].sh_name = sh_symtab; + shdrs[SH_SYMTAB].sh_type = SHT_SYMTAB; + shdrs[SH_SYMTAB].sh_offset = sym_off; + shdrs[SH_SYMTAB].sh_size = symtab_size; + shdrs[SH_SYMTAB].sh_link = SH_STRTAB; + shdrs[SH_SYMTAB].sh_info = 2; + shdrs[SH_SYMTAB].sh_addralign = 8; + shdrs[SH_SYMTAB].sh_entsize = sizeof(Elf64_Sym); + + struct jit_code_entry *entry = PyMem_RawMalloc(sizeof(*entry)); + if (entry == NULL) { + PyMem_RawFree(buf); + return NULL; + } + entry->symfile_addr = (const char *)buf; + entry->symfile_size = total_size; + entry->code_addr = code_addr; + + PyMutex_Lock(&_Py_jit_debug_mutex); + entry->prev = NULL; + entry->next = __jit_debug_descriptor.first_entry; + if (entry->next != NULL) { + entry->next->prev = entry; + } + __jit_debug_descriptor.first_entry = entry; + __jit_debug_descriptor.relevant_entry = entry; + __jit_debug_descriptor.action_flag = JIT_REGISTER_FN; + __jit_debug_register_code(); + __jit_debug_descriptor.action_flag = JIT_NOACTION; + __jit_debug_descriptor.relevant_entry = NULL; + PyMutex_Unlock(&_Py_jit_debug_mutex); + return entry; +} +#endif // defined(PY_HAVE_JIT_GDB_UNWIND) + +void * +_PyJitUnwind_GdbRegisterCode(const void *code_addr, + size_t code_size, + const char *entry, + const char *filename) +{ +#if defined(PY_HAVE_JIT_GDB_UNWIND) + /* GDB expects a stable symbol name and absolute addresses in .eh_frame. */ + if (entry == NULL) { + entry = ""; + } + if (filename == NULL) { + filename = ""; + } + size_t name_size = snprintf(NULL, 0, "py::%s:%s", entry, filename) + 1; + char *name = (char *)PyMem_RawMalloc(name_size); + if (name == NULL) { + return NULL; + } + snprintf(name, name_size, "py::%s:%s", entry, filename); + + uint8_t buffer[1024]; + size_t eh_frame_size = _PyJitUnwind_BuildEhFrame( + buffer, sizeof(buffer), code_addr, code_size, 1); + if (eh_frame_size == 0) { + PyMem_RawFree(name); + return NULL; + } + + void *handle = gdb_jit_register_code(code_addr, code_size, name, + buffer, eh_frame_size); + PyMem_RawFree(name); + return handle; +#else + (void)code_addr; + (void)code_size; + (void)entry; + (void)filename; + return NULL; +#endif +} + +void +_PyJitUnwind_GdbUnregisterCode(void *handle) +{ +#if defined(PY_HAVE_JIT_GDB_UNWIND) + struct jit_code_entry *entry = (struct jit_code_entry *)handle; + if (entry == NULL) { + return; + } + + PyMutex_Lock(&_Py_jit_debug_mutex); + if (entry->prev != NULL) { + entry->prev->next = entry->next; + } + else { + __jit_debug_descriptor.first_entry = entry->next; + } + if (entry->next != NULL) { + entry->next->prev = entry->prev; + } + + __jit_debug_descriptor.relevant_entry = entry; + __jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN; + __jit_debug_register_code(); + __jit_debug_descriptor.action_flag = JIT_NOACTION; + __jit_debug_descriptor.relevant_entry = NULL; + + PyMutex_Unlock(&_Py_jit_debug_mutex); + + PyMem_RawFree((void *)entry->symfile_addr); + PyMem_RawFree(entry); +#else + (void)handle; +#endif +} + +#endif // defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND) diff --git a/Python/optimizer.c b/Python/optimizer.c index 820a0771a2c..11658fca0da 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1448,6 +1448,7 @@ allocate_executor(int exit_count, int length) res->trace = (_PyUOpInstruction *)(res->exits + exit_count); res->code_size = length; res->exit_count = exit_count; + res->jit_gdb_handle = NULL; return res; } diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 15d4d0bc181..fa34fe4cbb3 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1,4 +1,6 @@ #include "Python.h" +#include "pycore_long.h" +#include "pycore_opcode_utils.h" #include "pycore_optimizer.h" #include "pycore_uops.h" #include "pycore_uop_ids.h" @@ -229,8 +231,8 @@ dummy_func(void) { } op(_CHECK_ATTR_CLASS, (type_version/2, owner -- owner)) { - PyObject *type = (PyObject *)_PyType_LookupByVersion(type_version); - if (type) { + PyObject *type = sym_get_probable_value(owner); + if (type != NULL && ((PyTypeObject *)type)->tp_version_tag == type_version) { if (type == sym_get_const(ctx, owner)) { ADD_OP(_NOP, 0, 0); } @@ -246,7 +248,7 @@ dummy_func(void) { op(_GUARD_TYPE_VERSION, (type_version/2, owner -- owner)) { assert(type_version); - assert(this_instr[-1].opcode == _RECORD_TOS_TYPE); + assert(this_instr[-1].opcode == _RECORD_TOS_TYPE || this_instr[-1].opcode == _RECORD_TOS); if (sym_matches_type_version(owner, type_version)) { ADD_OP(_NOP, 0, 0); } @@ -875,8 +877,14 @@ dummy_func(void) { op(_LOAD_COMMON_CONSTANT, (-- value)) { assert(oparg < NUM_COMMON_CONSTANTS); PyObject *val = _PyInterpreterState_GET()->common_consts[oparg]; - ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val); - value = PyJitRef_Borrow(sym_new_const(ctx, val)); + if (_Py_IsImmortal(val)) { + ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val); + value = PyJitRef_Borrow(sym_new_const(ctx, val)); + } + else { + ADD_OP(_LOAD_CONST_INLINE, 0, (uintptr_t)val); + value = sym_new_const(ctx, val); + } } op(_LOAD_SMALL_INT, (-- value)) { @@ -936,6 +944,13 @@ dummy_func(void) { assert(oparg >= 2); } + op(_RROT_3, (bottom, middle, top -- bottom, middle, top)) { + JitOptRef temp = top; + top = middle; + middle = bottom; + bottom = temp; + } + op(_LOAD_ATTR_INSTANCE_VALUE, (offset/1, owner -- attr, o)) { attr = sym_new_not_null(ctx); (void)offset; diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index b09aca910fc..553bd730202 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -638,10 +638,9 @@ if (sym_is_const(ctx, res)) { PyObject *result = sym_get_const(ctx, res); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -710,10 +709,9 @@ if (sym_is_const(ctx, res)) { PyObject *result = sym_get_const(ctx, res); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -782,10 +780,9 @@ if (sym_is_const(ctx, res)) { PyObject *result = sym_get_const(ctx, res); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -1605,10 +1602,9 @@ if (sym_is_const(ctx, res)) { PyObject *result = sym_get_const(ctx, res); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -1915,8 +1911,14 @@ JitOptRef value; assert(oparg < NUM_COMMON_CONSTANTS); PyObject *val = _PyInterpreterState_GET()->common_consts[oparg]; - ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val); - value = PyJitRef_Borrow(sym_new_const(ctx, val)); + if (_Py_IsImmortal(val)) { + ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val); + value = PyJitRef_Borrow(sym_new_const(ctx, val)); + } + else { + ADD_OP(_LOAD_CONST_INLINE, 0, (uintptr_t)val); + value = sym_new_const(ctx, val); + } CHECK_STACK_BOUNDS(1); stack_pointer[0] = value; stack_pointer += 1; @@ -2522,7 +2524,7 @@ owner = stack_pointer[-1]; uint32_t type_version = (uint32_t)this_instr->operand0; assert(type_version); - assert(this_instr[-1].opcode == _RECORD_TOS_TYPE); + assert(this_instr[-1].opcode == _RECORD_TOS_TYPE || this_instr[-1].opcode == _RECORD_TOS); if (sym_matches_type_version(owner, type_version)) { ADD_OP(_NOP, 0, 0); } @@ -2680,8 +2682,8 @@ JitOptRef owner; owner = stack_pointer[-1]; uint32_t type_version = (uint32_t)this_instr->operand0; - PyObject *type = (PyObject *)_PyType_LookupByVersion(type_version); - if (type) { + PyObject *type = sym_get_probable_value(owner); + if (type != NULL && ((PyTypeObject *)type)->tp_version_tag == type_version) { if (type == sym_get_const(ctx, owner)) { ADD_OP(_NOP, 0, 0); } @@ -2935,10 +2937,9 @@ if (sym_is_const(ctx, res)) { PyObject *result = sym_get_const(ctx, res); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -3010,10 +3011,9 @@ if (sym_is_const(ctx, res)) { PyObject *result = sym_get_const(ctx, res); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -3074,10 +3074,9 @@ if (sym_is_const(ctx, res)) { PyObject *result = sym_get_const(ctx, res); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -3156,10 +3155,9 @@ if (sym_is_const(ctx, b)) { PyObject *result = sym_get_const(ctx, b); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -3265,10 +3263,9 @@ if (sym_is_const(ctx, b)) { PyObject *result = sym_get_const(ctx, b); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -3332,10 +3329,9 @@ if (sym_is_const(ctx, b)) { PyObject *result = sym_get_const(ctx, b); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -5220,10 +5216,9 @@ if (sym_is_const(ctx, res)) { PyObject *result = sym_get_const(ctx, res); if (_Py_IsImmortal(result)) { - // Replace with _LOAD_CONST_INLINE_BORROW + _SWAP + _SWAP since we have two inputs and an immortal result + // Replace with _LOAD_CONST_INLINE_BORROW + _RROT_3 since we have two inputs and an immortal result ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)result); - ADD_OP(_SWAP, 3, 0); - ADD_OP(_SWAP, 2, 0); + ADD_OP(_RROT_3, 0, 0); } } CHECK_STACK_BOUNDS(1); @@ -5504,6 +5499,23 @@ break; } + case _RROT_3: { + JitOptRef top; + JitOptRef middle; + JitOptRef bottom; + top = stack_pointer[-1]; + middle = stack_pointer[-2]; + bottom = stack_pointer[-3]; + JitOptRef temp = top; + top = middle; + middle = bottom; + bottom = temp; + stack_pointer[-3] = bottom; + stack_pointer[-2] = middle; + stack_pointer[-1] = top; + break; + } + case _START_EXECUTOR: { break; } diff --git a/Python/perf_jit_trampoline.c b/Python/perf_jit_trampoline.c index 0ba856ea610..0c460282fec 100644 --- a/Python/perf_jit_trampoline.c +++ b/Python/perf_jit_trampoline.c @@ -62,6 +62,7 @@ #include "pycore_frame.h" #include "pycore_interp.h" #include "pycore_mmap.h" // _PyAnnotateMemoryMap() +#include "pycore_jit_unwind.h" #include "pycore_runtime.h" // _PyRuntime #ifdef PY_HAVE_PERF_TRAMPOLINE @@ -73,6 +74,7 @@ #include // File control operations #include // Standard I/O operations #include // Standard library functions +#include // memcpy, strlen #include // Memory mapping functions (mmap) #include // System data types #include // System calls (sysconf, getpid) @@ -246,6 +248,25 @@ typedef struct { */ } CodeUnwindingInfoEvent; +/* + * EH Frame Header structure for DWARF unwinding + * + * This header provides metadata about the .eh_frame data that follows. + * It uses PC-relative and data-relative encodings to keep the synthesized + * DSO self-contained when perf injects it. + */ +typedef struct __attribute__((packed)) { + uint8_t version; + uint8_t eh_frame_ptr_enc; + uint8_t fde_count_enc; + uint8_t table_enc; + int32_t eh_frame_ptr; + uint32_t eh_fde_count; + int32_t from; + int32_t to; +} EhFrameHeader; +_Static_assert(sizeof(EhFrameHeader) == 20, "EhFrameHeader layout mismatch"); + // ============================================================================= // GLOBAL STATE MANAGEMENT // ============================================================================= @@ -259,10 +280,11 @@ typedef struct { */ typedef struct { FILE* perf_map; // File handle for the jitdump file - PyThread_type_lock map_lock; // Thread synchronization lock + PyMutex map_lock; // Thread synchronization lock void* mapped_buffer; // Memory-mapped region (signals perf we're active) size_t mapped_size; // Size of the mapped region - int code_id; // Counter for unique code region identifiers + uint32_t code_id; // Counter for unique code region identifiers + uint64_t build_id_salt; // Per-process salt for unique synthetic DSOs } PerfMapJitState; /* Global singleton instance */ @@ -316,40 +338,6 @@ static int64_t get_current_time_microseconds(void) { return ((int64_t)(tv.tv_sec) * 1000000) + tv.tv_usec; } -// ============================================================================= -// UTILITY FUNCTIONS -// ============================================================================= - -/* - * Round up a value to the next multiple of a given number - * - * This is essential for maintaining proper alignment requirements in the - * jitdump format. Many structures need to be aligned to specific boundaries - * (typically 8 or 16 bytes) for efficient processing by perf. - * - * Args: - * value: The value to round up - * multiple: The multiple to round up to - * - * Returns: The smallest value >= input that is a multiple of 'multiple' - */ -static size_t round_up(int64_t value, int64_t multiple) { - if (multiple == 0) { - return value; // Avoid division by zero - } - - int64_t remainder = value % multiple; - if (remainder == 0) { - return value; // Already aligned - } - - /* Calculate how much to add to reach the next multiple */ - int64_t difference = multiple - remainder; - int64_t rounded_up_value = value + difference; - - return rounded_up_value; -} - // ============================================================================= // FILE I/O UTILITIES // ============================================================================= @@ -406,623 +394,6 @@ static void perf_map_jit_write_header(int pid, FILE* out_file) { perf_map_jit_write_fully(&header, sizeof(header)); } -// ============================================================================= -// DWARF CONSTANTS AND UTILITIES -// ============================================================================= - -/* - * DWARF (Debug With Arbitrary Record Formats) constants - * - * DWARF is a debugging data format used to provide stack unwinding information. - * These constants define the various encoding types and opcodes used in - * DWARF Call Frame Information (CFI) records. - */ - -/* DWARF Call Frame Information version */ -#define DWRF_CIE_VERSION 1 - -/* DWARF CFA (Call Frame Address) opcodes */ -enum { - DWRF_CFA_nop = 0x0, // No operation - DWRF_CFA_offset_extended = 0x5, // Extended offset instruction - DWRF_CFA_def_cfa = 0xc, // Define CFA rule - DWRF_CFA_def_cfa_register = 0xd, // Define CFA register - DWRF_CFA_def_cfa_offset = 0xe, // Define CFA offset - DWRF_CFA_offset_extended_sf = 0x11, // Extended signed offset - DWRF_CFA_advance_loc = 0x40, // Advance location counter - DWRF_CFA_offset = 0x80, // Simple offset instruction - DWRF_CFA_restore = 0xc0 // Restore register -}; - -/* DWARF Exception Handling pointer encodings */ -enum { - DWRF_EH_PE_absptr = 0x00, // Absolute pointer - DWRF_EH_PE_omit = 0xff, // Omitted value - - /* Data type encodings */ - DWRF_EH_PE_uleb128 = 0x01, // Unsigned LEB128 - DWRF_EH_PE_udata2 = 0x02, // Unsigned 2-byte - DWRF_EH_PE_udata4 = 0x03, // Unsigned 4-byte - DWRF_EH_PE_udata8 = 0x04, // Unsigned 8-byte - DWRF_EH_PE_sleb128 = 0x09, // Signed LEB128 - DWRF_EH_PE_sdata2 = 0x0a, // Signed 2-byte - DWRF_EH_PE_sdata4 = 0x0b, // Signed 4-byte - DWRF_EH_PE_sdata8 = 0x0c, // Signed 8-byte - DWRF_EH_PE_signed = 0x08, // Signed flag - - /* Reference type encodings */ - DWRF_EH_PE_pcrel = 0x10, // PC-relative - DWRF_EH_PE_textrel = 0x20, // Text-relative - DWRF_EH_PE_datarel = 0x30, // Data-relative - DWRF_EH_PE_funcrel = 0x40, // Function-relative - DWRF_EH_PE_aligned = 0x50, // Aligned - DWRF_EH_PE_indirect = 0x80 // Indirect -}; - -/* Additional DWARF constants for debug information */ -enum { DWRF_TAG_compile_unit = 0x11 }; -enum { DWRF_children_no = 0, DWRF_children_yes = 1 }; -enum { - DWRF_AT_name = 0x03, // Name attribute - DWRF_AT_stmt_list = 0x10, // Statement list - DWRF_AT_low_pc = 0x11, // Low PC address - DWRF_AT_high_pc = 0x12 // High PC address -}; -enum { - DWRF_FORM_addr = 0x01, // Address form - DWRF_FORM_data4 = 0x06, // 4-byte data - DWRF_FORM_string = 0x08 // String form -}; - -/* Line number program opcodes */ -enum { - DWRF_LNS_extended_op = 0, // Extended opcode - DWRF_LNS_copy = 1, // Copy operation - DWRF_LNS_advance_pc = 2, // Advance program counter - DWRF_LNS_advance_line = 3 // Advance line number -}; - -/* Line number extended opcodes */ -enum { - DWRF_LNE_end_sequence = 1, // End of sequence - DWRF_LNE_set_address = 2 // Set address -}; - -/* - * Architecture-specific DWARF register numbers - * - * These constants define the register numbering scheme used by DWARF - * for each supported architecture. The numbers must match the ABI - * specification for proper stack unwinding. - */ -enum { -#ifdef __x86_64__ - /* x86_64 register numbering (note: order is defined by x86_64 ABI) */ - DWRF_REG_AX, // RAX - DWRF_REG_DX, // RDX - DWRF_REG_CX, // RCX - DWRF_REG_BX, // RBX - DWRF_REG_SI, // RSI - DWRF_REG_DI, // RDI - DWRF_REG_BP, // RBP - DWRF_REG_SP, // RSP - DWRF_REG_8, // R8 - DWRF_REG_9, // R9 - DWRF_REG_10, // R10 - DWRF_REG_11, // R11 - DWRF_REG_12, // R12 - DWRF_REG_13, // R13 - DWRF_REG_14, // R14 - DWRF_REG_15, // R15 - DWRF_REG_RA, // Return address (RIP) -#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) - /* AArch64 register numbering */ - DWRF_REG_FP = 29, // Frame Pointer - DWRF_REG_RA = 30, // Link register (return address) - DWRF_REG_SP = 31, // Stack pointer -#else -# error "Unsupported target architecture" -#endif -}; - -/* DWARF encoding constants used in EH frame headers */ -static const uint8_t DwarfUData4 = 0x03; // Unsigned 4-byte data -static const uint8_t DwarfSData4 = 0x0b; // Signed 4-byte data -static const uint8_t DwarfPcRel = 0x10; // PC-relative encoding -static const uint8_t DwarfDataRel = 0x30; // Data-relative encoding - -// ============================================================================= -// ELF OBJECT CONTEXT -// ============================================================================= - -/* - * Context for building ELF/DWARF structures - * - * This structure maintains state while constructing DWARF unwind information. - * It acts as a simple buffer manager with pointers to track current position - * and important landmarks within the buffer. - */ -typedef struct ELFObjectContext { - uint8_t* p; // Current write position in buffer - uint8_t* startp; // Start of buffer (for offset calculations) - uint8_t* eh_frame_p; // Start of EH frame data (for relative offsets) - uint8_t* fde_p; // Start of FDE data (for PC-relative calculations) - uint32_t code_size; // Size of the code being described -} ELFObjectContext; - -/* - * EH Frame Header structure for DWARF unwinding - * - * This structure provides metadata about the DWARF unwinding information - * that follows. It's required by the perf jitdump format to enable proper - * stack unwinding during profiling. - */ -typedef struct { - unsigned char version; // EH frame version (always 1) - unsigned char eh_frame_ptr_enc; // Encoding of EH frame pointer - unsigned char fde_count_enc; // Encoding of FDE count - unsigned char table_enc; // Encoding of table entries - int32_t eh_frame_ptr; // Pointer to EH frame data - int32_t eh_fde_count; // Number of FDEs (Frame Description Entries) - int32_t from; // Start address of code range - int32_t to; // End address of code range -} EhFrameHeader; - -// ============================================================================= -// DWARF GENERATION UTILITIES -// ============================================================================= - -/* - * Append a null-terminated string to the ELF context buffer - * - * Args: - * ctx: ELF object context - * str: String to append (must be null-terminated) - * - * Returns: Offset from start of buffer where string was written - */ -static uint32_t elfctx_append_string(ELFObjectContext* ctx, const char* str) { - uint8_t* p = ctx->p; - uint32_t ofs = (uint32_t)(p - ctx->startp); - - /* Copy string including null terminator */ - do { - *p++ = (uint8_t)*str; - } while (*str++); - - ctx->p = p; - return ofs; -} - -/* - * Append a SLEB128 (Signed Little Endian Base 128) value - * - * SLEB128 is a variable-length encoding used extensively in DWARF. - * It efficiently encodes small numbers in fewer bytes. - * - * Args: - * ctx: ELF object context - * v: Signed value to encode - */ -static void elfctx_append_sleb128(ELFObjectContext* ctx, int32_t v) { - uint8_t* p = ctx->p; - - /* Encode 7 bits at a time, with continuation bit in MSB */ - for (; (uint32_t)(v + 0x40) >= 0x80; v >>= 7) { - *p++ = (uint8_t)((v & 0x7f) | 0x80); // Set continuation bit - } - *p++ = (uint8_t)(v & 0x7f); // Final byte without continuation bit - - ctx->p = p; -} - -/* - * Append a ULEB128 (Unsigned Little Endian Base 128) value - * - * Similar to SLEB128 but for unsigned values. - * - * Args: - * ctx: ELF object context - * v: Unsigned value to encode - */ -static void elfctx_append_uleb128(ELFObjectContext* ctx, uint32_t v) { - uint8_t* p = ctx->p; - - /* Encode 7 bits at a time, with continuation bit in MSB */ - for (; v >= 0x80; v >>= 7) { - *p++ = (char)((v & 0x7f) | 0x80); // Set continuation bit - } - *p++ = (char)v; // Final byte without continuation bit - - ctx->p = p; -} - -/* - * Macros for generating DWARF structures - * - * These macros provide a convenient way to write various data types - * to the DWARF buffer while automatically advancing the pointer. - */ -#define DWRF_U8(x) (*p++ = (x)) // Write unsigned 8-bit -#define DWRF_I8(x) (*(int8_t*)p = (x), p++) // Write signed 8-bit -#define DWRF_U16(x) (*(uint16_t*)p = (x), p += 2) // Write unsigned 16-bit -#define DWRF_U32(x) (*(uint32_t*)p = (x), p += 4) // Write unsigned 32-bit -#define DWRF_ADDR(x) (*(uintptr_t*)p = (x), p += sizeof(uintptr_t)) // Write address -#define DWRF_UV(x) (ctx->p = p, elfctx_append_uleb128(ctx, (x)), p = ctx->p) // Write ULEB128 -#define DWRF_SV(x) (ctx->p = p, elfctx_append_sleb128(ctx, (x)), p = ctx->p) // Write SLEB128 -#define DWRF_STR(str) (ctx->p = p, elfctx_append_string(ctx, (str)), p = ctx->p) // Write string - -/* Align to specified boundary with NOP instructions */ -#define DWRF_ALIGNNOP(s) \ - while ((uintptr_t)p & ((s)-1)) { \ - *p++ = DWRF_CFA_nop; \ - } - -/* Write a DWARF section with automatic size calculation */ -#define DWRF_SECTION(name, stmt) \ - { \ - uint32_t* szp_##name = (uint32_t*)p; \ - p += 4; \ - stmt; \ - *szp_##name = (uint32_t)((p - (uint8_t*)szp_##name) - 4); \ - } - -// ============================================================================= -// DWARF EH FRAME GENERATION -// ============================================================================= - -static void elf_init_ehframe(ELFObjectContext* ctx); - -/* - * Initialize DWARF .eh_frame section for a code region - * - * The .eh_frame section contains Call Frame Information (CFI) that describes - * how to unwind the stack at any point in the code. This is essential for - * proper profiling as it allows perf to generate accurate call graphs. - * - * The function generates two main components: - * 1. CIE (Common Information Entry) - describes calling conventions - * 2. FDE (Frame Description Entry) - describes specific function unwinding - * - * Args: - * ctx: ELF object context containing code size and buffer pointers - */ -static size_t calculate_eh_frame_size(void) { - /* Calculate the EH frame size for the trampoline function */ - extern void *_Py_trampoline_func_start; - extern void *_Py_trampoline_func_end; - - size_t code_size = (char*)&_Py_trampoline_func_end - (char*)&_Py_trampoline_func_start; - - ELFObjectContext ctx; - char buffer[1024]; // Buffer for DWARF data (1KB should be sufficient) - ctx.code_size = code_size; - ctx.startp = ctx.p = (uint8_t*)buffer; - ctx.fde_p = NULL; - - elf_init_ehframe(&ctx); - return ctx.p - ctx.startp; -} - -static void elf_init_ehframe(ELFObjectContext* ctx) { - uint8_t* p = ctx->p; - uint8_t* framep = p; // Remember start of frame data - - /* - * DWARF Unwind Table for Trampoline Function - * - * This section defines DWARF Call Frame Information (CFI) using encoded macros - * like `DWRF_U8`, `DWRF_UV`, and `DWRF_SECTION` to describe how the trampoline function - * preserves and restores registers. This is used by profiling tools (e.g., `perf`) - * and debuggers for stack unwinding in JIT-compiled code. - * - * ------------------------------------------------- - * TO REGENERATE THIS TABLE FROM GCC OBJECTS: - * ------------------------------------------------- - * - * 1. Create a trampoline source file (e.g., `trampoline.c`): - * - * #include - * typedef PyObject* (*py_evaluator)(void*, void*, int); - * PyObject* trampoline(void *ts, void *f, int throwflag, py_evaluator evaluator) { - * return evaluator(ts, f, throwflag); - * } - * - * 2. Compile to an object file with frame pointer preservation: - * - * gcc trampoline.c -I. -I./Include -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -c - * - * 3. Extract DWARF unwind info from the object file: - * - * readelf -w trampoline.o - * - * Example output from `.eh_frame`: - * - * 00000000 CIE - * Version: 1 - * Augmentation: "zR" - * Code alignment factor: 4 - * Data alignment factor: -8 - * Return address column: 30 - * DW_CFA_def_cfa: r31 (sp) ofs 0 - * - * 00000014 FDE cie=00000000 pc=0..14 - * DW_CFA_advance_loc: 4 - * DW_CFA_def_cfa_offset: 16 - * DW_CFA_offset: r29 at cfa-16 - * DW_CFA_offset: r30 at cfa-8 - * DW_CFA_advance_loc: 12 - * DW_CFA_restore: r30 - * DW_CFA_restore: r29 - * DW_CFA_def_cfa_offset: 0 - * - * -- These values can be verified by comparing with `readelf -w` or `llvm-dwarfdump --eh-frame`. - * - * ---------------------------------- - * HOW TO TRANSLATE TO DWRF_* MACROS: - * ---------------------------------- - * - * After compiling your trampoline with: - * - * gcc trampoline.c -I. -I./Include -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -c - * - * run: - * - * readelf -w trampoline.o - * - * to inspect the generated `.eh_frame` data. You will see two main components: - * - * 1. A CIE (Common Information Entry): shared configuration used by all FDEs. - * 2. An FDE (Frame Description Entry): function-specific unwind instructions. - * - * --------------------- - * Translating the CIE: - * --------------------- - * From `readelf -w`, you might see: - * - * 00000000 0000000000000010 00000000 CIE - * Version: 1 - * Augmentation: "zR" - * Code alignment factor: 4 - * Data alignment factor: -8 - * Return address column: 30 - * Augmentation data: 1b - * DW_CFA_def_cfa: r31 (sp) ofs 0 - * - * Map this to: - * - * DWRF_SECTION(CIE, - * DWRF_U32(0); // CIE ID (always 0 for CIEs) - * DWRF_U8(DWRF_CIE_VERSION); // Version: 1 - * DWRF_STR("zR"); // Augmentation string "zR" - * DWRF_UV(4); // Code alignment factor = 4 - * DWRF_SV(-8); // Data alignment factor = -8 - * DWRF_U8(DWRF_REG_RA); // Return address register (e.g., x30 = 30) - * DWRF_UV(1); // Augmentation data length = 1 - * DWRF_U8(DWRF_EH_PE_pcrel | DWRF_EH_PE_sdata4); // Encoding for FDE pointers - * - * DWRF_U8(DWRF_CFA_def_cfa); // DW_CFA_def_cfa - * DWRF_UV(DWRF_REG_SP); // Register: SP (r31) - * DWRF_UV(0); // Offset = 0 - * - * DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer size boundary - * ) - * - * Notes: - * - Use `DWRF_UV` for unsigned LEB128, `DWRF_SV` for signed LEB128. - * - `DWRF_REG_RA` and `DWRF_REG_SP` are architecture-defined constants. - * - * --------------------- - * Translating the FDE: - * --------------------- - * From `readelf -w`: - * - * 00000014 0000000000000020 00000018 FDE cie=00000000 pc=0000000000000000..0000000000000014 - * DW_CFA_advance_loc: 4 - * DW_CFA_def_cfa_offset: 16 - * DW_CFA_offset: r29 at cfa-16 - * DW_CFA_offset: r30 at cfa-8 - * DW_CFA_advance_loc: 12 - * DW_CFA_restore: r30 - * DW_CFA_restore: r29 - * DW_CFA_def_cfa_offset: 0 - * - * Map the FDE header and instructions to: - * - * DWRF_SECTION(FDE, - * DWRF_U32((uint32_t)(p - framep)); // Offset to CIE (relative from here) - * DWRF_U32(pc_relative_offset); // PC-relative location of the code (calculated dynamically) - * DWRF_U32(ctx->code_size); // Code range covered by this FDE - * DWRF_U8(0); // Augmentation data length (none) - * - * DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance location by 1 unit (1 * 4 = 4 bytes) - * DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP + 16 - * DWRF_UV(16); - * - * DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // Save x29 (frame pointer) - * DWRF_UV(2); // At offset 2 * 8 = 16 bytes - * - * DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // Save x30 (return address) - * DWRF_UV(1); // At offset 1 * 8 = 8 bytes - * - * DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance location by 3 units (3 * 4 = 12 bytes) - * - * DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // Restore x30 - * DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // Restore x29 - * - * DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP - * DWRF_UV(0); - * ) - * - * To regenerate: - * 1. Get the `code alignment factor`, `data alignment factor`, and `RA column` from the CIE. - * 2. Note the range of the function from the FDE's `pc=...` line and map it to the JIT code as - * the code is in a different address space every time. - * 3. For each `DW_CFA_*` entry, use the corresponding `DWRF_*` macro: - * - `DW_CFA_def_cfa_offset` → DWRF_U8(DWRF_CFA_def_cfa_offset), DWRF_UV(value) - * - `DW_CFA_offset: rX` → DWRF_U8(DWRF_CFA_offset | reg), DWRF_UV(offset) - * - `DW_CFA_restore: rX` → DWRF_U8(DWRF_CFA_offset | reg) // restore is same as reusing offset - * - `DW_CFA_advance_loc: N` → DWRF_U8(DWRF_CFA_advance_loc | (N / code_alignment_factor)) - * 4. Use `DWRF_REG_FP`, `DWRF_REG_RA`, etc., for register numbers. - * 5. Use `sizeof(uintptr_t)` (typically 8) for pointer size calculations and alignment. - */ - - /* - * Emit DWARF EH CIE (Common Information Entry) - * - * The CIE describes the calling conventions and basic unwinding rules - * that apply to all functions in this compilation unit. - */ - DWRF_SECTION(CIE, - DWRF_U32(0); // CIE ID (0 indicates this is a CIE) - DWRF_U8(DWRF_CIE_VERSION); // CIE version (1) - DWRF_STR("zR"); // Augmentation string ("zR" = has LSDA) -#ifdef __x86_64__ - DWRF_UV(1); // Code alignment factor (x86_64: 1 byte) -#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) - DWRF_UV(4); // Code alignment factor (AArch64: 4 bytes per instruction) -#endif - DWRF_SV(-(int64_t)sizeof(uintptr_t)); // Data alignment factor (negative) - DWRF_U8(DWRF_REG_RA); // Return address register number - DWRF_UV(1); // Augmentation data length - DWRF_U8(DWRF_EH_PE_pcrel | DWRF_EH_PE_sdata4); // FDE pointer encoding - - /* Initial CFI instructions - describe default calling convention */ -#ifdef __x86_64__ - /* x86_64 initial CFI state */ - DWRF_U8(DWRF_CFA_def_cfa); // Define CFA (Call Frame Address) - DWRF_UV(DWRF_REG_SP); // CFA = SP register - DWRF_UV(sizeof(uintptr_t)); // CFA = SP + pointer_size - DWRF_U8(DWRF_CFA_offset|DWRF_REG_RA); // Return address is saved - DWRF_UV(1); // At offset 1 from CFA -#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) - /* AArch64 initial CFI state */ - DWRF_U8(DWRF_CFA_def_cfa); // Define CFA (Call Frame Address) - DWRF_UV(DWRF_REG_SP); // CFA = SP register - DWRF_UV(0); // CFA = SP + 0 (AArch64 starts with offset 0) - // No initial register saves in AArch64 CIE -#endif - DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer boundary - ) - - ctx->eh_frame_p = p; // Remember start of FDE data - - /* - * Emit DWARF EH FDE (Frame Description Entry) - * - * The FDE describes unwinding information specific to this function. - * It references the CIE and provides function-specific CFI instructions. - * - * The PC-relative offset is calculated after the entire EH frame is built - * to ensure accurate positioning relative to the synthesized DSO layout. - */ - DWRF_SECTION(FDE, - DWRF_U32((uint32_t)(p - framep)); // Offset to CIE (backwards reference) - ctx->fde_p = p; // Remember where PC offset field is located for later calculation - DWRF_U32(0); // Placeholder for PC-relative offset (calculated at end of elf_init_ehframe) - DWRF_U32(ctx->code_size); // Address range covered by this FDE (code length) - DWRF_U8(0); // Augmentation data length (none) - - /* - * Architecture-specific CFI instructions - * - * These instructions describe how registers are saved and restored - * during function calls. Each architecture has different calling - * conventions and register usage patterns. - */ -#ifdef __x86_64__ - /* x86_64 calling convention unwinding rules with frame pointer */ -# if defined(__CET__) && (__CET__ & 1) - DWRF_U8(DWRF_CFA_advance_loc | 4); // Advance past endbr64 (4 bytes) -# endif - DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance past push %rbp (1 byte) - DWRF_U8(DWRF_CFA_def_cfa_offset); // def_cfa_offset 16 - DWRF_UV(16); // New offset: SP + 16 - DWRF_U8(DWRF_CFA_offset | DWRF_REG_BP); // offset r6 at cfa-16 - DWRF_UV(2); // Offset factor: 2 * 8 = 16 bytes - DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance past mov %rsp,%rbp (3 bytes) - DWRF_U8(DWRF_CFA_def_cfa_register); // def_cfa_register r6 - DWRF_UV(DWRF_REG_BP); // Use base pointer register - DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance past call *%rcx (2 bytes) + pop %rbp (1 byte) = 3 - DWRF_U8(DWRF_CFA_def_cfa); // def_cfa r7 ofs 8 - DWRF_UV(DWRF_REG_SP); // Use stack pointer register - DWRF_UV(8); // New offset: SP + 8 -#elif defined(__aarch64__) && defined(__AARCH64EL__) && !defined(__ILP32__) - /* AArch64 calling convention unwinding rules */ - DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance by 1 instruction (4 bytes) - DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP + 16 - DWRF_UV(16); // Stack pointer moved by 16 bytes - DWRF_U8(DWRF_CFA_offset | DWRF_REG_FP); // x29 (frame pointer) saved - DWRF_UV(2); // At CFA-16 (2 * 8 = 16 bytes from CFA) - DWRF_U8(DWRF_CFA_offset | DWRF_REG_RA); // x30 (link register) saved - DWRF_UV(1); // At CFA-8 (1 * 8 = 8 bytes from CFA) - DWRF_U8(DWRF_CFA_advance_loc | 3); // Advance by 3 instructions (12 bytes) - DWRF_U8(DWRF_CFA_restore | DWRF_REG_RA); // Restore x30 - NO DWRF_UV() after this! - DWRF_U8(DWRF_CFA_restore | DWRF_REG_FP); // Restore x29 - NO DWRF_UV() after this! - DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA = SP + 0 (stack restored) - DWRF_UV(0); // Back to original stack position -#else -# error "Unsupported target architecture" -#endif - - DWRF_ALIGNNOP(sizeof(uintptr_t)); // Align to pointer boundary - ) - - ctx->p = p; // Update context pointer to end of generated data - - /* Calculate and update the PC-relative offset in the FDE - * - * When perf processes the jitdump, it creates a synthesized DSO with this layout: - * - * Synthesized DSO Memory Layout: - * ┌─────────────────────────────────────────────────────────────┐ < code_start - * │ Code Section │ - * │ (round_up(code_size, 8) bytes) │ - * ├─────────────────────────────────────────────────────────────┤ < start of EH frame data - * │ EH Frame Data │ - * │ ┌─────────────────────────────────────────────────────┐ │ - * │ │ CIE data │ │ - * │ └─────────────────────────────────────────────────────┘ │ - * │ ┌─────────────────────────────────────────────────────┐ │ - * │ │ FDE Header: │ │ - * │ │ - CIE offset (4 bytes) │ │ - * │ │ - PC offset (4 bytes) <─ fde_offset_in_frame ─────┼────┼─> points to code_start - * │ │ - address range (4 bytes) │ │ (this specific field) - * │ │ CFI Instructions... │ │ - * │ └─────────────────────────────────────────────────────┘ │ - * ├─────────────────────────────────────────────────────────────┤ < reference_point - * │ EhFrameHeader │ - * │ (navigation metadata) │ - * └─────────────────────────────────────────────────────────────┘ - * - * The PC offset field in the FDE must contain the distance from itself to code_start: - * - * distance = code_start - fde_pc_field - * - * Where: - * fde_pc_field_location = reference_point - eh_frame_size + fde_offset_in_frame - * code_start_location = reference_point - eh_frame_size - round_up(code_size, 8) - * - * Therefore: - * distance = code_start_location - fde_pc_field_location - * = (ref - eh_frame_size - rounded_code_size) - (ref - eh_frame_size + fde_offset_in_frame) - * = -rounded_code_size - fde_offset_in_frame - * = -(round_up(code_size, 8) + fde_offset_in_frame) - * - * Note: fde_offset_in_frame is the offset from EH frame start to the PC offset field, - * - */ - if (ctx->fde_p != NULL) { - int32_t fde_offset_in_frame = (ctx->fde_p - ctx->startp); - int32_t rounded_code_size = round_up(ctx->code_size, 8); - int32_t pc_relative_offset = -(rounded_code_size + fde_offset_in_frame); - - - // Update the PC-relative offset in the FDE - *(int32_t*)ctx->fde_p = pc_relative_offset; - } -} - // ============================================================================= // JITDUMP INITIALIZATION // ============================================================================= @@ -1042,6 +413,12 @@ static void elf_init_ehframe(ELFObjectContext* ctx) { * Returns: Pointer to initialized state, or NULL on failure */ static void* perf_map_jit_init(void) { + PyMutex_Lock(&perf_jit_map_state.map_lock); + if (perf_jit_map_state.perf_map != NULL) { + PyMutex_Unlock(&perf_jit_map_state.map_lock); + return &perf_jit_map_state; + } + char filename[100]; int pid = getpid(); @@ -1051,6 +428,7 @@ static void* perf_map_jit_init(void) { /* Create/open the jitdump file with appropriate permissions */ const int fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0666); if (fd == -1) { + PyMutex_Unlock(&perf_jit_map_state.map_lock); return NULL; // Failed to create file } @@ -1058,6 +436,7 @@ static void* perf_map_jit_init(void) { const long page_size = sysconf(_SC_PAGESIZE); if (page_size == -1) { close(fd); + PyMutex_Unlock(&perf_jit_map_state.map_lock); return NULL; // Failed to get page size } @@ -1086,6 +465,7 @@ static void* perf_map_jit_init(void) { if (perf_jit_map_state.mapped_buffer == MAP_FAILED) { perf_jit_map_state.mapped_buffer = NULL; close(fd); + PyMutex_Unlock(&perf_jit_map_state.map_lock); return NULL; // Memory mapping failed } (void)_PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size, @@ -1098,6 +478,7 @@ static void* perf_map_jit_init(void) { perf_jit_map_state.perf_map = fdopen(fd, "w+"); if (perf_jit_map_state.perf_map == NULL) { close(fd); + PyMutex_Unlock(&perf_jit_map_state.map_lock); return NULL; // Failed to create FILE* } @@ -1113,28 +494,18 @@ static void* perf_map_jit_init(void) { /* Write the jitdump file header */ perf_map_jit_write_header(pid, perf_jit_map_state.perf_map); - /* - * Initialize thread synchronization lock - * - * Multiple threads may attempt to write to the jitdump file - * simultaneously. This lock ensures thread-safe access to the - * global jitdump state. - */ - perf_jit_map_state.map_lock = PyThread_allocate_lock(); - if (perf_jit_map_state.map_lock == NULL) { - fclose(perf_jit_map_state.perf_map); - return NULL; // Failed to create lock - } - /* Initialize code ID counter */ perf_jit_map_state.code_id = 0; + perf_jit_map_state.build_id_salt = + ((uint64_t)pid << 32) ^ (uint64_t)get_current_monotonic_ticks(); /* Calculate padding size based on actual unwind info requirements */ - size_t eh_frame_size = calculate_eh_frame_size(); + size_t eh_frame_size = _PyJitUnwind_EhFrameSize(0); size_t unwind_data_size = sizeof(EhFrameHeader) + eh_frame_size; - trampoline_api.code_padding = round_up(unwind_data_size, 16); + trampoline_api.code_padding = _Py_SIZE_ROUND_UP(unwind_data_size, 16); trampoline_api.code_alignment = 32; + PyMutex_Unlock(&perf_jit_map_state.map_lock); return &perf_jit_map_state; } @@ -1143,54 +514,31 @@ static void* perf_map_jit_init(void) { // ============================================================================= /* - * Write a complete jitdump entry for a Python function + * Write a complete jitdump entry for a code region with a provided name. * - * This is the main function called by Python's trampoline system whenever - * a new piece of JIT-compiled code needs to be recorded. It writes both - * the unwinding information and the code load event to the jitdump file. - * - * The function performs these steps: - * 1. Initialize jitdump system if not already done - * 2. Extract function name and filename from Python code object - * 3. Generate DWARF unwinding information - * 4. Write unwinding info event to jitdump file - * 5. Write code load event to jitdump file - * - * Args: - * state: Jitdump state (currently unused, uses global state) - * code_addr: Address where the compiled code resides - * code_size: Size of the compiled code in bytes - * co: Python code object containing metadata - * - * IMPORTANT: This function signature is part of Python's internal API - * and must not be changed without coordinating with core Python development. + * This shares the same implementation as the trampoline callback, but + * allows callers that don't have a PyCodeObject to reuse the jitdump + * infrastructure. */ -static void perf_map_jit_write_entry(void *state, const void *code_addr, - unsigned int code_size, PyCodeObject *co) +static void perf_map_jit_write_entry_with_name( + void *state, + const void *code_addr, + size_t code_size, + const char *entry, + const char *filename +) { /* Initialize jitdump system on first use */ - if (perf_jit_map_state.perf_map == NULL) { - void* ret = perf_map_jit_init(); - if(ret == NULL){ - return; // Initialization failed, silently abort - } + void* ret = perf_map_jit_init(); + if (ret == NULL) { + return; // Initialization failed, silently abort } - /* - * Extract function information from Python code object - * - * We create a human-readable function name by combining the qualified - * name (includes class/module context) with the filename. This helps - * developers identify functions in perf reports. - */ - const char *entry = ""; - if (co->co_qualname != NULL) { - entry = PyUnicode_AsUTF8(co->co_qualname); + if (entry == NULL) { + entry = ""; } - - const char *filename = ""; - if (co->co_filename != NULL) { - filename = PyUnicode_AsUTF8(co->co_filename); + if (filename == NULL) { + filename = ""; } /* @@ -1218,15 +566,20 @@ static void perf_map_jit_write_entry(void *state, const void *code_addr, * Without it, perf cannot generate accurate call graphs, especially * in optimized code where frame pointers may be omitted. */ - ELFObjectContext ctx; - char buffer[1024]; // Buffer for DWARF data (1KB should be sufficient) - ctx.code_size = code_size; - ctx.startp = ctx.p = (uint8_t*)buffer; - ctx.fde_p = NULL; // Initialize to NULL, will be set when FDE is written + uint8_t buffer[1024]; // Buffer for DWARF data (1KB should be sufficient) + size_t eh_frame_size = _PyJitUnwind_BuildEhFrame( + buffer, sizeof(buffer), code_addr, code_size, 0); + if (eh_frame_size == 0) { + PyMem_RawFree(perf_map_entry); + return; + } - /* Generate EH frame (Exception Handling frame) data */ - elf_init_ehframe(&ctx); - int eh_frame_size = ctx.p - ctx.startp; + /* + * A logical jitdump entry is written as multiple records and also consumes + * a process-global code_id. Serialize the whole sequence so concurrent JIT + * compilation cannot interleave records or reuse an ID. + */ + PyMutex_Lock(&perf_jit_map_state.map_lock); /* * Write Code Unwinding Information Event @@ -1244,12 +597,12 @@ static void perf_map_jit_write_entry(void *state, const void *code_addr, assert(ev2.unwind_data_size <= (uint64_t)trampoline_api.code_padding); ev2.eh_frame_hdr_size = sizeof(EhFrameHeader); - ev2.mapped_size = round_up(ev2.unwind_data_size, 16); // 16-byte alignment + ev2.mapped_size = _Py_SIZE_ROUND_UP(ev2.unwind_data_size, 16); // 16-byte alignment /* Calculate total event size with padding */ - int content_size = sizeof(ev2) + sizeof(EhFrameHeader) + eh_frame_size; - int padding_size = round_up(content_size, 8) - content_size; // 8-byte align - ev2.base.size = content_size + padding_size; + int content_size = (int)(sizeof(ev2) + sizeof(EhFrameHeader) + eh_frame_size); + int padding_size = (int)_Py_SIZE_ROUND_UP((size_t)content_size, 8) - content_size; // 8-byte align + ev2.base.size = (uint32_t)(content_size + padding_size); /* Write the unwinding info event header */ perf_map_jit_write_fully(&ev2, sizeof(ev2)); @@ -1263,20 +616,21 @@ static void perf_map_jit_write_entry(void *state, const void *code_addr, */ EhFrameHeader f; f.version = 1; - f.eh_frame_ptr_enc = DwarfSData4 | DwarfPcRel; // PC-relative signed 4-byte - f.fde_count_enc = DwarfUData4; // Unsigned 4-byte count - f.table_enc = DwarfSData4 | DwarfDataRel; // Data-relative signed 4-byte + f.eh_frame_ptr_enc = DWRF_EH_PE_sdata4 | DWRF_EH_PE_pcrel; + f.fde_count_enc = DWRF_EH_PE_udata4; + f.table_enc = DWRF_EH_PE_sdata4 | DWRF_EH_PE_datarel; /* Calculate relative offsets for EH frame navigation */ - f.eh_frame_ptr = -(eh_frame_size + 4 * sizeof(unsigned char)); + f.eh_frame_ptr = -(int32_t)(eh_frame_size + 4 * sizeof(unsigned char)); f.eh_fde_count = 1; // We generate exactly one FDE per function - f.from = -(round_up(code_size, 8) + eh_frame_size); - - int cie_size = ctx.eh_frame_p - ctx.startp; - f.to = -(eh_frame_size - cie_size); + f.from = -(int32_t)(_Py_SIZE_ROUND_UP(code_size, 8) + eh_frame_size); + uint32_t cie_payload_size; + memcpy(&cie_payload_size, buffer, sizeof(cie_payload_size)); + int cie_size = (int)(sizeof(cie_payload_size) + cie_payload_size); + f.to = -(int32_t)(eh_frame_size - cie_size); /* Write EH frame data and header */ - perf_map_jit_write_fully(ctx.startp, eh_frame_size); + perf_map_jit_write_fully(buffer, eh_frame_size); perf_map_jit_write_fully(&f, sizeof(f)); /* Write padding to maintain alignment */ @@ -1313,12 +667,86 @@ static void perf_map_jit_write_entry(void *state, const void *code_addr, /* Write code load event and associated data */ perf_map_jit_write_fully(&ev, sizeof(ev)); perf_map_jit_write_fully(perf_map_entry, name_length+1); // Include null terminator - perf_map_jit_write_fully((void*)(base), size); // Copy actual machine code + /* + * Ensure each synthetic DSO has unique .text bytes. + * + * perf merges DSOs that share a build-id. Since trampolines can share + * identical code and unwind bytes, perf may resolve all JIT frames to + * the first symbol it saw (including entries from previous runs when + * build-id caching is enabled). Patch a small marker in the emitted + * bytes to make the build-id depend on a per-process salt and code id + * without modifying the live code. + */ + uint64_t marker = perf_jit_map_state.build_id_salt ^ + ((uint64_t)perf_jit_map_state.code_id << 32) ^ + (uint64_t)code_size; + if (size >= sizeof(marker)) { + size_t prefix = size - sizeof(marker); + perf_map_jit_write_fully((void *)(base), prefix); + perf_map_jit_write_fully(&marker, sizeof(marker)); + } + else if (size > 0) { + uint8_t tmp[sizeof(marker)]; + memcpy(tmp, (void *)(base), size); + for (size_t i = 0; i < size; i++) { + tmp[i] ^= (uint8_t)(marker >> (i * 8)); + } + perf_map_jit_write_fully(tmp, size); + } /* Clean up allocated memory */ + PyMutex_Unlock(&perf_jit_map_state.map_lock); PyMem_RawFree(perf_map_entry); } +/* + * Write a complete jitdump entry for a Python function + * + * This is the main function called by Python's trampoline system whenever + * a new piece of JIT-compiled code needs to be recorded. It writes both + * the unwinding information and the code load event to the jitdump file. + * + * The function performs these steps: + * 1. Initialize jitdump system if not already done + * 2. Extract function name and filename from Python code object + * 3. Generate DWARF unwinding information + * 4. Write unwinding info event to jitdump file + * 5. Write code load event to jitdump file + * + * Args: + * state: Jitdump state (currently unused, uses global state) + * code_addr: Address where the compiled code resides + * code_size: Size of the compiled code in bytes + * co: Python code object containing metadata + * + * IMPORTANT: This function signature is part of Python's internal API + * and must not be changed without coordinating with core Python development. + */ +static void perf_map_jit_write_entry(void *state, const void *code_addr, + size_t code_size, PyCodeObject *co) +{ + const char *entry = ""; + const char *filename = ""; + if (co != NULL) { + if (co->co_qualname != NULL) { + entry = PyUnicode_AsUTF8(co->co_qualname); + } + if (co->co_filename != NULL) { + filename = PyUnicode_AsUTF8(co->co_filename); + } + } + perf_map_jit_write_entry_with_name(state, code_addr, code_size, + entry, filename); +} + +void +_PyPerfJit_WriteNamedCode(const void *code_addr, size_t code_size, + const char *entry, const char *filename) +{ + perf_map_jit_write_entry_with_name( + NULL, code_addr, code_size, entry, filename); +} + // ============================================================================= // CLEANUP AND FINALIZATION // ============================================================================= @@ -1346,15 +774,12 @@ static int perf_map_jit_fini(void* state) { * writing to the file when we close it. This prevents corruption * and ensures all data is properly flushed. */ + PyMutex_Lock(&perf_jit_map_state.map_lock); if (perf_jit_map_state.perf_map != NULL) { - PyThread_acquire_lock(perf_jit_map_state.map_lock, 1); fclose(perf_jit_map_state.perf_map); // This also flushes buffers - PyThread_release_lock(perf_jit_map_state.map_lock); - - /* Clean up synchronization primitive */ - PyThread_free_lock(perf_jit_map_state.map_lock); perf_jit_map_state.perf_map = NULL; } + PyMutex_Unlock(&perf_jit_map_state.map_lock); /* * Unmap the memory region diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c index 0d835f3b7f5..58c61e64bfc 100644 --- a/Python/perf_trampoline.c +++ b/Python/perf_trampoline.c @@ -243,7 +243,7 @@ perf_trampoline_code_watcher(PyCodeEvent event, PyCodeObject *co) static void perf_map_write_entry(void *state, const void *code_addr, - unsigned int code_size, PyCodeObject *co) + size_t code_size, PyCodeObject *co) { const char *entry = ""; if (co->co_qualname != NULL) { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 57ce519c3c1..728c0acdd4d 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -879,13 +879,19 @@ pycore_init_builtins(PyThreadState *tstate) interp->common_consts[CONSTANT_ASSERTIONERROR] = PyExc_AssertionError; interp->common_consts[CONSTANT_NOTIMPLEMENTEDERROR] = PyExc_NotImplementedError; - interp->common_consts[CONSTANT_BUILTIN_TUPLE] = (PyObject*)&PyTuple_Type; + interp->common_consts[CONSTANT_BUILTIN_TUPLE] = (PyObject *)&PyTuple_Type; interp->common_consts[CONSTANT_BUILTIN_ALL] = all; interp->common_consts[CONSTANT_BUILTIN_ANY] = any; - interp->common_consts[CONSTANT_BUILTIN_LIST] = (PyObject*)&PyList_Type; - interp->common_consts[CONSTANT_BUILTIN_SET] = (PyObject*)&PySet_Type; - - for (int i=0; i < NUM_COMMON_CONSTANTS; i++) { + interp->common_consts[CONSTANT_BUILTIN_LIST] = (PyObject *)&PyList_Type; + interp->common_consts[CONSTANT_BUILTIN_SET] = (PyObject *)&PySet_Type; + interp->common_consts[CONSTANT_NONE] = Py_None; + interp->common_consts[CONSTANT_EMPTY_STR] = + Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_STR); + interp->common_consts[CONSTANT_TRUE] = Py_True; + interp->common_consts[CONSTANT_FALSE] = Py_False; + interp->common_consts[CONSTANT_MINUS_ONE] = + (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS - 1]; + for (int i = 0; i < NUM_COMMON_CONSTANTS; i++) { assert(interp->common_consts[i] != NULL); } diff --git a/Python/record_functions.c.h b/Python/record_functions.c.h index 504f6e1d990..3163f45f4bb 100644 --- a/Python/record_functions.c.h +++ b/Python/record_functions.c.h @@ -101,10 +101,11 @@ void _PyOpcode_RecordFunction_CODE(_PyInterpreterFrame *frame, _PyStackRef *stac #define _RECORD_TOS_TYPE_INDEX 1 #define _RECORD_NOS_INDEX 2 #define _RECORD_3OS_GEN_FUNC_INDEX 3 -#define _RECORD_NOS_GEN_FUNC_INDEX 4 -#define _RECORD_CALLABLE_INDEX 5 -#define _RECORD_CALLABLE_KW_INDEX 6 -#define _RECORD_4OS_INDEX 7 +#define _RECORD_TOS_INDEX 4 +#define _RECORD_NOS_GEN_FUNC_INDEX 5 +#define _RECORD_CALLABLE_INDEX 6 +#define _RECORD_CALLABLE_KW_INDEX 7 +#define _RECORD_4OS_INDEX 8 const _PyOpcodeRecordEntry _PyOpcode_RecordEntries[256] = { [TO_BOOL_BOOL] = {1, {_RECORD_TOS_TYPE_INDEX}}, @@ -136,15 +137,15 @@ const _PyOpcodeRecordEntry _PyOpcode_RecordEntries[256] = { [STORE_ATTR] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_SUPER_ATTR] = {1, {_RECORD_NOS_INDEX}}, [LOAD_SUPER_ATTR_METHOD] = {1, {_RECORD_NOS_INDEX}}, - [LOAD_ATTR] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_INSTANCE_VALUE] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_MODULE] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_WITH_HINT] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_SLOT] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_CLASS] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_PROPERTY] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [LOAD_ATTR] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_INSTANCE_VALUE] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_MODULE] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_WITH_HINT] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_SLOT] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_CLASS] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_PROPERTY] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = {1, {_RECORD_TOS_INDEX}}, [STORE_ATTR_INSTANCE_VALUE] = {1, {_RECORD_TOS_TYPE_INDEX}}, [STORE_ATTR_WITH_HINT] = {1, {_RECORD_TOS_TYPE_INDEX}}, [STORE_ATTR_SLOT] = {1, {_RECORD_TOS_TYPE_INDEX}}, @@ -158,11 +159,11 @@ const _PyOpcodeRecordEntry _PyOpcode_RecordEntries[256] = { [FOR_ITER_RANGE] = {1, {_RECORD_NOS_GEN_FUNC_INDEX}}, [FOR_ITER_GEN] = {1, {_RECORD_NOS_GEN_FUNC_INDEX}}, [LOAD_SPECIAL] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_METHOD_WITH_VALUES] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_METHOD_NO_DICT] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [LOAD_ATTR_METHOD_LAZY_DICT] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [LOAD_ATTR_METHOD_WITH_VALUES] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_METHOD_NO_DICT] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = {1, {_RECORD_TOS_INDEX}}, + [LOAD_ATTR_METHOD_LAZY_DICT] = {1, {_RECORD_TOS_INDEX}}, [CALL] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_PY_GENERAL] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_BOUND_METHOD_GENERAL] = {1, {_RECORD_CALLABLE_INDEX}}, @@ -199,12 +200,13 @@ const _PyOpcodeRecordSlotMap _PyOpcode_RecordSlotMaps[256] = { [BINARY_OP_SUBSCR_GETITEM] = {1, 0, {0}}, [SEND_GEN] = {1, 0, {0}}, [LOAD_SUPER_ATTR_METHOD] = {1, 0, {0}}, - [LOAD_ATTR_INSTANCE_VALUE] = {1, 0, {0}}, - [LOAD_ATTR_WITH_HINT] = {1, 0, {0}}, - [LOAD_ATTR_SLOT] = {1, 0, {0}}, + [LOAD_ATTR_INSTANCE_VALUE] = {1, 1, {0}}, + [LOAD_ATTR_WITH_HINT] = {1, 1, {0}}, + [LOAD_ATTR_SLOT] = {1, 1, {0}}, + [LOAD_ATTR_CLASS] = {1, 0, {0}}, [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = {1, 0, {0}}, - [LOAD_ATTR_PROPERTY] = {1, 0, {0}}, - [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = {1, 0, {0}}, + [LOAD_ATTR_PROPERTY] = {1, 1, {0}}, + [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = {1, 1, {0}}, [STORE_ATTR_INSTANCE_VALUE] = {1, 0, {0}}, [STORE_ATTR_WITH_HINT] = {1, 0, {0}}, [STORE_ATTR_SLOT] = {1, 0, {0}}, @@ -213,11 +215,11 @@ const _PyOpcodeRecordSlotMap _PyOpcode_RecordSlotMaps[256] = { [GET_ITER_VIRTUAL] = {1, 0, {0}}, [FOR_ITER_GEN] = {1, 0, {0}}, [LOAD_SPECIAL] = {1, 0, {0}}, - [LOAD_ATTR_METHOD_WITH_VALUES] = {1, 0, {0}}, - [LOAD_ATTR_METHOD_NO_DICT] = {1, 0, {0}}, - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = {1, 0, {0}}, - [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = {1, 0, {0}}, - [LOAD_ATTR_METHOD_LAZY_DICT] = {1, 0, {0}}, + [LOAD_ATTR_METHOD_WITH_VALUES] = {1, 1, {0}}, + [LOAD_ATTR_METHOD_NO_DICT] = {1, 1, {0}}, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = {1, 1, {0}}, + [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = {1, 1, {0}}, + [LOAD_ATTR_METHOD_LAZY_DICT] = {1, 1, {0}}, [CALL_PY_GENERAL] = {1, 0, {0}}, [CALL_BOUND_METHOD_GENERAL] = {1, 1, {0}}, [CALL_NON_PY_GENERAL] = {1, 0, {0}}, @@ -237,11 +239,12 @@ const _PyOpcodeRecordSlotMap _PyOpcode_RecordSlotMaps[256] = { [BINARY_OP] = {2, 2, {1, 0}}, }; -const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[8] = { +const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[9] = { [0] = NULL, [_RECORD_TOS_TYPE_INDEX] = _PyOpcode_RecordFunction_TOS_TYPE, [_RECORD_NOS_INDEX] = _PyOpcode_RecordFunction_NOS, [_RECORD_3OS_GEN_FUNC_INDEX] = _PyOpcode_RecordFunction_3OS_GEN_FUNC, + [_RECORD_TOS_INDEX] = _PyOpcode_RecordFunction_TOS, [_RECORD_NOS_GEN_FUNC_INDEX] = _PyOpcode_RecordFunction_NOS_GEN_FUNC, [_RECORD_CALLABLE_INDEX] = _PyOpcode_RecordFunction_CALLABLE, [_RECORD_CALLABLE_KW_INDEX] = _PyOpcode_RecordFunction_CALLABLE_KW, diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 1ee0b3bec68..c6447d03369 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2707,7 +2707,7 @@ PyAPI_FUNC(int) PyUnstable_PerfMapState_Init(void) { PyAPI_FUNC(int) PyUnstable_WritePerfMapEntry( const void *code_addr, - unsigned int code_size, + size_t code_size, const char *entry_name ) { #ifndef MS_WINDOWS @@ -2718,7 +2718,7 @@ PyAPI_FUNC(int) PyUnstable_WritePerfMapEntry( } } PyThread_acquire_lock(perf_map_state.map_lock, 1); - fprintf(perf_map_state.perf_map, "%" PRIxPTR " %x %s\n", (uintptr_t) code_addr, code_size, entry_name); + fprintf(perf_map_state.perf_map, "%" PRIxPTR " %zx %s\n", (uintptr_t) code_addr, code_size, entry_name); fflush(perf_map_state.perf_map); PyThread_release_lock(perf_map_state.map_lock); #endif diff --git a/Tools/build/smelly.py b/Tools/build/smelly.py index 7197d70bc8b..17547d4d916 100755 --- a/Tools/build/smelly.py +++ b/Tools/build/smelly.py @@ -25,6 +25,8 @@ # "Legacy": some old symbols are prefixed by "PY_". EXCEPTIONS = frozenset({ 'PY_TIMEOUT_MAX', + '__jit_debug_descriptor', + '__jit_debug_register_code', }) IGNORED_EXTENSION = "_ctypes_test" diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index a251a045b91..fc3cbf3779d 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -318,14 +318,17 @@ def format_tsv_lines(lines): _abs('Modules/_hacl/*.c'): (200_000, 500), _abs('Modules/posixmodule.c'): (20_000, 500), _abs('Modules/termios.c'): (10_000, 800), + _abs('Modules/_remote_debugging/debug_offsets_validation.h'): (25_000, 1000), _abs('Modules/_remote_debugging/*.h'): (20_000, 1000), _abs('Modules/_testcapimodule.c'): (20_000, 400), _abs('Modules/expat/expat.h'): (10_000, 400), _abs('Objects/stringlib/unicode_format.h'): (10_000, 400), _abs('Objects/typeobject.c'): (380_000, 13_000), _abs('Python/compile.c'): (20_000, 500), + _abs('Python/jit_unwind.c'): (20_000, 300), _abs('Python/optimizer.c'): (100_000, 5_000), _abs('Python/parking_lot.c'): (40_000, 1000), + _abs('Python/perf_jit_trampoline.c'): (40_000, 1000), _abs('Python/pylifecycle.c'): (750_000, 5000), _abs('Python/pystate.c'): (750_000, 5000), _abs('Python/initconfig.c'): (50_000, 500), @@ -344,7 +347,7 @@ def format_tsv_lines(lines): _abs('Modules/_ssl_data_300.h'): (80_000, 10_000), _abs('Modules/_ssl_data_111.h'): (80_000, 10_000), _abs('Modules/cjkcodecs/mappings_*.h'): (160_000, 2_000), - _abs('Modules/clinic/_testclinic.c.h'): (120_000, 5_000), + _abs('Modules/clinic/_testclinic.c.h'): (125_000, 5_000), _abs('Modules/unicodedata_db.h'): (180_000, 3_000), _abs('Modules/unicodename_db.h'): (1_200_000, 15_000), _abs('Objects/unicodetype_db.h'): (240_000, 3_000), diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index d2489387f46..aa89e312b62 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -386,6 +386,8 @@ Python/intrinsics.c - _PyIntrinsics_UnaryFunctions - Python/intrinsics.c - _PyIntrinsics_BinaryFunctions - Python/lock.c - TIME_TO_BE_FAIR_NS - Python/opcode_targets.h - opcode_targets - +Python/jit_unwind.c - __jit_debug_descriptor - +Python/jit_unwind.c - _Py_jit_debug_mutex - Python/perf_trampoline.c - _Py_perfmap_callbacks - Python/perf_jit_trampoline.c - _Py_perfmap_jit_callbacks - Python/perf_jit_trampoline.c - perf_jit_map_state - diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 784e1a8a2d1..aa914783f7c 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -258,8 +258,7 @@ def replace_opcode_if_evaluates_pure( # usually for binary ops with passthrough references 2: [("_LOAD_CONST_INLINE_BORROW", "0, (uintptr_t)result"), - ("_SWAP", "3, 0"), - ("_SWAP", "2, 0")], + ("_RROT_3", "0, 0")], }, } diff --git a/Tools/jit/README.md b/Tools/jit/README.md index 9361f39dcc6..2a687bb9e89 100644 --- a/Tools/jit/README.md +++ b/Tools/jit/README.md @@ -9,7 +9,7 @@ ## Installing LLVM The JIT compiler does not require end users to install any third-party dependencies, but part of it must be *built* using LLVM[^why-llvm]. You are *not* required to build the rest of CPython using LLVM, or even the same version of LLVM (in fact, this is uncommon). -LLVM version 21 is the officially supported version. Both `clang` and `llvm-readobj` need to be installed and discoverable (version suffixes, like `clang-21`, are okay). It's highly recommended that you also have `llvm-objdump` available, since this allows the build script to dump human-readable assembly for the generated code. +LLVM version 21 is the officially supported version. The tools `clang`, `llvm-readobj`, `llvm-objdump`, and `llvm-dwarfdump` need to be installed and discoverable (version suffixes, like `clang-21`, are okay). You can customize the LLVM configuration using environment variables before running configure: @@ -85,7 +85,7 @@ ## Miscellaneous [^pep-744]: [PEP 744](https://peps.python.org/pep-0744/) -[^why-llvm]: Clang is specifically needed because it's the only C compiler with support for guaranteed tail calls (`musttail`), which are required by CPython's continuation-passing-style approach to JIT compilation. Since LLVM also includes other functionalities we need (namely, object file parsing and disassembly), it's convenient to only support one toolchain at this time. +[^why-llvm]: Clang is specifically needed because it's the only C compiler with support for guaranteed tail calls (`musttail`), which are required by CPython's continuation-passing-style approach to JIT compilation. Since LLVM also includes other functionalities we need (namely, object file parsing, disassembly, and DWARF inspection), it's convenient to only support one toolchain at this time. ### Understanding JIT behavior diff --git a/Tools/jit/_dwarf.py b/Tools/jit/_dwarf.py new file mode 100644 index 00000000000..5b6b148562e --- /dev/null +++ b/Tools/jit/_dwarf.py @@ -0,0 +1,236 @@ +"""Utilities for deriving JIT unwind information from DWARF CFI.""" + +import dataclasses +import pathlib +import re +import typing + +_LLVMRun = typing.Callable[..., typing.Awaitable[str]] + + +@dataclasses.dataclass(frozen=True) +class UnwindInfo: + code_alignment_factor: int + data_alignment_factor: int + return_address_register: int + cfa_register: int + cfa_offset: int + frame_pointer_register: int + frame_pointer_offset: int + return_address_offset: int + + +@dataclasses.dataclass(frozen=True) +class ELFUnwindConfig: + frame_pointer: str + return_address: str + register_numbers: typing.Mapping[str, int] + call_instruction_prefixes: tuple[str, ...] + + def is_call_instruction(self, instruction: str) -> bool: + return instruction.startswith(self.call_instruction_prefixes) + + +@dataclasses.dataclass(frozen=True) +class _UnwindRow: + pc: int + cfa_register: str + cfa_offset: int + saved_registers: dict[str, int] + + +class ELFUnwindInfo: + def __init__( + self, + target_name: str, + *, + config: ELFUnwindConfig, + verbose: bool = False, + llvm_version: str, + llvm_tools_install_dir: str | None = None, + llvm_run: _LLVMRun, + ) -> None: + self.target_name = target_name + self.config = config + self.verbose = verbose + self.llvm_version = llvm_version + self.llvm_tools_install_dir = llvm_tools_install_dir + self.llvm_run = llvm_run + + @staticmethod + def _parse_dwarfdump_int( + dump: str, field: str, *, required: bool = True + ) -> int | None: + match = re.search(rf"^\s*{field}:\s+(-?\d+)$", dump, re.MULTILINE) + if match is None: + if required: + raise ValueError(f"missing {field} in llvm-dwarfdump output") + return None + return int(match.group(1)) + + @staticmethod + def _parse_dwarfdump_rows(dump: str) -> list[_UnwindRow]: + row_pattern = re.compile( + r"^\s*0x(?P[0-9a-f]+):\s+" + r"CFA=(?P[A-Z][A-Z0-9]*)" + r"(?P[+-]\d+)?" + r"(?::\s*(?P.*))?$" + ) + saved_pattern = re.compile( + r"(?P[A-Z][A-Z0-9]*)=\[CFA(?P[+-]\d+)?\]" + ) + rows = [] + for line in dump.splitlines(): + row_match = row_pattern.match(line) + if row_match is None: + continue + saved_registers = {} + saved = row_match["saved"] + if saved: + for saved_match in saved_pattern.finditer(saved): + offset = saved_match["offset"] + saved_registers[saved_match["register"]] = ( + int(offset) if offset is not None else 0 + ) + cfa_offset = row_match["cfa_offset"] + rows.append( + _UnwindRow( + pc=int(row_match["pc"], 16), + cfa_register=row_match["cfa_register"], + cfa_offset=int(cfa_offset) if cfa_offset is not None else 0, + saved_registers=saved_registers, + ) + ) + if not rows: + raise ValueError("missing interpreted CFI rows in llvm-dwarfdump output") + return rows + + @staticmethod + def _parse_objdump_instructions(dump: str) -> list[tuple[int, str]]: + instructions = [] + for line in dump.splitlines(): + match = re.match( + r"^\s*(?P[0-9a-f]+):\s+" + r"(?:(?:[0-9a-f]{2}|[0-9a-f]{8})\s+)+" + r"(?P.+)$", + line, + ) + if match: + instructions.append( + ( + int(match["pc"], 16), + re.sub(r"\s+", " ", match["instruction"].strip()), + ) + ) + if not instructions: + raise ValueError("missing instructions in llvm-objdump output") + return instructions + + def _reg_number(self, register: str) -> int: + try: + return self.config.register_numbers[register] + except KeyError as exc: + raise ValueError( + f"unsupported register {register!r} in llvm-dwarfdump output" + ) from exc + + @staticmethod + def _encoded_cfa_offset(byte_offset: int, data_alignment_factor: int) -> int: + if data_alignment_factor == 0: + raise ValueError("DWARF data alignment factor must not be zero") + if byte_offset % data_alignment_factor: + raise ValueError( + f"offset {byte_offset} is not a multiple of " + f"data alignment factor {data_alignment_factor}" + ) + return byte_offset // data_alignment_factor + + async def _read_objdump(self, output: pathlib.Path) -> str: + return await self.llvm_run( + "llvm-objdump", + ["-d", f"{output}"], + echo=self.verbose, + llvm_version=self.llvm_version, + llvm_tools_install_dir=self.llvm_tools_install_dir, + ) + + async def _read_eh_frame(self, output: pathlib.Path) -> str: + return await self.llvm_run( + "llvm-dwarfdump", + ["--eh-frame", f"{output}"], + echo=self.verbose, + llvm_version=self.llvm_version, + llvm_tools_install_dir=self.llvm_tools_install_dir, + ) + + def _executor_call_pc(self, disassembly: str) -> int: + calls = [ + pc + for pc, instruction in self._parse_objdump_instructions(disassembly) + if self.config.is_call_instruction(instruction) + ] + if len(calls) != 1: + raise ValueError( + f"{self.target_name} JIT shim should contain exactly one executor call" + ) + call_pc = calls[0] + return call_pc + + def _active_row(self, eh_frame: str, call_pc: int) -> _UnwindRow: + rows = self._parse_dwarfdump_rows(eh_frame) + active_rows = [row for row in rows if row.pc <= call_pc] + if not active_rows: + raise ValueError( + f"{self.target_name} JIT shim has no CFI row for executor call " + f"at 0x{call_pc:x}" + ) + return max(active_rows, key=lambda row: row.pc) + + def _check_saved_registers(self, row: _UnwindRow) -> None: + if ( + self.config.frame_pointer not in row.saved_registers + or self.config.return_address not in row.saved_registers + ): + raise ValueError( + f"{self.target_name} JIT shim CFI row at 0x{row.pc:x} " + f"does not save {self.config.frame_pointer} and " + f"{self.config.return_address}" + ) + + def _build_unwind_info(self, eh_frame: str, active_row: _UnwindRow) -> UnwindInfo: + code_alignment_factor = self._parse_dwarfdump_int( + eh_frame, "Code alignment factor" + ) + data_alignment_factor = self._parse_dwarfdump_int( + eh_frame, "Data alignment factor" + ) + return_address_register = self._parse_dwarfdump_int( + eh_frame, "Return address column" + ) + assert code_alignment_factor is not None + assert data_alignment_factor is not None + assert return_address_register is not None + return UnwindInfo( + code_alignment_factor=code_alignment_factor, + data_alignment_factor=data_alignment_factor, + return_address_register=return_address_register, + cfa_register=self._reg_number(active_row.cfa_register), + cfa_offset=active_row.cfa_offset, + frame_pointer_register=self._reg_number(self.config.frame_pointer), + frame_pointer_offset=self._encoded_cfa_offset( + active_row.saved_registers[self.config.frame_pointer], + data_alignment_factor, + ), + return_address_offset=self._encoded_cfa_offset( + active_row.saved_registers[self.config.return_address], + data_alignment_factor, + ), + ) + + async def extract(self, output: pathlib.Path) -> UnwindInfo: + disassembly = await self._read_objdump(output) + call_pc = self._executor_call_pc(disassembly) + eh_frame = await self._read_eh_frame(output) + active_row = self._active_row(eh_frame, call_pc) + self._check_saved_registers(active_row) + return self._build_unwind_info(eh_frame, active_row) diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py index 15cac3de3fe..ceee383ea68 100644 --- a/Tools/jit/_targets.py +++ b/Tools/jit/_targets.py @@ -12,6 +12,7 @@ import typing import shlex +import _dwarf import _llvm import _optimizers import _schema @@ -37,6 +38,27 @@ ) +_ELF_UNWIND_AARCH64 = _dwarf.ELFUnwindConfig( + frame_pointer="W29", + return_address="W30", + register_numbers={ + "W29": 29, + "W30": 30, + }, + call_instruction_prefixes=("blr ",), +) + +_ELF_UNWIND_X86_64 = _dwarf.ELFUnwindConfig( + frame_pointer="RBP", + return_address="RIP", + register_numbers={ + "RBP": 6, + "RIP": 16, + }, + call_instruction_prefixes=("callq ", "call "), +) + + @dataclasses.dataclass class _Target(typing.Generic[_S, _R]): triple: str @@ -52,6 +74,7 @@ class _Target(typing.Generic[_S, _R]): verbose: bool = False cflags: str = "" frame_pointers: bool = False + unwind: _dwarf.ELFUnwindConfig | None = None llvm_version: str = _llvm._LLVM_VERSION llvm_tools_install_dir: str | None = None known_symbols: dict[str, int] = dataclasses.field(default_factory=dict) @@ -88,6 +111,32 @@ def _compute_digest(self) -> str: hasher.update(pathlib.Path(dirpath, filename).read_bytes()) return hasher.hexdigest() + def _write_generated_header( + self, + output: pathlib.Path, + *, + digest: str, + comment: str, + lines: typing.Iterable[str], + ) -> None: + output_new = output.with_name(f"{output.name}.new") + try: + with output_new.open("w") as file: + file.write(digest) + if comment: + file.write(f"// {comment}\n") + file.write("\n") + for line in lines: + file.write(f"{line}\n") + try: + output_new.replace(output) + except FileNotFoundError: + # another process probably already moved the file + if not output.is_file(): + raise + finally: + output_new.unlink(missing_ok=True) + async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup: group = _stencils.StencilGroup() args = ["--disassemble", "--reloc", f"{path}"] @@ -234,7 +283,7 @@ async def _build_shim_object(self, output: pathlib.Path) -> None: args_o += self._shim_compile_args() args_o += [ "-c", - # The linked shim is a real function in the final binary, so + # The shim is a real function in the final binary, so # keep unwind info for debuggers and stack walkers. "-fasynchronous-unwind-tables", ] @@ -251,6 +300,11 @@ async def _build_shim_object(self, output: pathlib.Path) -> None: llvm_tools_install_dir=self.llvm_tools_install_dir, ) + async def _get_shim_unwind_info( + self, output: pathlib.Path + ) -> _dwarf.UnwindInfo | None: + return None + async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]: generated_cases = PYTHON_EXECUTOR_CASES_C_H.read_text() cases_and_opnames = sorted( @@ -289,6 +343,7 @@ def build( force: bool = False, jit_stencils: pathlib.Path, jit_shim_object: pathlib.Path, + jit_unwind_info: pathlib.Path, ) -> None: """Build jit_stencils.h and the shim object in the given directory.""" jit_stencils.parent.mkdir(parents=True, exist_ok=True) @@ -300,39 +355,42 @@ def build( outline = "=" * len(warning) print("\n".join(["", outline, warning, request, outline, ""])) digest = f"// {self._compute_digest()}\n" + # The generated headers include the input digest as their first line. + # If every generated artifact is current, skip the expensive rebuild. if ( not force and jit_stencils.exists() and jit_stencils.read_text().startswith(digest) and jit_shim_object.exists() + and jit_unwind_info.exists() + and jit_unwind_info.read_text().startswith(digest) ): return + # Build the shim first so its compiled DWARF CFI can be used to derive + # the unwind rules emitted into jit_unwind_info-.h. ASYNCIO_RUNNER.run(self._build_shim_object(jit_shim_object)) + unwind_info = ASYNCIO_RUNNER.run(self._get_shim_unwind_info(jit_shim_object)) + self._write_generated_header( + jit_unwind_info, + digest=digest, + comment=comment, + lines=_writer.dump_unwind_info(unwind_info), + ) + # Build the uop stencils after the shim metadata has been emitted. stencil_groups = ASYNCIO_RUNNER.run(self._build_stencils()) - jit_stencils_new = jit_stencils.parent / "jit_stencils.h.new" - try: - with jit_stencils_new.open("w") as file: - file.write(digest) - if comment: - file.write(f"// {comment}\n") - file.write("\n") - for line in _writer.dump(stencil_groups, self.known_symbols): - file.write(f"{line}\n") - try: - jit_stencils_new.replace(jit_stencils) - except FileNotFoundError: - # another process probably already moved the file - if not jit_stencils.is_file(): - raise - finally: - jit_stencils_new.unlink(missing_ok=True) + self._write_generated_header( + jit_stencils, + digest=digest, + comment=comment, + lines=_writer.dump(stencil_groups, self.known_symbols), + ) class _COFF( _Target[_schema.COFFSection, _schema.COFFRelocation] ): # pylint: disable = too-few-public-methods def _shim_compile_args(self) -> list[str]: - # The linked shim is part of pythoncore, not a shared extension. + # The shim is part of pythoncore, not a shared extension. # On Windows, Py_BUILD_CORE_MODULE makes public APIs import from # pythonXY.lib, which creates a self-dependency when linking # pythoncore.dll. Build the shim with builtin/core semantics. @@ -450,6 +508,19 @@ class _ELF( symbol_prefix = "" re_global = re.compile(r'\s*\.globl\s+(?P